Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
# build folder for ESP-IDF
build/

# submodule configuration
components/rtps_embedded/thirdparty/Micro-CDR/include/ucdr/config.h

# we only version sdkconfig.defaults
sdkconfig
sdkconfig.old
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@
[submodule "components/gfps_service/detail/nearby"]
path = components/gfps_service/detail/nearby
url = https://github.com/esp-cpp/nearby
[submodule "components/rtps_embedded/thirdparty/Micro-CDR"]
path = components/rtps_embedded/thirdparty/Micro-CDR
url = git@github.com:esp-cpp/Micro-CDR.git
Comment on lines +40 to +42
30 changes: 30 additions & 0 deletions components/rtps_embedded/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
idf_component_register(
SRCS
"src/ThreadPool.cpp"
"src/communication/EsppTransport.cpp"
"src/discovery/ParticipantProxyData.cpp"
"src/discovery/SEDPAgent.cpp"
"src/discovery/SPDPAgent.cpp"
"src/discovery/TopicData.cpp"
"src/entities/Domain.cpp"
"src/entities/Participant.cpp"
"src/entities/Reader.cpp"
"src/entities/StatelessReader.cpp"
"src/entities/Writer.cpp"
"src/messages/MessageReceiver.cpp"
"src/messages/MessageTypes.cpp"
"src/utils/Diagnostics.cpp"
"thirdparty/Micro-CDR/src/c/common.c"
"thirdparty/Micro-CDR/src/c/types/array.c"
"thirdparty/Micro-CDR/src/c/types/basic.c"
"thirdparty/Micro-CDR/src/c/types/sequence.c"
"thirdparty/Micro-CDR/src/c/types/string.c"
INCLUDE_DIRS
"include"
"thirdparty/Micro-CDR/include"
REQUIRES
base_component cdr task thread_pool socket
)
Comment on lines +1 to +27

# target_compile_features(${COMPONENT_LIB} PUBLIC cxx_std_17)
# target_compile_definitions(${COMPONENT_LIB} PUBLIC RTPS_CONFIG_HEADER=\"rtps/config_esp32.h\")
162 changes: 162 additions & 0 deletions components/rtps_embedded/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# rtps_embedded

ESPP component that integrates the [embeddedRTPS](https://github.com/embedded-software-laboratory/embeddedRTPS)
RTPS/DDS stack into the ESPP ecosystem.
Any platform that can build ESPP — including ESP32, Linux, and desktop PCs —
can use this component to discover and exchange typed messages with ROS 2 nodes
or any other DDS participant on the same network using the standard RTPS wire
protocol.

The original embeddedRTPS library has hard dependencies on FreeRTOS and lwIP.
`rtps_embedded` removes those dependencies by replacing all socket, task, and
synchronisation calls with ESPP's platform-agnostic `UdpSocket`, `Task`, and
`ThreadPool` primitives. When built for ESP32, ESPP uses FreeRTOS and lwIP
under the hood; on other platforms it uses the host OS equivalents — the RTPS
code itself is unchanged in either case.

---

## Architecture

```
user code
rtps::Domain — routes packets to participants; owns discovery threads
├── rtps::Participant — groups writers and readers
│ ├── rtps::Writer — publishes CacheChange samples
│ └── rtps::Reader — delivers samples to a user callback
├── rtps::ThreadPool — espp::ThreadPool workers that drain the four
│ incoming/outgoing meta/user traffic queues
└── rtps::EsppTransport — one espp::UdpSocket per open UDP port,
each with its own receive task
```

`EsppTransport` is the sole platform-specific adapter. It wraps ESPP's
`UdpSocket` and `Task`, which in turn map to:

| Build target | Socket backend | Task backend |
|---|---|---|
| ESP32 | lwIP (via ESP-IDF) | FreeRTOS |
| Linux / PC | POSIX sockets | `std::thread` |

---

## Quick-start

```cpp
#include "rtps/entities/Domain.h"

// 1. Construct the domain with the local interface IP.
rtps::Domain domain(local_ip);

// 2. Create a participant *before* completeInit().
rtps::Participant *part = domain.createParticipant();

// 3. Add user-defined writer and reader endpoints.
rtps::Writer *writer = domain.createWriter(*part, "my/topic",
"std_msgs::msg::String", false);
rtps::Reader *reader = domain.createReader(*part, "my/topic",
"std_msgs::msg::String", false);

// 4. Register a receive callback on the reader.
reader->registerCallback(
[](void *, const rtps::ReaderCacheChange &change) {
// process change.getData() / change.copyInto(...)
}, nullptr);

// 5. Start discovery (SPDP/SEDP) and worker threads.
domain.completeInit();

// 6. Publish a sample.
const char *payload = "hello";
writer->newChange(rtps::ChangeKind_t::ALIVE,
reinterpret_cast<const uint8_t *>(payload),
static_cast<rtps::DataSize_t>(strlen(payload) + 1));
```

> **Note**: `createParticipant()` **must** be called before `completeInit()`.
> No new participants can be added after init is complete.

---

## Configuration

Two built-in config headers are provided. Select one by defining
`RTPS_CONFIG_HEADER`, or let `include/rtps/config.h` pick automatically based
on the build target.

| Header | Target |
|---|---|
| [`include/rtps/config_esp32.h`](include/rtps/config_esp32.h) | ESP32 (ESP-IDF) |
| [`include/rtps/config_desktop.h`](include/rtps/config_desktop.h) | Linux / PC |

All tunable constants follow the same layout in both files:

| Constant | Default | Description |
|---|---|---|
| `DOMAIN_ID` | 0 | RTPS domain number (0–230 with UDP) |
| `MAX_NUM_PARTICIPANTS` | 1 | Participant pool size |
| `NUM_STATEFUL_WRITERS` | 5 | User writer endpoint pool |
| `NUM_STATEFUL_READERS` | 5 | User reader endpoint pool |
| `NUM_STATELESS_WRITERS` | 5 | Discovery writer endpoint pool |
| `NUM_STATELESS_READERS` | 5 | Discovery reader endpoint pool |
| `NUM_WRITERS_PER_PARTICIPANT` | 10 | Max writers per participant |
| `NUM_READERS_PER_PARTICIPANT` | 10 | Max readers per participant |
| `HISTORY_SIZE_STATEFUL` | 10 | Per-endpoint history depth |
| `THREAD_POOL_NUM_WRITERS` | 2 | Writer worker threads |
| `THREAD_POOL_NUM_READERS` | 2 | Reader worker threads |
| `THREAD_POOL_WRITER_STACKSIZE` | 4096 B | Writer task stack |
| `THREAD_POOL_READER_STACKSIZE` | 6144 B | Reader / UDP-receive task stack |
| `MAX_NUM_UDP_CONNECTIONS` | 10 | UDP socket pool size |
| `SPDP_RESEND_PERIOD_MS` | 2000 | Discovery announce period |
| `SF_WRITER_HB_PERIOD_MS` | 4000 | Reliable-writer heartbeat period |

The `OVERALL_HEAP_SIZE` constant at the bottom of that file estimates the
total stack RAM consumed by all internal tasks.

---

## ESPP component dependencies

| Component | Purpose |
|---|---|
| `base_component` | ESPP base class with integrated `espp::Logger` |
| `socket` | ESPP `UdpSocket` used by `EsppTransport` |
| `task` | ESPP `Task` for per-port UDP receive loops |
| `thread_pool` | ESPP `ThreadPool` for writer/reader workers |
| `cdr` | CDR serialization helpers |

These components abstract away all OS and network-stack details, so
`rtps_embedded` itself has no direct dependency on FreeRTOS, lwIP, or any
other platform library.

Third-party (vendored in `thirdparty/`):

| Library | Purpose |
|---|---|
| `Micro-CDR` | eProsima CDR (de)serialization for discovery messages |

---

## Example

See [`example/`](example/) for a two-node **initiator / responder** demo.

The same logic runs on any ESPP-supported platform. For ESP32, flash one board
as *Initiator* and a second as *Responder* via menuconfig
(`idf.py menuconfig → RTPS Example Configuration`). The initiator periodically
publishes numbered request messages; the responder echoes each message back on
the response topic.

Key menuconfig options (ESP32 example):

| Option | Description |
|---|---|
| `RTPS_EXAMPLE_ROLE` | `Initiator` or `Responder` |
| `RTPS_EXAMPLE_TOPIC_PREFIX` | Shared topic prefix (e.g. `espp/rtps_example`) |
| `RTPS_EXAMPLE_PUBLISH_PERIOD_MS` | Initiator publish interval |
| `ESP_WIFI_SSID` / `ESP_WIFI_PASSWORD` | Wi-Fi credentials |
21 changes: 21 additions & 0 deletions components/rtps_embedded/example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.20)

set(ENV{IDF_COMPONENT_MANAGER} "0")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)

set(EXTRA_COMPONENT_DIRS
"../../../components/"
)

set(
COMPONENTS
"main esptool_py logger rtps_embedded wifi"
CACHE STRING
"List of components to include"
)

project(rtps_embedded_example)

set(CMAKE_CXX_STANDARD 20)
3 changes: 3 additions & 0 deletions components/rtps_embedded/example/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
idf_component_register(SRC_DIRS "."
INCLUDE_DIRS "."
REQUIRES logger rtps_embedded wifi)
109 changes: 109 additions & 0 deletions components/rtps_embedded/example/main/Kconfig.projbuild
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
menu "RTPS Example Configuration"

choice RTPS_EXAMPLE_ROLE
prompt "Example Role"
default RTPS_EXAMPLE_ROLE_INITIATOR
help
Build one board as the initiator and a second board as the responder
to verify RTPS discovery and end-to-end UInt32 sample exchange.

config RTPS_EXAMPLE_ROLE_INITIATOR
bool "Initiator"
help
Publishes incrementing request samples after it discovers a
responder, then logs the matching responses.

config RTPS_EXAMPLE_ROLE_RESPONDER
bool "Responder"
help
Waits for request samples and echoes the same value back on the
response topic.

endchoice

config RTPS_EXAMPLE_NODE_NAME
string "Participant node name"
default "espp_rtps_initiator" if RTPS_EXAMPLE_ROLE_INITIATOR
default "espp_rtps_responder" if RTPS_EXAMPLE_ROLE_RESPONDER
help
Logical RTPS participant name announced during discovery.

config RTPS_EXAMPLE_DOMAIN_ID
int "RTPS domain ID"
range 0 231
default 0
help
Both boards must use the same domain ID to discover each other.

config RTPS_EXAMPLE_PARTICIPANT_ID
int "RTPS participant ID"
range 0 119
default 1 if RTPS_EXAMPLE_ROLE_INITIATOR
default 2 if RTPS_EXAMPLE_ROLE_RESPONDER
help
Each board should use a unique participant ID within the same domain.

config RTPS_EXAMPLE_TOPIC_PREFIX
string "Topic prefix"
default "espp/rtps_example"
help
Prefix used to derive the request and response topics.

config RTPS_EXAMPLE_ANNOUNCE_PERIOD_MS
int "Discovery announce period (ms)"
range 200 10000
default 1500
help
Period between periodic SPDP/SEDP discovery announcements.

config RTPS_EXAMPLE_PUBLISH_PERIOD_MS
int "Initiator publish period (ms)"
range 250 60000
default 2000
depends on RTPS_EXAMPLE_ROLE_INITIATOR
help
Period between request messages sent by the initiator after a
responder has been discovered.

config RTPS_EXAMPLE_USE_USER_MULTICAST
bool "Use best-effort user-data multicast"
default n
help
If enabled, the example advertises and uses topic-specific
multicast groups for the request and response topics instead of
per-participant unicast delivery.

config RTPS_EXAMPLE_REQUEST_MULTICAST_GROUP
string "Request topic multicast group"
default "239.255.0.11"
depends on RTPS_EXAMPLE_USE_USER_MULTICAST
help
IPv4 multicast group used for the <topic-prefix>/request topic.

config RTPS_EXAMPLE_RESPONSE_MULTICAST_GROUP
string "Response topic multicast group"
default "239.255.0.12"
depends on RTPS_EXAMPLE_USE_USER_MULTICAST
help
IPv4 multicast group used for the <topic-prefix>/response topic.

config ESP_WIFI_SSID
string "WiFi SSID"
default ""
help
SSID (network name) for the example to connect to.

config ESP_WIFI_PASSWORD
string "WiFi Password"
default ""
help
WiFi password (WPA or WPA2) for the example to use.

config ESP_MAXIMUM_RETRY
int "Maximum retry"
default 5
help
Set the maximum retry count to avoid reconnecting forever when the
network is unavailable.

endmenu
Loading