diff --git a/.devcontainer.json b/.devcontainer.json index c0e51d9..08d3592 100644 --- a/.devcontainer.json +++ b/.devcontainer.json @@ -3,18 +3,17 @@ "build": { "dockerfile": "Containerfile", "target": "dev", - "context": ".", - "args": { - "ROS_DISTRO": "kilted", - } + "context": "." }, "workspaceFolder": "/workspace", "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind", "mounts": [], "runArgs": [ - "--net=host" + "--net=host", + "--env-file", + "${localWorkspaceFolder}/.env" // Adjust path as needed ], "containerEnv": { "SHELL": "/bin/bash" } -} +} \ No newline at end of file diff --git a/.env b/.env new file mode 100644 index 0000000..33dbb8c --- /dev/null +++ b/.env @@ -0,0 +1,41 @@ +# Name of this project: +REPO_NAME = ros2-container-template + +#ROS Configuration +ROS_DOMAIN_ID=11 +ROS_DISTRO=jazzy +CYCLONEDDS_URI=/cyclonedds.xml +RMW_IMPLEMENTATION=rmw_zenoh_cpp +ZENOH_ROUTER_CONFIG_URI=/CUSTOM_RMW_ZENOH_ROUTER_CONFIG.json5 +ZENOH_SESSION_CONFIG_URI=/CUSTOM_RMW_ZENOH_SESSION_CONFIG.json5 + +# Registry Configuration +CACHE = ${REG}/${REPO_NAME} +CACHE_AMD64 = ${CACHE}:cache-amd64 +CACHE_ARM64 = ${CACHE}:cache-arm64 +REG=example.registry.com + +# Docker configuration +CONTAINERFILE = Containerfile +CONTAINER_ENGINE = docker +COMPOSEFILE = docker-compose.yml + +# Tag names +# These are dependent on the distro, meaning changing distro will cause a rebuild +BASE_TAG = ${ROS_DISTRO}-base +DEV_TAG = ${ROS_DISTRO}-dev +FINAL_TAG = ${ROS_DISTRO}-final +TAG=${FINAL_TAG} + +# Image names +BASE_IMAGE = ${REPO_NAME}:${BASE_TAG} +DEV_IMAGE = ${REPO_NAME}:${DEV_TAG} +FINAL_IMAGE = ${REPO_NAME}:${FINAL_TAG} + +# Marker files +# These are used by `build`, `test`, `dev`, and `final` commands +# to ensure rebuilds only happen when necessary +MARKER_DIR = .make-markers +BASE_BUILT = ${MARKER_DIR}/base.built +DEV_BUILT = ${MARKER_DIR}/dev.built +FINAL_BUILT = ${MARKER_DIR}/final.built diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml deleted file mode 100644 index 27d31a8..0000000 --- a/.github/workflows/makefile.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Makefile CI - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Workspace Build - run: make build - - # the ros2/examples repo has stderr output on most tests :) - # - name: Workspace Test - # run: make test - - - name: Final Image - run: make final - diff --git a/.gitignore b/.gitignore index 29694e5..c2b7bd5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,10 @@ !Containerfile !.devcontainer.json !README.md - +!docker-compose.yaml +!.env +!/root +!/root/** !/container/ !/container/** !/linux/ diff --git a/.gitmodules b/.gitmodules index 1943c52..fb2d5b4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ -[submodule "src/examples"] - path = src/examples +[submodule "src/example"] + path = src/example url = https://github.com/ros2/examples.git - branch = kilted + branch = jazzy diff --git a/Containerfile b/Containerfile index 69ca21e..9754461 100644 --- a/Containerfile +++ b/Containerfile @@ -1,10 +1,20 @@ -ARG ROS_DISTRO +# Set up ROS2 container for diesl, and configure Zenoh for the RMW +ARG ROS_DISTRO=jazzy TAG=latest REG=registry.gitlab.sitcore.net/haiisw/diesl + + + +########################################################################### +### BASE IMAGE ### +########################################################################### # The base layer only installs dependencies, and does not build any code # It installs a full ros environment -FROM docker.io/osrf/ros:${ROS_DISTRO}-desktop-full AS base -ENV DEBIAN_FRONTEND=noninteractive +# You can either user the diesl base image or the official ROS base image +# FROM ${REG}:diesl-base:${TAG} AS base +FROM ros:${ROS_DISTRO}-ros-base AS base + +ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 LC_ALL=C.UTF-8 OPAL_PREFIX= ROS_DISTRO=${ROS_DISTRO} # Install packages RUN apt-get update \ @@ -15,32 +25,44 @@ RUN apt-get update \ git \ python3-pip \ pip \ - python3-rosdep \ + python3-rosdep \ + wget \ + unzip \ ros-${ROS_DISTRO}-ros-gz \ ros-${ROS_DISTRO}-launch \ ros-${ROS_DISTRO}-launch-ros \ ros-${ROS_DISTRO}-ament-cmake \ ros-${ROS_DISTRO}-ament-cmake-core \ - ros-${ROS_DISTRO}-ament-cmake-python - # TODO add your dependencies here! + ros-${ROS_DISTRO}-ament-cmake-python \ + # alternative RMW packages: + ros-${ROS_DISTRO}-rmw-cyclonedds-cpp \ + ros-${ROS_DISTRO}-rmw-zenoh-cpp \ + # Add additional depencies here as needed, these are for the example + ros-jazzy-example-interfaces \ + ros-jazzy-demo-nodes-cpp \ + python3-pytest \ + python3-numpy + +ENV RMW_IMPLEMENTATION=rmw_zenoh_cpp ZENOH_ROUTER_CONFIG_URI=/CUSTOM_RMW_ZENOH_ROUTER_CONFIG.json5 ZENOH_SESSION_CONFIG_URI=/CUSTOM_RMW_ZENOH_SESSION_CONFIG.json5 +# expose ports used for Zenoh +EXPOSE 7447/tcp +EXPOSE 8000/tcp +EXPOSE 7447/udp +EXPOSE 7446/udp + +COPY root/ / -# Set up the entrypoint -RUN cat < /entrypoint.bash -#!/usr/bin/env bash -source /opt/ros/${ROS_DISTRO}/setup.bash +RUN chmod +x /entrypoint.bash +ENTRYPOINT [ "/entrypoint.bash" ] -if [ -f /workspace/install/setup.bash ]; then - source /workspace/install/setup.bash -fi +STOPSIGNAL SIGINT -exec "\$@" -exec bash -EOF -RUN chmod +x /entrypoint.bash -ENTRYPOINT [ "/entrypoint.bash" ] +########################################################################### +### BUILD IMAGE ### +########################################################################### -# The build layer (which should probably be renamed), actually builds everything. +# Full image with all the functionality, built on top of the base image FROM base AS build # Copy in workspace @@ -50,19 +72,54 @@ WORKDIR /workspace # Actually build package RUN . /opt/ros/${ROS_DISTRO}/setup.sh && colcon build -# Start from mininal image for final -FROM docker.io/library/ros:${ROS_DISTRO}-ros-core AS final +STOPSIGNAL SIGINT + +########################################################################### +### FINAL IMAGE ### +########################################################################### -# TODO uncomment this and add runtime dependencies -# RUN apt-get update \ -# && apt-get install -y --no-install-recommends \ -# my-package -# && rm -rf /var/lib/apt/lists/* +# Stripped down image with only what is needed to run the code +FROM ros:${ROS_DISTRO}-ros-core AS final + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ros-${ROS_DISTRO}-rmw-cyclonedds-cpp \ + ros-${ROS_DISTRO}-rmw-zenoh-cpp \ +# Add additional depencies here as needed, these are examples + ros-jazzy-example-interfaces \ + ros-jazzy-demo-nodes-cpp \ + python3-pytest \ + python3-numpy \ + # and clean up apt cache + && apt-get autoclean \ + && rm -rf /var/lib/apt/lists/* COPY --from=build /workspace/build /workspace/build COPY --from=build /workspace/install /workspace/install -CMD [ "bash" ] # TODO your command here!! +ENV DEBIAN_FRONTEND=noninteractive LANG=C.UTF-8 LC_ALL=C.UTF-8 OPAL_PREFIX= +ENV RMW_IMPLEMENTATION=rmw_zenoh_cpp ZENOH_ROUTER_CONFIG_URI=/DEFAULT_RMW_ZENOH_ROUTER_CONFIG.json5 ZENOH_SESSION_CONFIG_URI=/DEFAULT_RMW_ZENOH_SESSION_CONFIG.json5 +# expose ports used for Zenoh +EXPOSE 7447/tcp +EXPOSE 8000/tcp +EXPOSE 7447/udp +EXPOSE 7446/udp + +COPY root/ / + +RUN chmod +x /entrypoint.bash +ENTRYPOINT [ "/entrypoint.bash" ] + +# Example of run command, in this case startng a zenoh router node +# CMD ["ros2", "run", "rmw_zenoh_cpp", "rmw_zenohd"] + +STOPSIGNAL SIGINT + + + +########################################################################### +### DEV IMAGE ### +########################################################################### # Dev environment with useful tools FROM base AS dev diff --git a/Makefile b/Makefile index 472c4bb..4b3c4c8 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,8 @@ -.PHONY: all sync-submodules dev build test final refresh clean +.PHONY: all sync-submodules dev build test final refresh clean pull push .DEFAULT_GOAL := all -# TODO Configure these -ROS_DISTRO ?= kilted -REPO_NAME ?= ros-template - -CONTAINERFILE := Containerfile -CONTAINER_ENGINE := docker - -# Tag names -# These are dependent on the distro, meaning changing distro will cause a rebuild -BASE_TAG := $(ROS_DISTRO)-base -DEV_TAG := $(ROS_DISTRO)-dev -FINAL_TAG := $(ROS_DISTRO)-final - -# Image names -BASE_IMAGE := $(REPO_NAME):$(BASE_TAG) -DEV_IMAGE := $(REPO_NAME):$(DEV_TAG) -FINAL_IMAGE := $(REPO_NAME):$(FINAL_TAG) - -# Marker files -# These are used by `build`, `test`, `dev`, and `final` commands -# to ensure rebuilds only happen when necessary -MARKER_DIR := .make-markers -BASE_BUILT := $(MARKER_DIR)/base.built -DEV_BUILT := $(MARKER_DIR)/dev.built -FINAL_BUILT := $(MARKER_DIR)/final.built +include .env +export all: final @@ -37,42 +14,42 @@ sync-submodules: dev: $(DEV_BUILT) sync-submodules @echo "==> Entering workspace..." @$(CONTAINER_ENGINE) run \ - --rm \ - --tty \ - --interactive \ - --volume=./:/workspace/:rw \ - --workdir=/workspace \ - --name=$(REPO_NAME)-dev \ - --userns=host \ - --network=host \ - --pid=host \ - --ipc=host \ - -e QT_X11_NO_MITSHM=1 \ - -e NVIDIA_DRIVER_CAPABILITIES=all \ - -e DISPLAY=$$DISPLAY \ - --volume=/tmp/.X11-unix:/tmp/.X11-unix:rw \ - $(DEV_IMAGE) \ - bash + --rm \ + --tty \ + --interactive \ + --volume=./:/workspace/:rw \ + --workdir=/workspace \ + --name=$(REPO_NAME)-dev \ + --userns=host \ + --network=host \ + --pid=host \ + --ipc=host \ + -e QT_X11_NO_MITSHM=1 \ + -e NVIDIA_DRIVER_CAPABILITIES=all \ + -e DISPLAY=$$DISPLAY \ + --volume=/tmp/.X11-unix:/tmp/.X11-unix:rw \ + $(DEV_IMAGE) \ + bash build: $(BASE_BUILT) sync-submodules @echo "==> Building colcon workspace..." @$(CONTAINER_ENGINE) run \ - --rm \ - --volume=./:/workspace/:rw \ - --workdir=/workspace \ - --name=$(REPO_NAME)-build \ - $(BASE_IMAGE) \ - colcon build --symlink-install + --rm \ + --volume=./:/workspace/:rw \ + --workdir=/workspace \ + --name=$(REPO_NAME)-build \ + $(BASE_IMAGE) \ + colcon build --symlink-install test: $(BASE_BUILT) sync-submodules @echo "==> Testing colcon workspace..." @$(CONTAINER_ENGINE) run \ - --rm \ - --volume=./:/workspace/:rw \ - --workdir=/workspace \ - --name=$(REPO_NAME)-test \ - $(BASE_IMAGE) \ - colcon test --event-handlers console_cohesion+ + --rm \ + --volume=./:/workspace/:rw \ + --workdir=/workspace \ + --name=$(REPO_NAME)-test \ + $(BASE_IMAGE) \ + colcon test --event-handlers console_cohesion+ final: $(FINAL_BUILT) sync-submodules @@ -99,10 +76,10 @@ $(DEV_BUILT): $(BASE_BUILT) $(CONTAINERFILE) $(FINAL_BUILT): $(BASE_BUILT) $(CONTAINERFILE) @echo "==> Building final image..." @$(CONTAINER_ENGINE) build \ - -t $(BASE_IMAGE) \ - -f $(CONTAINERFILE) \ - --target=final \ - --build-arg ROS_DISTRO=$(ROS_DISTRO) . + -t $(FINAL_IMAGE) \ + -f $(CONTAINERFILE) \ + --target=final \ + --build-arg ROS_DISTRO=$(ROS_DISTRO) . @mkdir -p $(MARKER_DIR) @touch $@ @@ -115,3 +92,25 @@ clean: -@$(CONTAINER_ENGINE) rmi $(BASE_IMAGE) $(DEV_IMAGE) $(FINAL_IMAGE) 2>/dev/null @echo "==> Removed build markers and deleted images..." +pull: + @echo "==> Pulling images from registry and then removing the tag..." + @$(CONTAINER_ENGINE) login $(REG) || true + -@$(CONTAINER_ENGINE) pull $(REG)/$(BASE_IMAGE) && $(CONTAINER_ENGINE) tag $(REG)/$(BASE_IMAGE) $(BASE_IMAGE) && $(CONTAINER_ENGINE) rmi $(REG)/$(BASE_IMAGE) 2>/dev/null || true + -@$(CONTAINER_ENGINE) pull $(REG)/$(DEV_IMAGE) && $(CONTAINER_ENGINE) tag $(REG)/$(DEV_IMAGE) $(DEV_IMAGE) && $(CONTAINER_ENGINE) rmi $(REG)/$(DEV_IMAGE) 2>/dev/null || true + -@$(CONTAINER_ENGINE) pull $(REG)/$(FINAL_IMAGE) && $(CONTAINER_ENGINE) tag $(REG)/$(FINAL_IMAGE) $(FINAL_IMAGE) && $(CONTAINER_ENGINE) rmi $(REG)/$(FINAL_IMAGE) 2>/dev/null || true + +push: + @echo "==> Pushing images that have been built..." + @$(CONTAINER_ENGINE) login $(REG) || true + @if [ -f "$(MARKER_DIR)/base.built" ]; then \ + $(CONTAINER_ENGINE) tag $(BASE_IMAGE) $(REG)/$(BASE_IMAGE); \ + $(CONTAINER_ENGINE) push $(REG)/$(BASE_IMAGE); \ + fi + @if [ -f "$(MARKER_DIR)/dev.built" ]; then \ + $(CONTAINER_ENGINE) tag $(DEV_IMAGE) $(REG)/$(DEV_IMAGE); \ + $(CONTAINER_ENGINE) push $(REG)/$(DEV_IMAGE); \ + fi + @if [ -f "$(MARKER_DIR)/final.built" ]; then \ + $(CONTAINER_ENGINE) tag $(FINAL_IMAGE) $(REG)/$(FINAL_IMAGE); \ + $(CONTAINER_ENGINE) push $(REG)/$(FINAL_IMAGE); \ + fi \ No newline at end of file diff --git a/README.md b/README.md index 0791460..026b116 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # ros-template -This repository is a template for a containerized [ROS 2](https://docs.ros.org/) project. It uses `make` to implement the following utility commands: +This repository is a template for a containerized [ROS 2](https://docs.ros.org/) project. + +You should first create a fork of the repository before cloning! Otherwise its not a very good template + +It uses `make` to implement the following utility commands: | Command | Description | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | @@ -11,6 +15,8 @@ This repository is a template for a containerized [ROS 2](https://docs.ros.org/) | `make refresh` | Causes the previous set of commands to rebuild the image when run. | | `make clean` | Untags and removes any installed images. | | `make sync-submodules` | Manually syncs and recursively updates git submodules. | +| `make pull` | pulls all built images from the registry. | +| `make push` | Pushes all built images to the registry ## Why? @@ -90,3 +96,13 @@ If this again bothers you, both nix and pixi (the tool that robostack is based o > It sounds like you really dislike containers and container based development. Why did you make this? This template only depends on a container engine, git, and make. These, unlike nix or pixi/conda, are tools almost everyone, regardless of their os, has installed on their system. Perhaps one day things may be different, but until then, I am but a servant to network effects. + +# Whats new? + +I made a few changes to the original template. First, the example is just built in instead of being a submodule which makes it easier to both install and remove. Additionally, I made changes to include a shared '.env' file for setting environment variables which are shared between the container and make file. I also added an example compose file, and changes to the entrypoint to be a copied root directory for any additional configs that are needed, and changed the base contianer file to include the zenoh RMW. Finally, added push and pull commands for pushing built images to an image registry. + +# Credit + +Original tempalte by anglesideangle from https://github.com/anglesideangle/ros-template.git +Also, would like to acknowledge Nathan Hahn for zenoh setup which is what I used for a basis. + diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..0c787dd --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,78 @@ + +###### +# Example compose file for the ROS2 Zenoh Router +###### +### Based on compose file by Nathan Hahn, 2024 + +networks: + ros2: + name: ros2br + driver: 'bridge' + enable_ipv6: true + external: true + ipam: + driver: default + config: + - subnet: 255.255.255.0/16 + ip_range: 192.168.1.0/24 + gateway: 192.168.1.0 + driver_opts: + com.docker.network.bridge.name: ros2br + # com.docker.network.bridge.mtu: 65536 + com.docker.network.container_iface_prefix: ros2br + #com.docker.network.bridge.gateway_mode_ipv6: routed + +#### If building this multiplatform on windows, you need this: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes -c yes +configs: + DEFAULT_RMW_ZENOH_ROUTER_CONFIG.json5: + file: /CUSTOM_RMW_ZENOH_ROUTER_CONFIG.json5 + DEFAULT_RMW_ZENOH_SESSION_CONFIG.json5: + file: /DIESL_RMW_ZENOH_SESSION_CONFIG.json5 + cyclonedds_config: + file: /cyclonedds.xml + +services: + zenoh-router: + image: ${REG}/${REPO_NAME}:${TAG:-latest} + build: + context: ./ + target: final + dockerfile: Containerfile + args: + TAG: ${TAG:-latest} + REG: ${REG} + cache_from: + - ${CACHE:-${REG}/${REPO_NAME}:cache-amd64 + - ${CACHE:-${REG}/${REPO_NAME}:cache-arm64 + cache_to: + - type=registry,ref=${REG}/${REPO_NAME}:cache-amd64 + - type=registry,ref=${REG}/${REPO_NAME}:cache-arm64 + platforms: + - linux/amd64 + - linux/arm64 + environment: + - ROS_ROBOT_ID=${ROS_ROBOT_ID:--1} + - ROS_NAMESPACE=${ROS_NAMESPACE:-${ROS_ROBOT_ID:+r$ROS_ROBOT_ID}} + - ROS_DOMAIN_ID=${ROS_DOMAIN_ID:-11} + - UHLC_MAX_DELTA_MS=3000 + # devices: + # - "/dev/ttyUSB0:/dev/ttyUSB0" # Maps host /dev/ttyUSB0 to container /dev/ttyUSB0 + ipc: shareable + restart: unless-stopped + env_file: + - path: .env + required: true + configs: + - DEFAULT_RMW_ZENOH_ROUTER_CONFIG.json5 + - DEFAULT_RMW_ZENOH_SESSION_CONFIG.json5 + shm_size: 256mb + network_mode: host + # volumes: + # - ./launch:/launch + ports: + - "7447:7448:8000/tcp" + - "7446/udp" + # Launches the zenoh router with the default configuration + command: ["ros2", "run", "rmw_zenoh_cpp", "rmw_zenohd"] + deploy: + mode: global \ No newline at end of file diff --git a/root/CUSTOM_RMW_ZENOH_ROUTER_CONFIG.json5 b/root/CUSTOM_RMW_ZENOH_ROUTER_CONFIG.json5 new file mode 100644 index 0000000..c2d36a4 --- /dev/null +++ b/root/CUSTOM_RMW_ZENOH_ROUTER_CONFIG.json5 @@ -0,0 +1,664 @@ +/// This file attempts to list and document available configuration elements. +/// For a more complete view of the configuration's structure, check out `zenoh/src/config.rs`'s `Config` structure. +/// Note that the values here are correctly typed, but may not be sensible, so copying this file to change only the parts that matter to you is not good practice. +{ + /// The identifier (as unsigned 128bit integer in hexadecimal lowercase - leading zeros are not accepted) + /// that zenoh runtime will use. + /// If not set, a random unsigned 128bit integer will be used. + /// WARNING: this id must be unique in your zenoh network. + // id: "1234567890abcdef", + + /// The node's mode (router, peer or client) + mode: "router", + + /// Which endpoints to connect to. E.g. tcp/localhost:7447. + /// By configuring the endpoints, it is possible to tell zenoh which router/peer to connect to at startup. + /// + /// For TCP/UDP on Linux, it is possible additionally specify the interface to be connected to: + /// E.g. tcp/192.168.0.1:7447#iface=eth0, for connect only if the IP address is reachable via the interface eth0 + /// + /// It is also possible to specify a priority range and/or a reliability setting to be used on the link. + /// For example `tcp/localhost?prio=6-7;rel=0` assigns priorities "data_low" and "background" to the established link. + /// + /// For TCP and TLS links, it is possible to specify the TCP buffer sizes: + /// E.g. tcp/192.168.0.1:7447#so_sndbuf=65000;so_rcvbuf=65000 + connect: { + /// timeout waiting for all endpoints connected (0: no retry, -1: infinite timeout) + /// Accepts a single value (e.g. timeout_ms: 0) + /// or different values for router, peer and client (e.g. timeout_ms: { router: -1, peer: -1, client: 0 }). + timeout_ms: { router: -1, peer: -1, client: 0 }, + + /// The list of endpoints to connect to. + /// Accepts a single list (e.g. endpoints: ["tcp/10.10.10.10:7447", "tcp/11.11.11.11:7447"]) + /// or different lists for router, peer and client (e.g. endpoints: { router: ["tcp/10.10.10.10:7447"], peer: ["tcp/11.11.11.11:7447"] }). + /// + /// See https://docs.rs/zenoh/latest/zenoh/config/struct.EndPoint.html + endpoints: [ + // "/
" + ], + + /// Global connect configuration, + /// Accepts a single value or different values for router, peer and client. + /// The configuration can also be specified for the separate endpoint + /// it will override the global one + /// E.g. tcp/192.168.0.1:7447#retry_period_init_ms=20000;retry_period_max_ms=10000" + + /// exit from application, if timeout exceed + exit_on_failure: { router: false, peer: false, client: true }, + /// connect establishing retry configuration + retry: { + /// initial wait timeout until next connect try + period_init_ms: 1000, + /// maximum wait timeout until next connect try + period_max_ms: 4000, + /// increase factor for the next timeout until nexti connect try + period_increase_factor: 2, + }, + }, + + /// Which endpoints to listen on. E.g. tcp/0.0.0.0:7447. + /// By configuring the endpoints, it is possible to tell zenoh which are the endpoints that other routers, + /// peers, or client can use to establish a zenoh session. + /// + /// For TCP/UDP on Linux, it is possible additionally specify the interface to be listened to: + /// E.g. tcp/0.0.0.0:7447#iface=eth0, for listen connection only on eth0 + /// + /// It is also possible to specify a priority range and/or a reliability setting to be used on the link. + /// For example `tcp/localhost?prio=6-7;rel=0` assigns priorities "data_low" and "background" to the established link. + /// + /// For TCP and TLS links, it is possible to specify the TCP buffer sizes: + /// E.g. tcp/192.168.0.1:7447#so_sndbuf=65000;so_rcvbuf=65000 + listen: { + /// timeout waiting for all listen endpoints (0: no retry, -1: infinite timeout) + /// Accepts a single value (e.g. timeout_ms: 0) + /// or different values for router, peer and client (e.g. timeout_ms: { router: -1, peer: -1, client: 0 }). + timeout_ms: 0, + + /// The list of endpoints to listen on. + /// Accepts a single list (e.g. endpoints: ["tcp/[::]:7447", "udp/[::]:7447"]) + /// or different lists for router, peer and client (e.g. endpoints: { router: ["tcp/[::]:7447"], peer: ["tcp/[::]:0"] }). + /// + /// See https://docs.rs/zenoh/latest/zenoh/config/struct.EndPoint.html + endpoints: [ + "tcp/[::]:7448", + ], + + /// Global listen configuration, + /// Accepts a single value or different values for router, peer and client. + /// The configuration can also be specified for the separate endpoint + /// it will override the global one + /// E.g. tcp/192.168.0.1:7447#exit_on_failure=false;retry_period_max_ms=1000" + + /// exit from application, if timeout exceed + exit_on_failure: true, + /// listen retry configuration + retry: { + /// initial wait timeout until next try + period_init_ms: 1000, + /// maximum wait timeout until next try + period_max_ms: 4000, + /// increase factor for the next timeout until next try + period_increase_factor: 2, + }, + }, + /// Configure the session open behavior. + open: { + /// Configure the conditions to be met before session open returns. + return_conditions: { + /// Session open waits to connect to scouted peers and routers before returning. + /// When set to false, first publications and queries after session open from peers may be lost. + connect_scouted: true, + /// Session open waits to receive initial declares from connected peers before returning. + /// Setting to false may cause extra traffic at startup from peers. + declares: true, + }, + }, + /// Configure the scouting mechanisms and their behaviours + scouting: { + /// In client mode, the period in milliseconds dedicated to scouting for a router before failing. + timeout: 3000, + /// In peer mode, the maximum period in milliseconds dedicated to scouting remote peers before attempting other operations. + delay: 500, + /// The multicast scouting configuration. + multicast: { + /// Whether multicast scouting is enabled or not + /// + /// ROS setting: disable multicast discovery by default + enabled: true, + /// The socket which should be used for multicast scouting + address: "224.0.0.252:7446", + /// The network interface which should be used for multicast scouting + interface: "auto", // If not set or set to "auto" the interface if picked automatically + /// The time-to-live on multicast scouting packets + ttl: 1, + /// Which type of Zenoh instances to automatically establish sessions with upon discovery on UDP multicast. + /// Accepts a single value (e.g. autoconnect: ["router", "peer"]) which applies whatever the configured "mode" is, + /// or different values for router, peer or client mode (e.g. autoconnect: { router: [], peer: ["router", "peer"] }). + /// Each value is a list of: "peer", "router" and/or "client". + autoconnect: { router: ["router"], peer: ["router", "peer"], client: ["router"] }, + /// Strategy for autoconnection, mainly to avoid nodes connecting to each other redundantly. + /// Possible options are: + /// - "always": always attempt to autoconnect, may result in redundant connections. + /// - "greater-zid": attempt to connect to another node only if its own zid is greater than the other's. + /// If both nodes use this strategy, only one will attempt the connection. + /// This strategy may not be suited if one of the nodes is not reachable by the other one, for example + /// because of a private IP. + /// Accepts a single value (e.g. autoconnect: "always") which applies whatever node would be auto-connected to, + /// or different values for router and/or peer depending on the type of node detected + /// (e.g. autoconnect_strategy : { to_router: "always", to_peer: "greater-zid" }), + /// or different values for router or peer mode + /// (e.g. autoconnect_strategy : { peer: { to_router: "always", to_peer: "greater-zid" } }). + autoconnect_strategy: { router: { to_router: "always", to_peer: "always" } }, + /// Whether or not to listen for scout messages on UDP multicast and reply to them. + listen: true, + }, + /// The gossip scouting configuration. Note that instances in "client" mode do not participate in gossip. + gossip: { + /// Whether gossip scouting is enabled or not + enabled: true, + /// When true, gossip scouting information are propagated multiple hops to all nodes in the local network. + /// When false, gossip scouting information are only propagated to the next hop. + /// Activating multihop gossip implies more scouting traffic and a lower scalability. + /// It mostly makes sense when using "linkstate" routing mode where all nodes in the subsystem don't have + /// direct connectivity with each other. + multihop: true, + /// Which type of Zenoh instances to send gossip messages to. + /// Accepts a single value (e.g. target: ["router", "peer"]) which applies whatever the configured "mode" is, + /// or different values for router or peer mode (e.g. target: { router: ["router", "peer"], peer: ["router"] }). + /// Each value is a list of "peer" and/or "router". + /// ROS setting: by default all peers rely on the router to discover each other. Thus configuring the peer to send gossip + /// messages only to the router is sufficient and avoids unecessary traffic between Nodes at launch time. + target: { router: ["router", "peer"], peer: ["router"]}, + /// Which type of Zenoh instances to automatically establish sessions with upon discovery on gossip. + /// Accepts a single value (e.g. autoconnect: ["router", "peer"]) which applies whatever the configured "mode" is, + /// or different values for router or peer mode (e.g. autoconnect: { router: [], peer: ["router", "peer"] }). + /// Each value is a list of: "peer" and/or "router". + autoconnect: { router: ["router"], peer: ["router", "peer"] }, + /// Strategy for autoconnection, mainly to avoid nodes connecting to each other redundantly. + /// Possible options are: + /// - "always": always attempt to autoconnect, may result in redundant connection which will then be closed. + /// - "greater-zid": attempt to connect to another node only if its own zid is greater than the other's. + /// If both nodes use this strategy, only one will attempt the connection. + /// This strategy may not be suited if one of the nodes is not reachable by the other one, for example + /// because of a private IP. + /// Accepts a single value (e.g. autoconnect: "always") which applies whatever node would be auto-connected to, + /// or different values for router and/or peer depending on the type of node detected + /// (e.g. autoconnect_strategy : { to_router: "always", to_peer: "greater-zid" }), + /// or different values for router or peer mode + /// (e.g. autoconnect_strategy : { peer: { to_router: "always", to_peer: "greater-zid" } }). + autoconnect_strategy: { router: { to_router: "always", to_peer: "always" } }, + }, + }, + + /// Configuration of data messages timestamps management. + timestamping: { + /// Whether data messages should be timestamped if not already. + /// Accepts a single boolean value or different values for router, peer and client. + /// + /// ROS setting: PublicationCache which is required for transient_local durability + /// only works when time-stamping is enabled. + enabled: { router: true, peer: true, client: true }, + /// Whether data messages with timestamps in the future should be dropped or not. + /// If set to false (default), messages with timestamps in the future are retimestamped. + /// Timestamps are ignored if timestamping is disabled. + drop_future_timestamp: false, + }, + + /// The default timeout to apply to queries in milliseconds. + /// ROS setting: increase the value to avoid timeout at launch time with a large number of Nodes starting all together. + /// Note: the requests to services and actions are hard-coded with an infinite timeout. Hence, this setting + /// only applies to the queries made by the Advanced Subscriber for TRANSIENT_LOCAL implementation. + queries_default_timeout: 60000, + + /// The routing strategy to use and it's configuration. + routing: { + /// The routing strategy to use in routers and it's configuration. + router: { + /// When set to true a router will forward data between two peers + /// directly connected to it if it detects that those peers are not + /// connected to each other. + /// The failover brokering only works if gossip discovery is enabled + /// and peers are configured with gossip target "router". + /// ROS setting: disabled by default because it serves no purpose when each peer connects directly to all others, + /// and it introduces additional management overhead and extra messages during system startup. + peers_failover_brokering: true, + }, + /// The routing strategy to use in peers and it's configuration. + peer: { + /// The routing strategy to use in peers. ("peer_to_peer" or "linkstate"). + mode: "peer_to_peer", + }, + /// The interests-based routing configuration. + /// This configuration applies regardless of the mode (router, peer or client). + interests: { + /// The timeout to wait for incoming interests declarations in milliseconds. + /// The expiration of this timeout implies that the discovery protocol might be incomplete, + /// leading to potential loss of messages, queries or liveliness tokens. + timeout: 10000, + }, + }, + + // /// Overwrite QoS options for Zenoh messages by key expression (ignores Zenoh API QoS config for overwritten values) + // qos: { + // /// Overwrite QoS options for PUT and DELETE messages + // publication: [ + // { + // /// PUT and DELETE messages on key expressions that are included by these key expressions + // /// will have their QoS options overwritten by the given config. + // key_exprs: ["demo/**", "example/key"], + // /// Configurations that will be applied on the publisher. + // /// Options that are supplied here will overwrite the configuration given in Zenoh API + // config: { + // congestion_control: "block", + // priority: "data_high", + // express: true, + // reliability: "best_effort", + // allowed_destination: "remote", + // }, + // }, + // ], + // }, + + // /// The declarations aggregation strategy. + // aggregation: { + // /// A list of key-expressions for which all included subscribers will be aggregated into. + // subscribers: [ + // // key_expression + // ], + // /// A list of key-expressions for which all included publishers will be aggregated into. + // publishers: [ + // // key_expression + // ], + // }, + + // /// Namespace prefix. + // /// If specified, all outgoing key expressions will be automatically prefixed with specified string, + // /// and all incoming key expressions will be stripped of specified prefix. + // /// The namespace prefix should satisfy all key expression constraints + // /// and additionally it can not contain wild characters ('*'). + // /// Namespace is applied to the session. + // /// E. g. if session has a namespace of "1" then session.put("my/keyexpr", my_message), + // /// will put a message into 1/my/keyexpr. Same applies to all other operations within this session. + // namespace: "my/namespace", + + // /// The downsampling declaration. + // downsampling: [ + // { + // /// Optional Id, has to be unique + // "id": "wlan0egress", + // /// Optional list of network interfaces messages will be processed on, the rest will be passed as is. + // /// If absent, the rules will be applied to all interfaces, in case of an empty list it means that they will not be applied to any. + // interfaces: [ "wlan0" ], + // /// Optional list of data flows messages will be processed on ("egress" and/or "ingress"). + // /// If absent, the rules will be applied to both flows. + // flow: ["ingress", "egress"], + // /// List of message type on which downsampling will be applied. Must not be empty. + // messages: [ + // /// Publication (Put and Delete) + // "push", + // /// Get + // "query", + // /// Queryable Reply to a Query + // "reply" + // ], + // /// A list of downsampling rules: key_expression and the maximum frequency in Hertz + // rules: [ + // { key_expr: "demo/example/zenoh-rs-pub", freq: 0.1 }, + // ], + // }, + // ], + + // /// Configure access control (ACL) rules + // access_control: { + // /// [true/false] acl will be activated only if this is set to true + // "enabled": false, + // /// [deny/allow] default permission is deny (even if this is left empty or not specified) + // "default_permission": "deny", + // /// Rule set for permissions allowing or denying access to key-expressions + // "rules": + // [ + // { + // /// Id has to be unique within the rule set + // "id": "rule1", + // "messages": [ + // "put", "delete", "declare_subscriber", + // "query", "reply", "declare_queryable", + // "liveliness_token", "liveliness_query", "declare_liveliness_subscriber", + // ], + // "flows":["egress","ingress"], + // "permission": "allow", + // "key_exprs": [ + // "test/demo" + // ], + // }, + // { + // "id": "rule2", + // "messages": [ + // "put", "delete", "declare_subscriber", + // "query", "reply", "declare_queryable", + // ], + // "flows":["ingress"], + // "permission": "allow", + // "key_exprs": [ + // "**" + // ], + // }, + // ], + // /// List of combinations of subjects. + // /// + // /// If a subject property (i.e. username, certificate common name or interface) is empty + // /// it is interpreted as a wildcard. Moreover, a subject property cannot be an empty list. + // "subjects": + // [ + // { + // /// Id has to be unique within the subjects list + // "id": "subject1", + // /// Subjects can be interfaces + // "interfaces": [ + // "lo0", + // "en0", + // ], + // /// Subjects can be cert_common_names when using TLS or Quic + // "cert_common_names": [ + // "example.zenoh.io" + // ], + // /// Subjects can be usernames when using user/password authentication + // "usernames": [ + // "zenoh-example" + // ], + // /// This instance translates internally to this filter: + // /// (interface="lo0" && cert_common_name="example.zenoh.io" && username="zenoh-example") || + // /// (interface="en0" && cert_common_name="example.zenoh.io" && username="zenoh-example") + // }, + // { + // "id": "subject2", + // "interfaces": [ + // "lo0", + // "en0", + // ], + // "cert_common_names": [ + // "example2.zenoh.io" + // ], + // /// This instance translates internally to this filter: + // /// (interface="lo0" && cert_common_name="example2.zenoh.io") || + // /// (interface="en0" && cert_common_name="example2.zenoh.io") + // }, + // { + // "id": "subject3", + // /// An empty subject combination is a wildcard + // }, + // ], + // /// The policies list associates rules to subjects + // "policies": + // [ + // /// Each policy associates one or multiple rules to one or multiple subject combinations + // { + // /// Id is optional. If provided, it has to be unique within the policies list + // "id": "policy1", + // /// Rules and Subjects are identified with their unique IDs declared above + // "rules": ["rule1"], + // "subjects": ["subject1", "subject2"], + // }, + // { + // "rules": ["rule2"], + // "subjects": ["subject3"], + // }, + // ] + //}, + + /// Configure internal transport parameters + transport: { + unicast: { + /// Timeout in milliseconds when opening a link + /// ROS setting: increase the value to avoid timeout at launch time with a large number of Nodes starting all together + open_timeout: 60000, + /// Timeout in milliseconds when accepting a link + /// ROS setting: increase the value to avoid timeout at launch time with a large number of Nodes starting all together + accept_timeout: 60000, + /// Maximum number of links in pending state while performing the handshake for accepting it + /// ROS setting: increase the value to support a large number of Nodes starting all together + accept_pending: 10000, + /// Maximum number of transports that can be simultaneously alive for a single zenoh sessions + /// ROS setting: increase the value to support a large number of Nodes starting all together + max_sessions: 10000, + /// Maximum number of incoming links that are admitted per transport + max_links: 1, + /// Enables the LowLatency transport + /// This option does not make LowLatency transport mandatory, the actual implementation of transport + /// used will depend on Establish procedure and other party's settings + /// + /// NOTE: Currently, the LowLatency transport doesn't preserve QoS prioritization. + /// NOTE: Due to the note above, 'lowlatency' is incompatible with 'qos' option, so in order to + /// enable 'lowlatency' you need to explicitly disable 'qos'. + /// NOTE: LowLatency transport does not support the fragmentation, so the message size should be + /// smaller than the tx batch_size. + lowlatency: false, + /// Enables QoS on unicast communications. + qos: { + enabled: true, + }, + /// Enables compression on unicast communications. + /// Compression capabilities are negotiated during session establishment. + /// If both Zenoh nodes support compression, then compression is activated. + compression: { + enabled: false, + }, + }, + /// WARNING: multicast communication does not perform any negotiation upon group joining. + /// Because of that, it is important that all transport parameters are the same to make + /// sure all your nodes in the system can communicate. One common parameter to configure + /// is "transport/link/tx/batch_size" since its default value depends on the actual platform + /// when operating on multicast. + /// E.g., the batch size on Linux and Windows is 65535 bytes, on Mac OS X is 9216, and anything else is 8192. + multicast: { + /// JOIN message transmission interval in milliseconds. + join_interval: 2500, + /// Maximum number of multicast sessions. + max_sessions: 1000, + /// Enables QoS on multicast communication. + /// Default to false for Zenoh-to-Zenoh-Pico out-of-the-box compatibility. + qos: { + enabled: false, + }, + /// Enables compression on multicast communication. + /// Default to false for Zenoh-to-Zenoh-Pico out-of-the-box compatibility. + compression: { + enabled: false, + }, + }, + link: { + /// An optional whitelist of protocols to be used for accepting and opening sessions. If not + /// configured, all the supported protocols are automatically whitelisted. The supported + /// protocols are: ["tcp" , "udp", "tls", "quic", "ws", "unixsock-stream", "vsock"] For + /// example, to only enable "tls" and "quic": protocols: ["tls", "quic"], + /// + /// Configure the zenoh TX parameters of a link + tx: { + /// The resolution in bits to be used for the message sequence numbers. + /// When establishing a session with another Zenoh instance, the lowest value of the two instances will be used. + /// Accepted values: 8bit, 16bit, 32bit, 64bit. + sequence_number_resolution: "32bit", + /// Link lease duration in milliseconds to announce to other zenoh nodes + /// ROS setting: increase the value to avoid lease expiration at launch time with a large number of Nodes starting all together + lease: 60000, + /// Number of keep-alive messages in a link lease duration. If no data is sent, keep alive + /// messages will be sent at the configured time interval. + /// NOTE: In order to consider eventual packet loss and transmission latency and jitter, + /// set the actual keep_alive interval to one fourth of the lease time: i.e. send + /// 4 keep_alive messages in a lease period. Changing the lease time will have the + /// keep_alive messages sent more or less often. + /// This is in-line with the ITU-T G.8013/Y.1731 specification on continuous connectivity + /// check which considers a link as failed when no messages are received in 3.5 times the + /// target interval. + /// ROS setting: decrease the value since Nodes are communicating over the loopback + /// where keep-alive messages have less chances to be lost. + keep_alive: 2, + /// Batch size in bytes is expressed as a 16bit unsigned integer. + /// Therefore, the maximum batch size is 2^16-1 (i.e. 65535). + /// The default batch size value is the maximum batch size: 65535. + batch_size: 65535, + /// Each zenoh link has a transmission queue that can be configured + queue: { + /// The size of each priority queue indicates the number of batches a given queue can contain. + /// NOTE: the number of batches in each priority must be included between 1 and 16. Different values will result in an error. + /// The amount of memory being allocated for each queue is then SIZE_XXX * BATCH_SIZE. + /// In the case of the transport link MTU being smaller than the ZN_BATCH_SIZE, + /// then amount of memory being allocated for each queue is SIZE_XXX * LINK_MTU. + /// If qos is false, then only the DATA priority will be allocated. + size: { + control: 2, + real_time: 2, + interactive_high: 2, + interactive_low: 2, + data_high: 2, + data: 2, + data_low: 2, + background: 2, + }, + /// Congestion occurs when the queue is empty (no available batch). + congestion_control: { + /// Behavior pushing CongestionControl::Drop messages to the queue. + drop: { + /// The maximum time in microseconds to wait for an available batch before dropping a droppable message if still no batch is available. + wait_before_drop: 1000, + /// The maximum deadline limit for multi-fragment messages. + max_wait_before_drop_fragments: 50000, + }, + /// Behavior pushing CongestionControl::Block messages to the queue. + block: { + /// The maximum time in microseconds to wait for an available batch before closing the transport session when sending a blocking message + /// if still no batch is available. + /// ROS setting: unlike DEFAULT_RMW_ZENOH_SESSION_CONFIG.json5, no change here: + /// as the router is routing messages to outside the robot, possibly over WiFi, + /// keeping a lower value ensure the router is not blocked for too long in case of congestioned WiFi. + wait_before_close: 5000000, + }, + }, + /// Perform batching of messages if they are smaller of the batch_size + batching: { + /// Perform adaptive batching of messages if they are smaller of the batch_size. + /// When the network is detected to not be fast enough to transmit every message individually, many small messages may be + /// batched together and sent all at once on the wire reducing the overall network overhead. This is typically of a high-throughput + /// scenario mainly composed of small messages. In other words, batching is activated by the network back-pressure. + enabled: true, + /// The maximum time limit (in ms) a message should be retained for batching when back-pressure happens. + time_limit: 1, + }, + allocation: { + /// Mode for memory allocation of batches in the priority queues. + /// - "init": batches are allocated at queue initialization time. + /// - "lazy": batches are allocated when needed up to the maximum number of batches configured in the size configuration parameter. + mode: "lazy", + }, + }, + }, + /// Configure the zenoh RX parameters of a link + rx: { + /// Receiving buffer size in bytes for each link + /// The default the rx_buffer_size value is the same as the default batch size: 65535. + /// For very high throughput scenarios, the rx_buffer_size can be increased to accommodate + /// more in-flight data. This is particularly relevant when dealing with large messages. + /// E.g. for 16MiB rx_buffer_size set the value to: 16777216. + buffer_size: 16777216, + /// Maximum size of the defragmentation buffer at receiver end. + /// Fragmented messages that are larger than the configured size will be dropped. + /// The default value is 1GiB. This would work in most scenarios. + /// NOTE: reduce the value if you are operating on a memory constrained device. + max_message_size: 1073741824, + }, + /// Configure TLS specific parameters + tls: { + /// Path to the certificate of the certificate authority used to validate either the server + /// or the client's keys and certificates, depending on the node's mode. If not specified + /// on router mode then the default WebPKI certificates are used instead. + root_ca_certificate: null, + /// Path to the TLS listening side private key + listen_private_key: null, + /// Path to the TLS listening side public certificate + listen_certificate: null, + /// Enables mTLS (mutual authentication), client authentication + enable_mtls: false, + /// Path to the TLS connecting side private key + connect_private_key: null, + /// Path to the TLS connecting side certificate + connect_certificate: null, + // Whether or not to verify the matching between hostname/dns and certificate when connecting, + // if set to false zenoh will disregard the common names of the certificates when verifying servers. + // This could be dangerous because your CA can have signed a server cert for foo.com, that's later being used to host a server at baz.com. If you wan't your + // ca to verify that the server at baz.com is actually baz.com, let this be true (default). + verify_name_on_connect: true, + // Whether or not to close links when remote certificates expires. + // If set to true, links that require certificates (tls/quic) will automatically disconnect when the time of expiration of the remote certificate chain is reached + // note that mTLS (client authentication) is required for a listener to disconnect a client on expiration + close_link_on_expiration: false, + /// Optional configuration for TCP system buffers sizes for TLS links + /// + /// Configure TCP read buffer size (bytes) + // so_rcvbuf: 123456, + /// Configure TCP write buffer size (bytes) + // so_sndbuf: 123456, + }, + // // Configure optional TCP link specific parameters + // tcp: { + // /// Optional configuration for TCP system buffers sizes for TCP links + // /// + // /// Configure TCP read buffer size (bytes) + // // so_rcvbuf: 123456, + // /// Configure TCP write buffer size (bytes) + // // so_sndbuf: 123456, + // } + }, + /// Shared memory configuration. + /// NOTE: shared memory can be used only if zenoh is compiled with "shared-memory" feature, otherwise + /// settings in this section have no effect. + shared_memory: { + /// Whether shared memory is enabled or not. + /// If set to `true`, the SHM buffer optimization support will be announced to other parties. (default `true`). + /// This option doesn't make SHM buffer optimization mandatory, the real support depends on other party setting. + /// A probing procedure for shared memory is performed upon session opening. To enable zenoh to operate + /// over shared memory (and to not fallback on network mode), shared memory needs to be enabled also on the + /// subscriber side. By doing so, the probing procedure will succeed and shared memory will operate as expected. + /// + /// ROS setting: disabled by default until fully tested + enabled: true, + /// SHM resources initialization mode (default "lazy"). + /// - "lazy": SHM subsystem internals will be initialized lazily upon the first SHM buffer + /// allocation or reception. This setting provides better startup time and optimizes resource usage, + /// but produces extra latency at the first SHM buffer interaction. + /// - "init": SHM subsystem internals will be initialized upon Session opening. This setting sacrifices + /// startup time, but guarantees no latency impact when first SHM buffer is processed. + mode: "lazy", + }, + auth: { + /// The configuration of authentication. + /// A password implies a username is required. + usrpwd: { + user: null, + password: null, + /// The path to a file containing the user password dictionary + dictionary_file: null, + }, + pubkey: { + public_key_pem: null, + private_key_pem: null, + public_key_file: null, + private_key_file: null, + key_size: null, + known_keys_file: null, + }, + }, + }, + + /// Configure the Admin Space + /// Unstable: this configuration part works as advertised, but may change in a future release + adminspace: { + /// Enables the admin space + enabled: true, + /// read and/or write permissions on the admin space + permissions: { + read: true, + write: false, + }, + }, + +} \ No newline at end of file diff --git a/root/CUSTOM_RMW_ZENOH_SESSION_CONFIG.json5 b/root/CUSTOM_RMW_ZENOH_SESSION_CONFIG.json5 new file mode 100644 index 0000000..febf941 --- /dev/null +++ b/root/CUSTOM_RMW_ZENOH_SESSION_CONFIG.json5 @@ -0,0 +1,673 @@ +/// This file attempts to list and document available configuration elements. +/// For a more complete view of the configuration's structure, check out `zenoh/src/config.rs`'s `Config` structure. +/// Note that the values here are correctly typed, but may not be sensible, so copying this file to change only the parts that matter to you is not good practice. +{ + /// The identifier (as unsigned 128bit integer in hexadecimal lowercase - leading zeros are not accepted) + /// that zenoh runtime will use. + /// If not set, a random unsigned 128bit integer will be used. + /// WARNING: this id must be unique in your zenoh network. + // id: "1234567890abcdef", + + /// The node's mode (router, peer or client) + mode: "peer", + + /// Which endpoints to connect to. E.g. tcp/localhost:7447. + /// By configuring the endpoints, it is possible to tell zenoh which router/peer to connect to at startup. + /// + /// For TCP/UDP on Linux, it is possible additionally specify the interface to be connected to: + /// E.g. tcp/192.168.0.1:7447#iface=eth0, for connect only if the IP address is reachable via the interface eth0 + /// + /// It is also possible to specify a priority range and/or a reliability setting to be used on the link. + /// For example `tcp/localhost?prio=6-7;rel=0` assigns priorities "data_low" and "background" to the established link. + /// + /// For TCP and TLS links, it is possible to specify the TCP buffer sizes: + /// E.g. tcp/192.168.0.1:7447#so_sndbuf=65000;so_rcvbuf=65000 + connect: { + /// timeout waiting for all endpoints connected (0: no retry, -1: infinite timeout) + /// Accepts a single value (e.g. timeout_ms: 0) + /// or different values for router, peer and client (e.g. timeout_ms: { router: -1, peer: -1, client: 0 }). + timeout_ms: { router: -1, peer: -1, client: 0 }, + + /// The list of endpoints to connect to. + /// Accepts a single list (e.g. endpoints: ["tcp/10.10.10.10:7447", "tcp/11.11.11.11:7447"]) + /// or different lists for router, peer and client (e.g. endpoints: { router: ["tcp/10.10.10.10:7447"], peer: ["tcp/11.11.11.11:7447"] }). + /// + /// See https://docs.rs/zenoh/latest/zenoh/config/struct.EndPoint.html + /// + /// ROS setting: By default connect to the Zenoh router on localhost on port 7447. + endpoints: { + peer: [ + "tcp/172.28.0.1:7448" + ] + }, + + /// Global connect configuration, + /// Accepts a single value or different values for router, peer and client. + /// The configuration can also be specified for the separate endpoint + /// it will override the global one + /// E.g. tcp/192.168.0.1:7447#retry_period_init_ms=20000;retry_period_max_ms=10000" + + /// exit from application, if timeout exceed + exit_on_failure: { router: false, peer: false, client: true }, + /// connect establishing retry configuration + retry: { + /// initial wait timeout until next connect try + period_init_ms: 1000, + /// maximum wait timeout until next connect try + period_max_ms: 4000, + /// increase factor for the next timeout until nexti connect try + period_increase_factor: 2, + }, + }, + + /// Which endpoints to listen on. E.g. tcp/0.0.0.0:7447. + /// By configuring the endpoints, it is possible to tell zenoh which are the endpoints that other routers, + /// peers, or client can use to establish a zenoh session. + /// + /// For TCP/UDP on Linux, it is possible additionally specify the interface to be listened to: + /// E.g. tcp/0.0.0.0:7447#iface=eth0, for listen connection only on eth0 + /// + /// It is also possible to specify a priority range and/or a reliability setting to be used on the link. + /// For example `tcp/localhost?prio=6-7;rel=0` assigns priorities "data_low" and "background" to the established link. + /// + /// For TCP and TLS links, it is possible to specify the TCP buffer sizes: + /// E.g. tcp/192.168.0.1:7447#so_sndbuf=65000;so_rcvbuf=65000 + listen: { + /// timeout waiting for all listen endpoints (0: no retry, -1: infinite timeout) + /// Accepts a single value (e.g. timeout_ms: 0) + /// or different values for router, peer and client (e.g. timeout_ms: { router: -1, peer: -1, client: 0 }). + timeout_ms: 0, + + /// The list of endpoints to listen on. + /// Accepts a single list (e.g. endpoints: ["tcp/[::]:7447", "udp/[::]:7447"]) + /// or different lists for router, peer and client (e.g. endpoints: { router: ["tcp/[::]:7447"], peer: ["tcp/[::]:0"] }). + /// + /// See https://docs.rs/zenoh/latest/zenoh/config/struct.EndPoint.html + /// + /// ROS setting: By default accept incoming connections only from localhost (i.e. from colocalized Nodes). + /// All communications with other hosts are routed by the Zenoh router. + endpoints: [ + "tcp/[::]:0" + ], + + /// Global listen configuration, + /// Accepts a single value or different values for router, peer and client. + /// The configuration can also be specified for the separate endpoint + /// it will override the global one + /// E.g. tcp/192.168.0.1:7447#exit_on_failure=false;retry_period_max_ms=1000" + + /// exit from application, if timeout exceed + exit_on_failure: true, + /// listen retry configuration + retry: { + /// initial wait timeout until next try + period_init_ms: 1000, + /// maximum wait timeout until next try + period_max_ms: 4000, + /// increase factor for the next timeout until next try + period_increase_factor: 2, + }, + }, + /// Configure the session open behavior. + open: { + /// Configure the conditions to be met before session open returns. + return_conditions: { + /// Session open waits to connect to scouted peers and routers before returning. + /// When set to false, first publications and queries after session open from peers may be lost. + connect_scouted: true, + /// Session open waits to receive initial declares from connected peers before returning. + /// Setting to false may cause extra traffic at startup from peers. + declares: true, + }, + }, + /// Configure the scouting mechanisms and their behaviours + scouting: { + /// In client mode, the period in milliseconds dedicated to scouting for a router before failing. + timeout: 3000, + /// In peer mode, the maximum period in milliseconds dedicated to scouting remote peers before attempting other operations. + delay: 500, + /// The multicast scouting configuration. + multicast: { + /// Whether multicast scouting is enabled or not + /// + /// ROS setting: disable multicast discovery by default + enabled: false, + /// The socket which should be used for multicast scouting + address: "224.0.0.252:7446", + /// The network interface which should be used for multicast scouting + interface: "auto", // If not set or set to "auto" the interface if picked automatically + /// The time-to-live on multicast scouting packets + ttl: 1, + /// Which type of Zenoh instances to automatically establish sessions with upon discovery on UDP multicast. + /// Accepts a single value (e.g. autoconnect: ["router", "peer"]) which applies whatever the configured "mode" is, + /// or different values for router, peer or client mode (e.g. autoconnect: { router: [], peer: ["router", "peer"] }). + /// Each value is a list of: "peer", "router" and/or "client". + autoconnect: { router: [], peer: ["router", "peer"], client: ["router"] }, + /// Strategy for autoconnection, mainly to avoid nodes connecting to each other redundantly. + /// Possible options are: + /// - "always": always attempt to autoconnect, may result in redundant connections. + /// - "greater-zid": attempt to connect to another node only if its own zid is greater than the other's. + /// If both nodes use this strategy, only one will attempt the connection. + /// This strategy may not be suited if one of the nodes is not reachable by the other one, for example + /// because of a private IP. + /// Accepts a single value (e.g. autoconnect: "always") which applies whatever node would be auto-connected to, + /// or different values for router and/or peer depending on the type of node detected + /// (e.g. autoconnect_strategy : { to_router: "always", to_peer: "greater-zid" }), + /// or different values for router or peer mode + /// (e.g. autoconnect_strategy : { peer: { to_router: "always", to_peer: "greater-zid" } }). + /// ROS setting: by default all peers rely on the router to discover each other. Thus configuring the peer to send gossip + /// messages only to the router is sufficient and avoids unecessary traffic between Nodes at launch time. + autoconnect_strategy: { peer: { to_router: "always", to_peer: "greater-zid" } }, + /// Whether or not to listen for scout messages on UDP multicast and reply to them. + listen: true, + }, + /// The gossip scouting configuration. Note that instances in "client" mode do not participate in gossip. + gossip: { + /// Whether gossip scouting is enabled or not + enabled: true, + /// When true, gossip scouting information are propagated multiple hops to all nodes in the local network. + /// When false, gossip scouting information are only propagated to the next hop. + /// Activating multihop gossip implies more scouting traffic and a lower scalability. + /// It mostly makes sense when using "linkstate" routing mode where all nodes in the subsystem don't have + /// direct connectivity with each other. + multihop: true, + /// Which type of Zenoh instances to send gossip messages to. + /// Accepts a single value (e.g. target: ["router", "peer"]) which applies whatever the configured "mode" is, + /// or different values for router or peer mode (e.g. target: { router: ["router", "peer"], peer: ["router"] }). + /// Each value is a list of "peer" and/or "router". + /// ROS setting: by default all peers rely on the router to discover each other. Thus configuring the peer to send gossip + /// messages only to the router is sufficient and avoids unecessary traffic between Nodes at launch time. + target: { router: ["router", "peer"], peer: ["router"]}, + /// Which type of Zenoh instances to automatically establish sessions with upon discovery on gossip. + /// Accepts a single value (e.g. autoconnect: ["router", "peer"]) which applies whatever the configured "mode" is, + /// or different values for router or peer mode (e.g. autoconnect: { router: [], peer: ["router", "peer"] }). + /// Each value is a list of: "peer" and/or "router". + autoconnect: { router: [], peer: ["router", "peer"] }, + /// Strategy for autoconnection, mainly to avoid nodes connecting to each other redundantly. + /// Possible options are: + /// - "always": always attempt to autoconnect, may result in redundant connection which will then be closed. + /// - "greater-zid": attempt to connect to another node only if its own zid is greater than the other's. + /// If both nodes use this strategy, only one will attempt the connection. + /// This strategy may not be suited if one of the nodes is not reachable by the other one, for example + /// because of a private IP. + /// Accepts a single value (e.g. autoconnect: "always") which applies whatever node would be auto-connected to, + /// or different values for router and/or peer depending on the type of node detected + /// (e.g. autoconnect_strategy : { to_router: "always", to_peer: "greater-zid" }), + /// or different values for router or peer mode + /// (e.g. autoconnect_strategy : { peer: { to_router: "always", to_peer: "greater-zid" } }). + /// ROS setting: as by default all peers will interconnect to each other over the loopback interface, + /// they are all reachable to each other. Hence using "greater-zid" for peers connecting to + /// other peers is sufficient and avoids unecessary double connections between peers at startup. + autoconnect_strategy: { peer: { to_router: "always", to_peer: "always" } }, + }, + }, + + /// Configuration of data messages timestamps management. + timestamping: { + /// Whether data messages should be timestamped if not already. + /// Accepts a single boolean value or different values for router, peer and client. + /// + /// ROS setting: PublicationCache which is required for transient_local durability + /// only works when time-stamping is enabled. + enabled: { router: true, peer: true, client: true }, + /// Whether data messages with timestamps in the future should be dropped or not. + /// If set to false (default), messages with timestamps in the future are retimestamped. + /// Timestamps are ignored if timestamping is disabled. + drop_future_timestamp: false, + }, + + /// The default timeout to apply to queries in milliseconds. + /// ROS setting: increase the value to avoid timeout at launch time with a large number of Nodes starting all together. + /// Note: the requests to services and actions are hard-coded with an infinite timeout. Hence, this setting + /// only applies to the queries made by the Advanced Subscriber for TRANSIENT_LOCAL implementation. + queries_default_timeout: 60000, + + /// The routing strategy to use and it's configuration. + routing: { + /// The routing strategy to use in routers and it's configuration. + router: { + /// When set to true a router will forward data between two peers + /// directly connected to it if it detects that those peers are not + /// connected to each other. + /// The failover brokering only works if gossip discovery is enabled + /// and peers are configured with gossip target "router". + peers_failover_brokering: true, + }, + /// The routing strategy to use in peers and it's configuration. + peer: { + /// The routing strategy to use in peers. ("peer_to_peer" or "linkstate"). + mode: "peer_to_peer", + }, + /// The interests-based routing configuration. + /// This configuration applies regardless of the mode (router, peer or client). + interests: { + /// The timeout to wait for incoming interests declarations in milliseconds. + /// The expiration of this timeout implies that the discovery protocol might be incomplete, + /// leading to potential loss of messages, queries or liveliness tokens. + timeout: 10000, + }, + }, + + // /// Overwrite QoS options for Zenoh messages by key expression (ignores Zenoh API QoS config for overwritten values) + // qos: { + // /// Overwrite QoS options for PUT and DELETE messages + // publication: [ + // { + // /// PUT and DELETE messages on key expressions that are included by these key expressions + // /// will have their QoS options overwritten by the given config. + // key_exprs: ["demo/**", "example/key"], + // /// Configurations that will be applied on the publisher. + // /// Options that are supplied here will overwrite the configuration given in Zenoh API + // config: { + // congestion_control: "block", + // priority: "data_high", + // express: true, + // reliability: "best_effort", + // allowed_destination: "remote", + // }, + // }, + // ], + // }, + + // /// The declarations aggregation strategy. + // aggregation: { + // /// A list of key-expressions for which all included subscribers will be aggregated into. + // subscribers: [ + // // key_expression + // ], + // /// A list of key-expressions for which all included publishers will be aggregated into. + // publishers: [ + // // key_expression + // ], + // }, + + // /// Namespace prefix. + // /// If specified, all outgoing key expressions will be automatically prefixed with specified string, + // /// and all incoming key expressions will be stripped of specified prefix. + // /// The namespace prefix should satisfy all key expression constraints + // /// and additionally it can not contain wild characters ('*'). + // /// Namespace is applied to the session. + // /// E. g. if session has a namespace of "1" then session.put("my/keyexpr", my_message), + // /// will put a message into 1/my/keyexpr. Same applies to all other operations within this session. + // namespace: "my/namespace", + + // /// The downsampling declaration. + // downsampling: [ + // { + // /// Optional Id, has to be unique + // "id": "wlan0egress", + // /// Optional list of network interfaces messages will be processed on, the rest will be passed as is. + // /// If absent, the rules will be applied to all interfaces, in case of an empty list it means that they will not be applied to any. + // interfaces: [ "wlan0" ], + // /// Optional list of data flows messages will be processed on ("egress" and/or "ingress"). + // /// If absent, the rules will be applied to both flows. + // flow: ["ingress", "egress"], + // /// List of message type on which downsampling will be applied. Must not be empty. + // messages: [ + // /// Publication (Put and Delete) + // "push", + // /// Get + // "query", + // /// Queryable Reply to a Query + // "reply" + // ], + // /// A list of downsampling rules: key_expression and the maximum frequency in Hertz + // rules: [ + // { key_expr: "demo/example/zenoh-rs-pub", freq: 0.1 }, + // ], + // }, + // ], + + // /// Configure access control (ACL) rules + // access_control: { + // /// [true/false] acl will be activated only if this is set to true + // "enabled": false, + // /// [deny/allow] default permission is deny (even if this is left empty or not specified) + // "default_permission": "deny", + // /// Rule set for permissions allowing or denying access to key-expressions + // "rules": + // [ + // { + // /// Id has to be unique within the rule set + // "id": "rule1", + // "messages": [ + // "put", "delete", "declare_subscriber", + // "query", "reply", "declare_queryable", + // "liveliness_token", "liveliness_query", "declare_liveliness_subscriber", + // ], + // "flows":["egress","ingress"], + // "permission": "allow", + // "key_exprs": [ + // "test/demo" + // ], + // }, + // { + // "id": "rule2", + // "messages": [ + // "put", "delete", "declare_subscriber", + // "query", "reply", "declare_queryable", + // ], + // "flows":["ingress"], + // "permission": "allow", + // "key_exprs": [ + // "**" + // ], + // }, + // ], + // /// List of combinations of subjects. + // /// + // /// If a subject property (i.e. username, certificate common name or interface) is empty + // /// it is interpreted as a wildcard. Moreover, a subject property cannot be an empty list. + // "subjects": + // [ + // { + // /// Id has to be unique within the subjects list + // "id": "subject1", + // /// Subjects can be interfaces + // "interfaces": [ + // "lo0", + // "en0", + // ], + // /// Subjects can be cert_common_names when using TLS or Quic + // "cert_common_names": [ + // "example.zenoh.io" + // ], + // /// Subjects can be usernames when using user/password authentication + // "usernames": [ + // "zenoh-example" + // ], + // /// This instance translates internally to this filter: + // /// (interface="lo0" && cert_common_name="example.zenoh.io" && username="zenoh-example") || + // /// (interface="en0" && cert_common_name="example.zenoh.io" && username="zenoh-example") + // }, + // { + // "id": "subject2", + // "interfaces": [ + // "lo0", + // "en0", + // ], + // "cert_common_names": [ + // "example2.zenoh.io" + // ], + // /// This instance translates internally to this filter: + // /// (interface="lo0" && cert_common_name="example2.zenoh.io") || + // /// (interface="en0" && cert_common_name="example2.zenoh.io") + // }, + // { + // "id": "subject3", + // /// An empty subject combination is a wildcard + // }, + // ], + // /// The policies list associates rules to subjects + // "policies": + // [ + // /// Each policy associates one or multiple rules to one or multiple subject combinations + // { + // /// Id is optional. If provided, it has to be unique within the policies list + // "id": "policy1", + // /// Rules and Subjects are identified with their unique IDs declared above + // "rules": ["rule1"], + // "subjects": ["subject1", "subject2"], + // }, + // { + // "rules": ["rule2"], + // "subjects": ["subject3"], + // }, + // ] + //}, + + /// Configure internal transport parameters + transport: { + unicast: { + /// Timeout in milliseconds when opening a link + /// ROS setting: increase the value to avoid timeout at launch time with a large number of Nodes starting all together + open_timeout: 60000, + /// Timeout in milliseconds when accepting a link + /// ROS setting: increase the value to avoid timeout at launch time with a large number of Nodes starting all together + accept_timeout: 60000, + /// Maximum number of links in pending state while performing the handshake for accepting it + /// ROS setting: increase the value to support a large number of Nodes starting all together + accept_pending: 10000, + /// Maximum number of transports that can be simultaneously alive for a single zenoh sessions + /// ROS setting: increase the value to support a large number of Nodes starting all together + max_sessions: 10000, + /// Maximum number of incoming links that are admitted per transport + max_links: 1, + /// Enables the LowLatency transport + /// This option does not make LowLatency transport mandatory, the actual implementation of transport + /// used will depend on Establish procedure and other party's settings + /// + /// NOTE: Currently, the LowLatency transport doesn't preserve QoS prioritization. + /// NOTE: Due to the note above, 'lowlatency' is incompatible with 'qos' option, so in order to + /// enable 'lowlatency' you need to explicitly disable 'qos'. + /// NOTE: LowLatency transport does not support the fragmentation, so the message size should be + /// smaller than the tx batch_size. + lowlatency: false, + /// Enables QoS on unicast communications. + qos: { + enabled: true, + }, + /// Enables compression on unicast communications. + /// Compression capabilities are negotiated during session establishment. + /// If both Zenoh nodes support compression, then compression is activated. + compression: { + enabled: false, + }, + }, + /// WARNING: multicast communication does not perform any negotiation upon group joining. + /// Because of that, it is important that all transport parameters are the same to make + /// sure all your nodes in the system can communicate. One common parameter to configure + /// is "transport/link/tx/batch_size" since its default value depends on the actual platform + /// when operating on multicast. + /// E.g., the batch size on Linux and Windows is 65535 bytes, on Mac OS X is 9216, and anything else is 8192. + multicast: { + /// JOIN message transmission interval in milliseconds. + join_interval: 2500, + /// Maximum number of multicast sessions. + max_sessions: 1000, + /// Enables QoS on multicast communication. + /// Default to false for Zenoh-to-Zenoh-Pico out-of-the-box compatibility. + qos: { + enabled: false, + }, + /// Enables compression on multicast communication. + /// Default to false for Zenoh-to-Zenoh-Pico out-of-the-box compatibility. + compression: { + enabled: false, + }, + }, + link: { + /// An optional whitelist of protocols to be used for accepting and opening sessions. If not + /// configured, all the supported protocols are automatically whitelisted. The supported + /// protocols are: ["tcp" , "udp", "tls", "quic", "ws", "unixsock-stream", "vsock"] For + /// example, to only enable "tls" and "quic": protocols: ["tls", "quic"], + /// + /// Configure the zenoh TX parameters of a link + tx: { + /// The resolution in bits to be used for the message sequence numbers. + /// When establishing a session with another Zenoh instance, the lowest value of the two instances will be used. + /// Accepted values: 8bit, 16bit, 32bit, 64bit. + sequence_number_resolution: "32bit", + /// Link lease duration in milliseconds to announce to other zenoh nodes + /// ROS setting: increase the value to avoid lease expiration at launch time with a large number of Nodes starting all together + lease: 60000, + /// Number of keep-alive messages in a link lease duration. If no data is sent, keep alive + /// messages will be sent at the configured time interval. + /// NOTE: In order to consider eventual packet loss and transmission latency and jitter, + /// set the actual keep_alive interval to one fourth of the lease time: i.e. send + /// 4 keep_alive messages in a lease period. Changing the lease time will have the + /// keep_alive messages sent more or less often. + /// This is in-line with the ITU-T G.8013/Y.1731 specification on continuous connectivity + /// check which considers a link as failed when no messages are received in 3.5 times the + /// target interval. + /// ROS setting: decrease the value since Nodes are communicating over the loopback + /// where keep-alive messages have less chances to be lost. + keep_alive: 2, + /// Batch size in bytes is expressed as a 16bit unsigned integer. + /// Therefore, the maximum batch size is 2^16-1 (i.e. 65535). + /// The default batch size value is the maximum batch size: 65535. + batch_size: 65535, + /// Each zenoh link has a transmission queue that can be configured + queue: { + /// The size of each priority queue indicates the number of batches a given queue can contain. + /// NOTE: the number of batches in each priority must be included between 1 and 16. Different values will result in an error. + /// The amount of memory being allocated for each queue is then SIZE_XXX * BATCH_SIZE. + /// In the case of the transport link MTU being smaller than the ZN_BATCH_SIZE, + /// then amount of memory being allocated for each queue is SIZE_XXX * LINK_MTU. + /// If qos is false, then only the DATA priority will be allocated. + size: { + control: 2, + real_time: 2, + interactive_high: 2, + interactive_low: 2, + data_high: 2, + data: 2, + data_low: 2, + background: 2, + }, + /// Congestion occurs when the queue is empty (no available batch). + congestion_control: { + /// Behavior pushing CongestionControl::Drop messages to the queue. + drop: { + /// The maximum time in microseconds to wait for an available batch before dropping a droppable message if still no batch is available. + wait_before_drop: 1000, + /// The maximum deadline limit for multi-fragment messages. + max_wait_before_drop_fragments: 50000, + }, + /// Behavior pushing CongestionControl::Block messages to the queue. + block: { + /// The maximum time in microseconds to wait for an available batch before closing the transport session when sending a blocking message + /// if still no batch is available. + /// ROS setting: increase the value to avoid unecessary link closure at launch time where congestion is likely + /// to occur even over the loopback since all the Nodes are starting at the same time. + wait_before_close: 60000000, + }, + }, + /// Perform batching of messages if they are smaller of the batch_size + batching: { + /// Perform adaptive batching of messages if they are smaller of the batch_size. + /// When the network is detected to not be fast enough to transmit every message individually, many small messages may be + /// batched together and sent all at once on the wire reducing the overall network overhead. This is typically of a high-throughput + /// scenario mainly composed of small messages. In other words, batching is activated by the network back-pressure. + enabled: true, + /// The maximum time limit (in ms) a message should be retained for batching when back-pressure happens. + time_limit: 1, + }, + allocation: { + /// Mode for memory allocation of batches in the priority queues. + /// - "init": batches are allocated at queue initialization time. + /// - "lazy": batches are allocated when needed up to the maximum number of batches configured in the size configuration parameter. + mode: "lazy", + }, + }, + }, + /// Configure the zenoh RX parameters of a link + rx: { + /// Receiving buffer size in bytes for each link + /// The default the rx_buffer_size value is the same as the default batch size: 65535. + /// For very high throughput scenarios, the rx_buffer_size can be increased to accommodate + /// more in-flight data. This is particularly relevant when dealing with large messages. + /// E.g. for 16MiB rx_buffer_size set the value to: 16777216. + buffer_size: 16777216, + /// Maximum size of the defragmentation buffer at receiver end. + /// Fragmented messages that are larger than the configured size will be dropped. + /// The default value is 1GiB. This would work in most scenarios. + /// NOTE: reduce the value if you are operating on a memory constrained device. + max_message_size: 1073741824, + }, + /// Configure TLS specific parameters + tls: { + /// Path to the certificate of the certificate authority used to validate either the server + /// or the client's keys and certificates, depending on the node's mode. If not specified + /// on router mode then the default WebPKI certificates are used instead. + root_ca_certificate: null, + /// Path to the TLS listening side private key + listen_private_key: null, + /// Path to the TLS listening side public certificate + listen_certificate: null, + /// Enables mTLS (mutual authentication), client authentication + enable_mtls: false, + /// Path to the TLS connecting side private key + connect_private_key: null, + /// Path to the TLS connecting side certificate + connect_certificate: null, + // Whether or not to verify the matching between hostname/dns and certificate when connecting, + // if set to false zenoh will disregard the common names of the certificates when verifying servers. + // This could be dangerous because your CA can have signed a server cert for foo.com, that's later being used to host a server at baz.com. If you wan't your + // ca to verify that the server at baz.com is actually baz.com, let this be true (default). + verify_name_on_connect: true, + // Whether or not to close links when remote certificates expires. + // If set to true, links that require certificates (tls/quic) will automatically disconnect when the time of expiration of the remote certificate chain is reached + // note that mTLS (client authentication) is required for a listener to disconnect a client on expiration + close_link_on_expiration: false, + /// Optional configuration for TCP system buffers sizes for TLS links + /// + /// Configure TCP read buffer size (bytes) + // so_rcvbuf: 123456, + /// Configure TCP write buffer size (bytes) + // so_sndbuf: 123456, + }, + // // Configure optional TCP link specific parameters + // tcp: { + // /// Optional configuration for TCP system buffers sizes for TCP links + // /// + // /// Configure TCP read buffer size (bytes) + // // so_rcvbuf: 123456, + // /// Configure TCP write buffer size (bytes) + // // so_sndbuf: 123456, + // } + }, + /// Shared memory configuration. + /// NOTE: shared memory can be used only if zenoh is compiled with "shared-memory" feature, otherwise + /// settings in this section have no effect. + shared_memory: { + /// Whether shared memory is enabled or not. + /// If set to `true`, the SHM buffer optimization support will be announced to other parties. (default `true`). + /// This option doesn't make SHM buffer optimization mandatory, the real support depends on other party setting. + /// A probing procedure for shared memory is performed upon session opening. To enable zenoh to operate + /// over shared memory (and to not fallback on network mode), shared memory needs to be enabled also on the + /// subscriber side. By doing so, the probing procedure will succeed and shared memory will operate as expected. + /// + /// ROS setting: disabled by default until fully tested + enabled: true, + /// SHM resources initialization mode (default "lazy"). + /// - "lazy": SHM subsystem internals will be initialized lazily upon the first SHM buffer + /// allocation or reception. This setting provides better startup time and optimizes resource usage, + /// but produces extra latency at the first SHM buffer interaction. + /// - "init": SHM subsystem internals will be initialized upon Session opening. This setting sacrifices + /// startup time, but guarantees no latency impact when first SHM buffer is processed. + mode: "lazy", + }, + auth: { + /// The configuration of authentication. + /// A password implies a username is required. + usrpwd: { + user: null, + password: null, + /// The path to a file containing the user password dictionary + dictionary_file: null, + }, + pubkey: { + public_key_pem: null, + private_key_pem: null, + public_key_file: null, + private_key_file: null, + key_size: null, + known_keys_file: null, + }, + }, + }, + + /// Configure the Admin Space + /// Unstable: this configuration part works as advertised, but may change in a future release + adminspace: { + /// Enables the admin space + enabled: true, + /// read and/or write permissions on the admin space + permissions: { + read: true, + write: false, + }, + }, + +} \ No newline at end of file diff --git a/root/cyclonedds.xml b/root/cyclonedds.xml new file mode 100644 index 0000000..f2f8471 --- /dev/null +++ b/root/cyclonedds.xml @@ -0,0 +1,13 @@ + + + + + + + + + true + error + + + \ No newline at end of file diff --git a/root/entrypoint.bash b/root/entrypoint.bash new file mode 100755 index 0000000..6fd84e2 --- /dev/null +++ b/root/entrypoint.bash @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +# setup ros2 environment +source /opt/ros/${ROS_DISTRO}/setup.bash + +if [ -f /workspace/install/setup.bash ]; then + source /workspace/install/setup.bash +fi +exec "$@" \ No newline at end of file diff --git a/src/example/.gitignore b/src/example/.gitignore new file mode 100644 index 0000000..6a4dae1 --- /dev/null +++ b/src/example/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +__pycache__ diff --git a/src/example/CONTRIBUTING.md b/src/example/CONTRIBUTING.md new file mode 100644 index 0000000..cfba094 --- /dev/null +++ b/src/example/CONTRIBUTING.md @@ -0,0 +1,18 @@ +Any contribution that you make to this repository will +be under the Apache 2 License, as dictated by that +[license](http://www.apache.org/licenses/LICENSE-2.0.html): + +~~~ +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. +~~~ + +Contributors must sign-off each commit by adding a `Signed-off-by: ...` +line to commit messages to certify that they have the right to submit +the code they are contributing to the project according to the +[Developer Certificate of Origin (DCO)](https://developercertificate.org/). diff --git a/src/example/LICENSE b/src/example/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/src/example/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/src/example/README.md b/src/example/README.md new file mode 100644 index 0000000..58ee050 --- /dev/null +++ b/src/example/README.md @@ -0,0 +1,4 @@ +ROS 2 examples +============== + +To see some of these examples in use, visit the [ROS 2 Tutorials page](https://docs.ros.org/en/rolling/Tutorials.html). diff --git a/src/example/launch_testing/launch_testing_examples/CHANGELOG.rst b/src/example/launch_testing/launch_testing_examples/CHANGELOG.rst new file mode 100644 index 0000000..fa0d39e --- /dev/null +++ b/src/example/launch_testing/launch_testing_examples/CHANGELOG.rst @@ -0,0 +1,142 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package launch_testing_examples +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- +* Cleanup the launch_testing_examples. (`#374 `_) +* Refactor WaitForNodes class. (`#373 `_) +* Contributors: Chris Lalancette + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- +* Enable document generation using rosdoc2 for ament_python pkgs (`#357 `_) +* Contributors: Yadu + +0.17.1 (2023-03-01) +------------------- +* increase the timeout for window platform to avoid flaky test (`#355 `_) +* Contributors: Chen Lihui + +0.17.0 (2023-02-14) +------------------- +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash + +0.16.2 (2022-11-02) +------------------- +* Increase the WaitForNode timeout. (`#350 `_) +* Contributors: Chris Lalancette + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Readded WaitForTopics utility (`#333 `_) +* Final batch of examples (`#327 `_) +* Update maintainers to Aditya Pande and Shane Loretz (`#332 `_) +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande, Audrow Nash + +0.13.0 (2021-10-18) +------------------- +* Reverted WaitForTopics utility usage (`#326 `_) +* Moved examples (`#324 `_) +* Contributors: Aditya Pande + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- + +0.10.0 (2020-09-21) +------------------- + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ + +0.9.0 (2020-04-30) +------------------ + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ + +0.8.0 (2019-09-26) +------------------ + +0.7.3 (2019-05-29) +------------------ + +0.7.2 (2019-05-20) +------------------ + +0.7.1 (2019-05-08) +------------------ + +0.7.0 (2019-04-14) +------------------ + +0.6.2 (2019-02-08) +------------------ + +0.6.1 (2018-12-07) +------------------ + +0.6.0 (2018-11-20) +------------------ + +0.5.1 (2018-06-27) +------------------ + +0.5.0 (2018-06-26) +------------------ + +0.4.0 (2017-12-08) +------------------ diff --git a/src/example/launch_testing/launch_testing_examples/README.md b/src/example/launch_testing/launch_testing_examples/README.md new file mode 100644 index 0000000..4c39619 --- /dev/null +++ b/src/example/launch_testing/launch_testing_examples/README.md @@ -0,0 +1,73 @@ +# Launch testing examples + +This package contains simple use cases for the ``launch`` and ``launch_testing`` packages. +These are designed to help beginners get started with these packages and help them understand the concepts. + +## Examples + +### `check_node_launch_test.py` + +Usage: + +```sh +launch_test launch_testing_examples/check_node_launch_test.py +``` + +There might be situations where nodes, once launched, take some time to actually start and we need to wait for the node to start to perform some action. +We can simulate this using ``launch.actions.TimerAction``. +This example shows one way to detect when a node has been launched. +We delay the launch by 5 seconds, and wait for the node to start with a timeout of 20 seconds. + +### `check_multiple_nodes_launch_test.py` + +```sh +launch_test test/examples/check_multiple_nodes_launch_test.py +``` + +This test launches multiple nodes, and checks if they were launched successfully using the `WaitForNodes` utility. + +### `record_rosbag_launch_test.py` + +```sh +launch_test test/examples/record_rosbag_launch_test.py +``` + +This test launches a `talker` node, records the topics to a `rosbag` and makes sure that the messages were recorded successfully, +then deletes the bag file. + +### `check_msgs_launch_test.py` + +Usage: + +```sh +launch_test launch_testing_examples/check_msgs_launch_test.py +``` + +Consider a problem statement where you need to launch a node and check if messages are published on a particular topic. +This example demonstrates how to do that, using a talker node. +It uses the ``Event`` object to end the test as soon as the first message is received on the chatter topic, with a timeout of 5 seconds. + +### `set_param_launch_test.py` + +Usage: + +```sh +launch_test launch_testing_examples/set_param_launch_test.py +``` + +This example demonstrates how to launch a node, set a parameter in it and check if that was successful. + +### `hello_world_launch_test.py` + +Usage: + +```sh +launch_test launch_testing_examples/hello_world_launch_test.py +``` + +This test is a simple example on how to use the ``launch_testing``. + +It launches a process and asserts that it prints "hello_world" to ``stdout`` using ``proc_output.assertWaitFor()``. +Finally, it checks if the process exits normally (zero exit code). + +The ``@launch_testing.markers.keep_alive`` decorator ensures that the launch process stays alive long enough for the tests to run. diff --git a/src/example/launch_testing/launch_testing_examples/launch_testing_examples/__init__.py b/src/example/launch_testing/launch_testing_examples/launch_testing_examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/example/launch_testing/launch_testing_examples/launch_testing_examples/check_msgs_launch_test.py b/src/example/launch_testing/launch_testing_examples/launch_testing_examples/check_msgs_launch_test.py new file mode 100644 index 0000000..03399c5 --- /dev/null +++ b/src/example/launch_testing/launch_testing_examples/launch_testing_examples/check_msgs_launch_test.py @@ -0,0 +1,44 @@ +# Copyright 2021 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. + +import unittest + +import launch +import launch.actions +import launch_ros.actions +import launch_testing.actions +import launch_testing.markers +from launch_testing_ros import WaitForTopics +import pytest +from std_msgs.msg import String + + +@pytest.mark.launch_test +@launch_testing.markers.keep_alive +def generate_test_description(): + return launch.LaunchDescription([ + launch_ros.actions.Node( + executable='talker', + package='demo_nodes_cpp', + name='demo_node_1' + ), + launch_testing.actions.ReadyToTest() + ]) + + +class TestFixture(unittest.TestCase): + + def test_check_if_msgs_published(self): + with WaitForTopics([('chatter', String)], timeout=15.0): + print('Topic received messages !') diff --git a/src/example/launch_testing/launch_testing_examples/launch_testing_examples/check_multiple_nodes_launch_test.py b/src/example/launch_testing/launch_testing_examples/launch_testing_examples/check_multiple_nodes_launch_test.py new file mode 100644 index 0000000..b78b0c0 --- /dev/null +++ b/src/example/launch_testing/launch_testing_examples/launch_testing_examples/check_multiple_nodes_launch_test.py @@ -0,0 +1,149 @@ +# Copyright 2021 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. + + +import random +import string +import time +import unittest + +import launch +import launch.actions +import launch_ros.actions +import launch_testing.actions +import launch_testing.markers +import pytest +import rclpy +from rclpy.node import Node + + +# TODO (adityapande-1995): Move WaitForNodes implementation to launch_testing_ros +# after https://github.com/ros2/rclpy/issues/831 is resolved +class WaitForNodes: + """ + Wait to discover supplied nodes. + + Example usage: + -------------- + # Method 1, calling wait() and shutdown() manually + def method_1(): + node_list = ['foo', 'bar'] + wait_for_nodes = WaitForNodes(node_list, timeout=15.0) + assert wait_for_nodes.wait() + print('Nodes found!') + assert wait_for_nodes.get_nodes_not_found() == set() + wait_for_nodes.shutdown() + + # Method 2, using the 'with' keyword + def method_2(): + with WaitForNodes(['foo', 'bar'], timeout=15.0) as wait_for_nodes: + assert wait_for_nodes.get_nodes_not_found() == set() + print('Nodes found!') + """ + + def __init__(self, node_names, timeout=15.0): + self.node_names = node_names + self.timeout = timeout + self.__ros_context = rclpy.Context() + rclpy.init(context=self.__ros_context) + self.__node_name = '_test_node_' +\ + ''.join(random.choices(string.ascii_uppercase + string.digits, k=10)) + self.__ros_node = Node(node_name=self.__node_name, context=self.__ros_context) + + self.__expected_nodes_set = set(node_names) + self.__nodes_found = set() + + def wait(self): + start = time.time() + finished = False + while time.time() - start < self.timeout and not finished: + finished = all(name in self.__ros_node.get_node_names() for name in self.node_names) + time.sleep(0.1) + + self.__nodes_found = set(self.__ros_node.get_node_names()) + self.__nodes_found.remove(self.__node_name) + return finished + + def shutdown(self): + self.__ros_node.destroy_node() + rclpy.shutdown(context=self.__ros_context) + + def __enter__(self): + if not self.wait(): + raise RuntimeError('Did not find all nodes !') + + return self + + def __exit__(self, exep_type, exep_value, trace): + if exep_type is not None: + raise Exception('Exception occured, value: ', exep_value) + self.shutdown() + + def get_nodes_found(self): + return self.__nodes_found + + def get_nodes_not_found(self): + return self.__expected_nodes_set - self.__nodes_found + + +@pytest.mark.launch_test +@launch_testing.markers.keep_alive +def generate_test_description(): + launch_actions = [] + node_names = [] + + for i in range(3): + node_name = 'demo_node_' + str(i) + launch_actions.append( + launch_ros.actions.Node( + executable='talker', + package='demo_nodes_cpp', + name=node_name + ) + ) + node_names.append(node_name) + + launch_actions.append(launch_testing.actions.ReadyToTest()) + return launch.LaunchDescription(launch_actions), {'node_list': node_names} + + +class CheckMultipleNodesLaunched(unittest.TestCase): + + def test_nodes_successful(self, node_list): + """Check if all the nodes were launched correctly.""" + # Method 1 + wait_for_nodes_1 = WaitForNodes(node_list) + assert wait_for_nodes_1.wait() + assert wait_for_nodes_1.get_nodes_not_found() == set() + wait_for_nodes_1.shutdown() + + # Method 2 + with WaitForNodes(node_list) as wait_for_nodes_2: + print('All nodes were found !') + assert wait_for_nodes_2.get_nodes_not_found() == set() + + def test_node_does_not_exist(self, node_list): + """Insert a invalid node name that should not exist.""" + invalid_node_list = node_list + ['invalid_node'] + + # Method 1 + wait_for_nodes_1 = WaitForNodes(invalid_node_list) + assert not wait_for_nodes_1.wait() + assert wait_for_nodes_1.get_nodes_not_found() == {'invalid_node'} + wait_for_nodes_1.shutdown() + + # Method 2 + with pytest.raises(RuntimeError): + with WaitForNodes(invalid_node_list): + pass diff --git a/src/example/launch_testing/launch_testing_examples/launch_testing_examples/check_node_launch_test.py b/src/example/launch_testing/launch_testing_examples/launch_testing_examples/check_node_launch_test.py new file mode 100644 index 0000000..5ab4ae7 --- /dev/null +++ b/src/example/launch_testing/launch_testing_examples/launch_testing_examples/check_node_launch_test.py @@ -0,0 +1,61 @@ +# Copyright 2021 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. + +import time +import unittest + +import launch +import launch.actions +import launch_ros.actions +import launch_testing.actions +import launch_testing.markers +import pytest +import rclpy +from rclpy.node import Node + + +@pytest.mark.launch_test +@launch_testing.markers.keep_alive +def generate_test_description(): + return launch.LaunchDescription([ + launch.actions.TimerAction( + period=5.0, + actions=[ + launch_ros.actions.Node( + executable='talker', + package='demo_nodes_cpp', + name='demo_node_1' + ), + ]), + launch_testing.actions.ReadyToTest() + ]) + + +class TestFixture(unittest.TestCase): + + def setUp(self): + rclpy.init() + self.node = Node('test_node') + + def tearDown(self): + self.node.destroy_node() + rclpy.shutdown() + + def test_node_start(self, proc_output): + start = time.time() + found = False + while time.time() - start < 20.0 and not found: + found = 'demo_node_1' in self.node.get_node_names() + time.sleep(0.1) + assert found, 'Node not found !' diff --git a/src/example/launch_testing/launch_testing_examples/launch_testing_examples/hello_world_launch_test.py b/src/example/launch_testing/launch_testing_examples/launch_testing_examples/hello_world_launch_test.py new file mode 100644 index 0000000..9ee1795 --- /dev/null +++ b/src/example/launch_testing/launch_testing_examples/launch_testing_examples/hello_world_launch_test.py @@ -0,0 +1,58 @@ +# Copyright 2021 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. + +import unittest + +import launch +import launch.actions +import launch_testing.actions +import launch_testing.markers +import pytest + + +# This function specifies the processes to be run for our test +@pytest.mark.launch_test +@launch_testing.markers.keep_alive +def generate_test_description(): + """Launch a simple process to print 'hello_world'.""" + return launch.LaunchDescription([ + # Launch a process to test + launch.actions.ExecuteProcess( + cmd=['echo', 'hello_world'], + shell=True + ), + # Tell launch to start the test + launch_testing.actions.ReadyToTest() + ]) + + +# This is our test fixture. Each method is a test case. +# These run alongside the processes specified in generate_test_description() +class TestHelloWorldProcess(unittest.TestCase): + + def test_read_stdout(self, proc_output): + """Check if 'hello_world' was found in the stdout.""" + # 'proc_output' is an object added automatically by the launch_testing framework. + # It captures the outputs of the processes launched in generate_test_description() + # Refer to the documentation for further details. + proc_output.assertWaitFor('hello_world', timeout=10, stream='stdout') + + +# These tests are run after the processes in generate_test_description() have shutdown. +@launch_testing.post_shutdown_test() +class TestHelloWorldShutdown(unittest.TestCase): + + def test_exit_codes(self, proc_info): + """Check if the processes exited normally.""" + launch_testing.asserts.assertExitCodes(proc_info) diff --git a/src/example/launch_testing/launch_testing_examples/launch_testing_examples/record_rosbag_launch_test.py b/src/example/launch_testing/launch_testing_examples/launch_testing_examples/record_rosbag_launch_test.py new file mode 100644 index 0000000..fc1bdf8 --- /dev/null +++ b/src/example/launch_testing/launch_testing_examples/launch_testing_examples/record_rosbag_launch_test.py @@ -0,0 +1,84 @@ +# Copyright 2021 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. + + +import os +import shutil +import tempfile +import time +import unittest + +import launch +import launch.actions +import launch_ros.actions +import launch_testing.actions +import launch_testing.markers +import pytest + +import yaml + + +@pytest.mark.launch_test +@launch_testing.markers.keep_alive +def generate_test_description(): + rosbag_dir = os.path.join(tempfile.mkdtemp(), 'test_bag') + + node_list = [ + launch_ros.actions.Node( + executable='talker', + package='demo_nodes_cpp', + name='demo_node' + ), + launch.actions.ExecuteProcess( + cmd=['ros2', 'bag', 'record', '-a', '-o', rosbag_dir], + output='screen' + ), + launch_testing.actions.ReadyToTest() + ] + + return launch.LaunchDescription(node_list), {'rosbag_dir': rosbag_dir} + + +class DelayShutdown(unittest.TestCase): + + def test_delay(self): + """Delay the shutdown of processes so that rosbag can record some messages.""" + time.sleep(3) + + +# TODO : Test fails on windows, to be fixed +# https://github.com/ros2/rosbag2/issues/926 +if os.name != 'nt': + @launch_testing.post_shutdown_test() + class TestFixtureAfterShutdown(unittest.TestCase): + + rosbag_dir = None + + def test_rosbag_record(self, rosbag_dir): + """Check if the rosbag2 recording was successful.""" + with open(os.path.join(rosbag_dir, 'metadata.yaml'), 'r') as file: + metadata = yaml.safe_load(file) + assert metadata['rosbag2_bagfile_information']['message_count'] > 0 + print('The following topics received messages:') + for item in metadata['rosbag2_bagfile_information']['topics_with_message_count']: + print(item['topic_metadata']['name'], 'recieved ', item['message_count'], + ' messages') + + TestFixtureAfterShutdown.rosbag_dir = rosbag_dir + + @classmethod + def tearDownClass(cls): + """Delete the rosbag directory.""" + print('Deleting ', cls.rosbag_dir) + shutil.rmtree(cls.rosbag_dir.replace('test_bag', '')) diff --git a/src/example/launch_testing/launch_testing_examples/launch_testing_examples/set_param_launch_test.py b/src/example/launch_testing/launch_testing_examples/launch_testing_examples/set_param_launch_test.py new file mode 100644 index 0000000..e240842 --- /dev/null +++ b/src/example/launch_testing/launch_testing_examples/launch_testing_examples/set_param_launch_test.py @@ -0,0 +1,68 @@ +# Copyright 2021 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. + +import os +import unittest + +import launch +import launch.actions +import launch_ros.actions +import launch_testing.actions +import launch_testing.markers +import pytest +from rcl_interfaces.srv import SetParameters +import rclpy +from rclpy.node import Node + + +@pytest.mark.launch_test +@launch_testing.markers.keep_alive +def generate_test_description(): + return launch.LaunchDescription([ + launch_ros.actions.Node( + executable='parameter_blackboard', + package='demo_nodes_cpp', + name='demo_node_1' + ), + launch_testing.actions.ReadyToTest() + ]) + + +# TODO: Fix windows failures for this test +if os.name != 'nt': + class TestFixture(unittest.TestCase): + + def setUp(self): + rclpy.init() + self.node = Node('test_node') + + def tearDown(self): + self.node.destroy_node() + rclpy.shutdown() + + def test_set_parameter(self, proc_output): + parameters = [rclpy.Parameter('demo_parameter_1', value=True).to_parameter_msg()] + + client = self.node.create_client(SetParameters, 'demo_node_1/set_parameters') + ready = client.wait_for_service(timeout_sec=15.0) + if not ready: + raise RuntimeError('Wait for service timed out') + + request = SetParameters.Request() + request.parameters = parameters + future = client.call_async(request) + rclpy.spin_until_future_complete(self.node, future, timeout_sec=15.0) + + response = future.result() + assert response.results[0].successful, 'Could not set parameter!' diff --git a/src/example/launch_testing/launch_testing_examples/package.xml b/src/example/launch_testing/launch_testing_examples/package.xml new file mode 100644 index 0000000..754af56 --- /dev/null +++ b/src/example/launch_testing/launch_testing_examples/package.xml @@ -0,0 +1,34 @@ + + + + launch_testing_examples + 0.19.6 + Examples of simple launch tests + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Aditya Pande + Shane Loretz + + demo_nodes_cpp + launch + launch_ros + launch_testing + launch_testing_ros + python3-pytest + rclpy + rcl_interfaces + ros2bag + std_msgs + + ament_copyright + ament_flake8 + ament_pep257 + + + ament_python + + diff --git a/src/example/launch_testing/launch_testing_examples/resource/launch_testing_examples b/src/example/launch_testing/launch_testing_examples/resource/launch_testing_examples new file mode 100644 index 0000000..e69de29 diff --git a/src/example/launch_testing/launch_testing_examples/setup.cfg b/src/example/launch_testing/launch_testing_examples/setup.cfg new file mode 100644 index 0000000..f820dc9 --- /dev/null +++ b/src/example/launch_testing/launch_testing_examples/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/launch_testing_examples +[install] +install_scripts=$base/lib/launch_testing_examples diff --git a/src/example/launch_testing/launch_testing_examples/setup.py b/src/example/launch_testing/launch_testing_examples/setup.py new file mode 100644 index 0000000..f8de58c --- /dev/null +++ b/src/example/launch_testing/launch_testing_examples/setup.py @@ -0,0 +1,25 @@ +from setuptools import setup + +package_name = 'launch_testing_examples' + +setup( + name=package_name, + version='0.19.6', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='Aditya Pande, Alejandro Hernandez Cordero', + maintainer_email='aditya.pande@openrobotics.org, alejandro@openrobotics.org', + description='Examples of simple launch tests', + license='Apache License 2.0', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + ], + }, +) diff --git a/src/example/launch_testing/launch_testing_examples/test/test_copyright.py b/src/example/launch_testing/launch_testing_examples/test/test_copyright.py new file mode 100644 index 0000000..cc8ff03 --- /dev/null +++ b/src/example/launch_testing/launch_testing_examples/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/example/launch_testing/launch_testing_examples/test/test_flake8.py b/src/example/launch_testing/launch_testing_examples/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/src/example/launch_testing/launch_testing_examples/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 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. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/example/launch_testing/launch_testing_examples/test/test_pep257.py b/src/example/launch_testing/launch_testing_examples/test/test_pep257.py new file mode 100644 index 0000000..b234a38 --- /dev/null +++ b/src/example/launch_testing/launch_testing_examples/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/example/rclcpp/README.md b/src/example/rclcpp/README.md new file mode 100644 index 0000000..35b81da --- /dev/null +++ b/src/example/rclcpp/README.md @@ -0,0 +1,4 @@ +# rclcpp examples + +This directory contains many examples of how to do common tasks with rclcpp. +The intent is that this material will be easy to copy-and-paste into your own projects. diff --git a/src/example/rclcpp/actions/minimal_action_client/CHANGELOG.rst b/src/example/rclcpp/actions/minimal_action_client/CHANGELOG.rst new file mode 100644 index 0000000..60fd5a6 --- /dev/null +++ b/src/example/rclcpp/actions/minimal_action_client/CHANGELOG.rst @@ -0,0 +1,202 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclcpp_minimal_action_client +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* Update the examples to C++17. (`#353 `_) +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash, Chris Lalancette + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- +* Update goal response callback signature (`#291 `_) +* Make sure to include what you use in all examples. (`#284 `_) +* Added common linters (`#265 `_) +* Contributors: Alejandro Hernández Cordero, Chris Lalancette, Jacob Perron + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ +* Fixed action_client sequence type (`#268 `_) +* Contributors: Alejandro Hernández Cordero + +0.9.0 (2020-04-30) +------------------ +* avoid new deprecations (`#267 `_) +* Restructure rclcpp folders (`#264 `_) +* Contributors: Marya Belanger, William Woodall + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ + +0.7.3 (2019-05-29) +------------------ +* Use action client get result method (`#245 `_) + Since a result callback is not provided when sending the goal, the goal handle is not "result aware" + and calling the action client method will make it so. + The behaviour was changed in https://github.com/ros2/rclcpp/pull/701. +* Contributors: Jacob Perron + +0.7.2 (2019-05-20) +------------------ + +0.7.1 (2019-05-08) +------------------ +* Avoid deprecated API's by providing history settings (`#240 `_) +* Add rclcpp action examples using member functions +* Use options struct when action client sends a goal +* Contributors: Jacob Perron, William Woodall + +0.7.0 (2019-04-14) +------------------ +* Updated to use separated action types. (`#227 `_) +* Contributors: Dirk Thomas + +0.6.2 (2019-02-08) +------------------ + +0.6.1 (2018-12-07) +------------------ +* Rclcpp action examples (`#220 `_) + * Add minimal_action_server package + Contains a non-composable implementation with global variables. + * Add minimal_action_client package + Contains a non-composable implementation. + * Add action client example with feedback + * async python action client example + * goal -> future + * fibb -> fib" + * Syncronous action client example + * No break statement + * Update client examples to use separate rcl_action package + * Add ClientGoalHandle to action client examplesj + * Add action client with cancel example + * python non-composable server example + * [wip] Update action server cpp example + * remove unnecessary event + * create_action_server -> ActionServer + * missing paren + * Add example of multiple goals in parallel + * No need for lock + * Reentrant callback group for execute + * create_action_client -> ActionClient + * Fix copyright date + * ) + * -) + * Refactor action server cpp example + * Fix action server cpp example + Seed the fibonacci sequence and remove const. + * Fix action server cpp example + Forgot to increment in Fibonacci sequence loop. + * Syntax fixes + * node -> self + * handle cb returns accept or reject + * Update action client cpp example + Return goal handle (containing future) when sending a goal. + * Preempt goals + * whitespace removal + * execute returns result + * Add missing resources + * Syntax error + * Add rcl_action dependency + * Update maintainer + * Use goal message getter and alias ResultResponse type + * Make minimal_action_server work with rclcpp_action + * Client and server communicate + * handle_execute -> handle_accepted + * Check if goal was rejected by server + * Update example to check result + * action client cancel example C++ works + * misc changes to compile + * misc client api changes + * Remove python examples + * Wait for action server +* Contributors: Shane Loretz + +0.6.0 (2018-11-20) +------------------ + +0.5.1 (2018-06-27) +------------------ + +0.5.0 (2018-06-26) +------------------ + +0.4.0 (2017-12-08) +------------------ diff --git a/src/example/rclcpp/actions/minimal_action_client/CMakeLists.txt b/src/example/rclcpp/actions/minimal_action_client/CMakeLists.txt new file mode 100644 index 0000000..e74cbb6 --- /dev/null +++ b/src/example/rclcpp/actions/minimal_action_client/CMakeLists.txt @@ -0,0 +1,55 @@ +cmake_minimum_required(VERSION 3.5) +project(examples_rclcpp_minimal_action_client) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) +find_package(example_interfaces REQUIRED) +find_package(rclcpp REQUIRED) +find_package(rclcpp_action REQUIRED) + +add_executable(action_client_member_functions member_functions.cpp) +ament_target_dependencies(action_client_member_functions + "rclcpp" + "rclcpp_action" + "example_interfaces") + +add_executable(action_client_not_composable not_composable.cpp) +ament_target_dependencies(action_client_not_composable + "rclcpp" + "rclcpp_action" + "example_interfaces") + +add_executable(action_client_not_composable_with_cancel not_composable_with_cancel.cpp) +ament_target_dependencies(action_client_not_composable_with_cancel + "rclcpp" + "rclcpp_action" + "example_interfaces") + +add_executable(action_client_not_composable_with_feedback not_composable_with_feedback.cpp) +ament_target_dependencies(action_client_not_composable_with_feedback + "rclcpp" + "rclcpp_action" + "example_interfaces") + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +install(TARGETS + action_client_member_functions + action_client_not_composable + action_client_not_composable_with_cancel + action_client_not_composable_with_feedback + DESTINATION lib/${PROJECT_NAME}) + +ament_package() diff --git a/src/example/rclcpp/actions/minimal_action_client/README.md b/src/example/rclcpp/actions/minimal_action_client/README.md new file mode 100644 index 0000000..45d2d8f --- /dev/null +++ b/src/example/rclcpp/actions/minimal_action_client/README.md @@ -0,0 +1,3 @@ +# Minimal action client cookbook recipes + +This package contains a few examples that show how to create action clients. diff --git a/src/example/rclcpp/actions/minimal_action_client/member_functions.cpp b/src/example/rclcpp/actions/minimal_action_client/member_functions.cpp new file mode 100644 index 0000000..f5c827e --- /dev/null +++ b/src/example/rclcpp/actions/minimal_action_client/member_functions.cpp @@ -0,0 +1,145 @@ +// Copyright 2019 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. + +#include +#include +#include +#include +#include +#include + +#include "example_interfaces/action/fibonacci.hpp" +#include "rclcpp/rclcpp.hpp" +// TODO(jacobperron): Remove this once it is included as part of 'rclcpp.hpp' +#include "rclcpp_action/rclcpp_action.hpp" + +class MinimalActionClient : public rclcpp::Node +{ +public: + using Fibonacci = example_interfaces::action::Fibonacci; + using GoalHandleFibonacci = rclcpp_action::ClientGoalHandle; + + explicit MinimalActionClient(const rclcpp::NodeOptions & node_options = rclcpp::NodeOptions()) + : Node("minimal_action_client", node_options), goal_done_(false) + { + this->client_ptr_ = rclcpp_action::create_client( + this->get_node_base_interface(), + this->get_node_graph_interface(), + this->get_node_logging_interface(), + this->get_node_waitables_interface(), + "fibonacci"); + + this->timer_ = this->create_wall_timer( + std::chrono::milliseconds(500), + std::bind(&MinimalActionClient::send_goal, this)); + } + + bool is_goal_done() const + { + return this->goal_done_; + } + + void send_goal() + { + using namespace std::placeholders; + + this->timer_->cancel(); + + this->goal_done_ = false; + + if (!this->client_ptr_) { + RCLCPP_ERROR(this->get_logger(), "Action client not initialized"); + } + + if (!this->client_ptr_->wait_for_action_server(std::chrono::seconds(10))) { + RCLCPP_ERROR(this->get_logger(), "Action server not available after waiting"); + this->goal_done_ = true; + return; + } + + auto goal_msg = Fibonacci::Goal(); + goal_msg.order = 10; + + RCLCPP_INFO(this->get_logger(), "Sending goal"); + + auto send_goal_options = rclcpp_action::Client::SendGoalOptions(); + send_goal_options.goal_response_callback = + std::bind(&MinimalActionClient::goal_response_callback, this, _1); + send_goal_options.feedback_callback = + std::bind(&MinimalActionClient::feedback_callback, this, _1, _2); + send_goal_options.result_callback = + std::bind(&MinimalActionClient::result_callback, this, _1); + auto goal_handle_future = this->client_ptr_->async_send_goal(goal_msg, send_goal_options); + } + +private: + rclcpp_action::Client::SharedPtr client_ptr_; + rclcpp::TimerBase::SharedPtr timer_; + bool goal_done_; + + void goal_response_callback(GoalHandleFibonacci::SharedPtr goal_handle) + { + if (!goal_handle) { + RCLCPP_ERROR(this->get_logger(), "Goal was rejected by server"); + } else { + RCLCPP_INFO(this->get_logger(), "Goal accepted by server, waiting for result"); + } + } + + void feedback_callback( + GoalHandleFibonacci::SharedPtr, + const std::shared_ptr feedback) + { + RCLCPP_INFO( + this->get_logger(), + "Next number in sequence received: %" PRId32, + feedback->sequence.back()); + } + + void result_callback(const GoalHandleFibonacci::WrappedResult & result) + { + this->goal_done_ = true; + switch (result.code) { + case rclcpp_action::ResultCode::SUCCEEDED: + break; + case rclcpp_action::ResultCode::ABORTED: + RCLCPP_ERROR(this->get_logger(), "Goal was aborted"); + return; + case rclcpp_action::ResultCode::CANCELED: + RCLCPP_ERROR(this->get_logger(), "Goal was canceled"); + return; + default: + RCLCPP_ERROR(this->get_logger(), "Unknown result code"); + return; + } + + RCLCPP_INFO(this->get_logger(), "Result received"); + for (auto number : result.result->sequence) { + RCLCPP_INFO(this->get_logger(), "%" PRId32, number); + } + } +}; // class MinimalActionClient + +int main(int argc, char ** argv) +{ + rclcpp::init(argc, argv); + auto action_client = std::make_shared(); + + while (!action_client->is_goal_done()) { + rclcpp::spin_some(action_client); + } + + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/actions/minimal_action_client/not_composable.cpp b/src/example/rclcpp/actions/minimal_action_client/not_composable.cpp new file mode 100644 index 0000000..406a7d9 --- /dev/null +++ b/src/example/rclcpp/actions/minimal_action_client/not_composable.cpp @@ -0,0 +1,91 @@ +// 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. + +#include +#include + +#include "example_interfaces/action/fibonacci.hpp" +#include "rclcpp/rclcpp.hpp" +// TODO(jacobperron): Remove this once it is included as part of 'rclcpp.hpp' +#include "rclcpp_action/rclcpp_action.hpp" + +using Fibonacci = example_interfaces::action::Fibonacci; + + +int main(int argc, char ** argv) +{ + rclcpp::init(argc, argv); + auto node = rclcpp::Node::make_shared("minimal_action_client"); + auto action_client = rclcpp_action::create_client(node, "fibonacci"); + + if (!action_client->wait_for_action_server(std::chrono::seconds(20))) { + RCLCPP_ERROR(node->get_logger(), "Action server not available after waiting"); + return 1; + } + + // Populate a goal + auto goal_msg = Fibonacci::Goal(); + goal_msg.order = 10; + + RCLCPP_INFO(node->get_logger(), "Sending goal"); + // Ask server to achieve some goal and wait until it's accepted + auto goal_handle_future = action_client->async_send_goal(goal_msg); + if (rclcpp::spin_until_future_complete(node, goal_handle_future) != + rclcpp::FutureReturnCode::SUCCESS) + { + RCLCPP_ERROR(node->get_logger(), "send goal call failed :("); + return 1; + } + + rclcpp_action::ClientGoalHandle::SharedPtr goal_handle = goal_handle_future.get(); + if (!goal_handle) { + RCLCPP_ERROR(node->get_logger(), "Goal was rejected by server"); + return 1; + } + + // Wait for the server to be done with the goal + auto result_future = action_client->async_get_result(goal_handle); + + RCLCPP_INFO(node->get_logger(), "Waiting for result"); + if (rclcpp::spin_until_future_complete(node, result_future) != + rclcpp::FutureReturnCode::SUCCESS) + { + RCLCPP_ERROR(node->get_logger(), "get result call failed :("); + return 1; + } + + rclcpp_action::ClientGoalHandle::WrappedResult wrapped_result = result_future.get(); + + switch (wrapped_result.code) { + case rclcpp_action::ResultCode::SUCCEEDED: + break; + case rclcpp_action::ResultCode::ABORTED: + RCLCPP_ERROR(node->get_logger(), "Goal was aborted"); + return 1; + case rclcpp_action::ResultCode::CANCELED: + RCLCPP_ERROR(node->get_logger(), "Goal was canceled"); + return 1; + default: + RCLCPP_ERROR(node->get_logger(), "Unknown result code"); + return 1; + } + + RCLCPP_INFO(node->get_logger(), "result received"); + for (auto number : wrapped_result.result->sequence) { + RCLCPP_INFO(node->get_logger(), "%" PRId32, number); + } + + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/actions/minimal_action_client/not_composable_with_cancel.cpp b/src/example/rclcpp/actions/minimal_action_client/not_composable_with_cancel.cpp new file mode 100644 index 0000000..3b17907 --- /dev/null +++ b/src/example/rclcpp/actions/minimal_action_client/not_composable_with_cancel.cpp @@ -0,0 +1,107 @@ +// 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. + +#include + +#include "example_interfaces/action/fibonacci.hpp" +#include "rclcpp/rclcpp.hpp" +// TODO(jacobperron): Remove this once it is included as part of 'rclcpp.hpp' +#include "rclcpp_action/rclcpp_action.hpp" + +using Fibonacci = example_interfaces::action::Fibonacci; + + +int main(int argc, char ** argv) +{ + rclcpp::init(argc, argv); + auto node = rclcpp::Node::make_shared("minimal_action_client"); + auto action_client = rclcpp_action::create_client(node, "fibonacci"); + + if (!action_client->wait_for_action_server(std::chrono::seconds(20))) { + RCLCPP_ERROR(node->get_logger(), "Action server not available after waiting"); + return 1; + } + + // Populate a goal + auto goal_msg = Fibonacci::Goal(); + goal_msg.order = 10; + + RCLCPP_INFO(node->get_logger(), "Sending goal"); + // Send goal and wait for result (registering feedback callback is optional) + auto goal_handle_future = action_client->async_send_goal(goal_msg); + if (rclcpp::spin_until_future_complete(node, goal_handle_future) != + rclcpp::FutureReturnCode::SUCCESS) + { + RCLCPP_ERROR(node->get_logger(), "send goal call failed :("); + return 1; + } + + rclcpp_action::ClientGoalHandle::SharedPtr goal_handle = goal_handle_future.get(); + if (!goal_handle) { + RCLCPP_ERROR(node->get_logger(), "Goal was rejected by server"); + return 1; + } + + // Wait for the server to be done with the goal + auto result_future = action_client->async_get_result(goal_handle); + + auto wait_result = rclcpp::spin_until_future_complete( + node, + result_future, + std::chrono::seconds(3)); + + if (rclcpp::FutureReturnCode::TIMEOUT == wait_result) { + RCLCPP_INFO(node->get_logger(), "canceling goal"); + // Cancel the goal since it is taking too long + auto cancel_result_future = action_client->async_cancel_goal(goal_handle); + if (rclcpp::spin_until_future_complete(node, cancel_result_future) != + rclcpp::FutureReturnCode::SUCCESS) + { + RCLCPP_ERROR(node->get_logger(), "failed to cancel goal"); + rclcpp::shutdown(); + return 1; + } + RCLCPP_INFO(node->get_logger(), "goal is being canceled"); + } else if (rclcpp::FutureReturnCode::SUCCESS != wait_result) { + RCLCPP_ERROR(node->get_logger(), "failed to get result"); + rclcpp::shutdown(); + return 1; + } + + RCLCPP_INFO(node->get_logger(), "Waiting for result"); + if (rclcpp::spin_until_future_complete(node, result_future) != + rclcpp::FutureReturnCode::SUCCESS) + { + RCLCPP_ERROR(node->get_logger(), "get result call failed :("); + return 1; + } + + rclcpp_action::ClientGoalHandle::WrappedResult wrapped_result = result_future.get(); + switch (wrapped_result.code) { + case rclcpp_action::ResultCode::SUCCEEDED: + break; + case rclcpp_action::ResultCode::ABORTED: + RCLCPP_ERROR(node->get_logger(), "Goal was aborted"); + return 1; + case rclcpp_action::ResultCode::CANCELED: + RCLCPP_ERROR(node->get_logger(), "Goal was canceled"); + return 1; + default: + RCLCPP_ERROR(node->get_logger(), "Unknown result code"); + return 1; + } + + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/actions/minimal_action_client/not_composable_with_feedback.cpp b/src/example/rclcpp/actions/minimal_action_client/not_composable_with_feedback.cpp new file mode 100644 index 0000000..52683c7 --- /dev/null +++ b/src/example/rclcpp/actions/minimal_action_client/not_composable_with_feedback.cpp @@ -0,0 +1,108 @@ +// 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. + +#include +#include +#include + +#include "example_interfaces/action/fibonacci.hpp" +#include "rclcpp/rclcpp.hpp" +// TODO(jacobperron): Remove this once it is included as part of 'rclcpp.hpp' +#include "rclcpp_action/rclcpp_action.hpp" + +using Fibonacci = example_interfaces::action::Fibonacci; +rclcpp::Node::SharedPtr g_node = nullptr; + + +void feedback_callback( + rclcpp_action::ClientGoalHandle::SharedPtr, + const std::shared_ptr feedback) +{ + RCLCPP_INFO( + g_node->get_logger(), + "Next number in sequence received: %" PRId32, + feedback->sequence.back()); +} + +int main(int argc, char ** argv) +{ + rclcpp::init(argc, argv); + g_node = rclcpp::Node::make_shared("minimal_action_client"); + auto action_client = rclcpp_action::create_client(g_node, "fibonacci"); + + if (!action_client->wait_for_action_server(std::chrono::seconds(20))) { + RCLCPP_ERROR(g_node->get_logger(), "Action server not available after waiting"); + return 1; + } + + // Populate a goal + auto goal_msg = Fibonacci::Goal(); + goal_msg.order = 10; + + RCLCPP_INFO(g_node->get_logger(), "Sending goal"); + // Ask server to achieve some goal and wait until it's accepted + auto send_goal_options = rclcpp_action::Client::SendGoalOptions(); + send_goal_options.feedback_callback = feedback_callback; + auto goal_handle_future = action_client->async_send_goal(goal_msg, send_goal_options); + + if (rclcpp::spin_until_future_complete(g_node, goal_handle_future) != + rclcpp::FutureReturnCode::SUCCESS) + { + RCLCPP_ERROR(g_node->get_logger(), "send goal call failed :("); + return 1; + } + + rclcpp_action::ClientGoalHandle::SharedPtr goal_handle = goal_handle_future.get(); + if (!goal_handle) { + RCLCPP_ERROR(g_node->get_logger(), "Goal was rejected by server"); + return 1; + } + + // Wait for the server to be done with the goal + auto result_future = action_client->async_get_result(goal_handle); + + RCLCPP_INFO(g_node->get_logger(), "Waiting for result"); + if (rclcpp::spin_until_future_complete(g_node, result_future) != + rclcpp::FutureReturnCode::SUCCESS) + { + RCLCPP_ERROR(g_node->get_logger(), "get result call failed :("); + return 1; + } + + rclcpp_action::ClientGoalHandle::WrappedResult wrapped_result = result_future.get(); + + switch (wrapped_result.code) { + case rclcpp_action::ResultCode::SUCCEEDED: + break; + case rclcpp_action::ResultCode::ABORTED: + RCLCPP_ERROR(g_node->get_logger(), "Goal was aborted"); + return 1; + case rclcpp_action::ResultCode::CANCELED: + RCLCPP_ERROR(g_node->get_logger(), "Goal was canceled"); + return 1; + default: + RCLCPP_ERROR(g_node->get_logger(), "Unknown result code"); + return 1; + } + + RCLCPP_INFO(g_node->get_logger(), "result received"); + for (auto number : wrapped_result.result->sequence) { + RCLCPP_INFO(g_node->get_logger(), "%" PRId32, number); + } + + action_client.reset(); + g_node.reset(); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/actions/minimal_action_client/package.xml b/src/example/rclcpp/actions/minimal_action_client/package.xml new file mode 100644 index 0000000..6e81f42 --- /dev/null +++ b/src/example/rclcpp/actions/minimal_action_client/package.xml @@ -0,0 +1,28 @@ + + + + examples_rclcpp_minimal_action_client + 0.19.6 + Minimal action client examples + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Jacob Perron + Shane Loretz + + ament_cmake + + example_interfaces + rclcpp + rclcpp_action + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/example/rclcpp/actions/minimal_action_server/CHANGELOG.rst b/src/example/rclcpp/actions/minimal_action_server/CHANGELOG.rst new file mode 100644 index 0000000..222a64e --- /dev/null +++ b/src/example/rclcpp/actions/minimal_action_server/CHANGELOG.rst @@ -0,0 +1,196 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclcpp_minimal_action_server +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* Update the examples to C++17. (`#353 `_) +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash, Chris Lalancette + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- +* Make sure to include what you use in all examples. (`#284 `_) +* Added common linters (`#265 `_) +* Contributors: Alejandro Hernández Cordero, Chris Lalancette + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ + +0.9.0 (2020-04-30) +------------------ +* Restructure rclcpp folders (`#264 `_) +* Contributors: Marya Belanger + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ + +0.7.3 (2019-05-29) +------------------ +* Fix small typo. (`#242 `_) +* Contributors: Chris Lalancette + +0.7.2 (2019-05-20) +------------------ + +0.7.1 (2019-05-08) +------------------ +* Avoid deprecated API's by providing history settings (`#240 `_) +* Add rclcpp action examples using member functions +* fix typo in action server example (`#237 `_) +* Rename action state transitions (`#234 `_) +* Contributors: Jacob Perron, Karsten Knese, William Woodall + +0.7.0 (2019-04-14) +------------------ +* Updated to use separated action types. (`#227 `_) +* Contributors: Dirk Thomas + +0.6.2 (2019-02-08) +------------------ + +0.6.1 (2018-12-07) +------------------ +* Rclcpp action examples (`#220 `_) + * Add minimal_action_server package + Contains a non-composable implementation with global variables. + * Add minimal_action_client package + Contains a non-composable implementation. + * Add action client example with feedback + * async python action client example + * goal -> future + * fibb -> fib" + * Syncronous action client example + * No break statement + * Update client examples to use separate rcl_action package + * Add ClientGoalHandle to action client examplesj + * Add action client with cancel example + * python non-composable server example + * [wip] Update action server cpp example + * remove unnecessary event + * create_action_server -> ActionServer + * missing paren + * Add example of multiple goals in parallel + * No need for lock + * Reentrant callback group for execute + * create_action_client -> ActionClient + * Fix copyright date + * ) + * -) + * Refactor action server cpp example + * Fix action server cpp example + Seed the fibonacci sequence and remove const. + * Fix action server cpp example + Forgot to increment in Fibonacci sequence loop. + * Syntax fixes + * node -> self + * handle cb returns accept or reject + * Update action client cpp example + Return goal handle (containing future) when sending a goal. + * Preempt goals + * whitespace removal + * execute returns result + * Add missing resources + * Syntax error + * Add rcl_action dependency + * Update maintainer + * Use goal message getter and alias ResultResponse type + * Make minimal_action_server work with rclcpp_action + * Client and server communicate + * handle_execute -> handle_accepted + * Check if goal was rejected by server + * Update example to check result + * action client cancel example C++ works + * misc changes to compile + * misc client api changes + * Remove python examples + * Wait for action server +* Contributors: Shane Loretz + +0.6.0 (2018-11-20) +------------------ + +0.5.1 (2018-06-27) +------------------ + +0.5.0 (2018-06-26) +------------------ + +0.4.0 (2017-12-08) +------------------ diff --git a/src/example/rclcpp/actions/minimal_action_server/CMakeLists.txt b/src/example/rclcpp/actions/minimal_action_server/CMakeLists.txt new file mode 100644 index 0000000..807b92a --- /dev/null +++ b/src/example/rclcpp/actions/minimal_action_server/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required(VERSION 3.5) +project(examples_rclcpp_minimal_action_server) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) +find_package(example_interfaces REQUIRED) +find_package(rclcpp REQUIRED) +find_package(rclcpp_action REQUIRED) + +add_executable(action_server_member_functions member_functions.cpp) +ament_target_dependencies(action_server_member_functions + "rclcpp" + "rclcpp_action" + "example_interfaces") + +add_executable(action_server_not_composable not_composable.cpp) +ament_target_dependencies(action_server_not_composable + "rclcpp" + "rclcpp_action" + "example_interfaces") + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +install(TARGETS + action_server_not_composable + action_server_member_functions + DESTINATION lib/${PROJECT_NAME}) + +ament_package() diff --git a/src/example/rclcpp/actions/minimal_action_server/README.md b/src/example/rclcpp/actions/minimal_action_server/README.md new file mode 100644 index 0000000..6ca790d --- /dev/null +++ b/src/example/rclcpp/actions/minimal_action_server/README.md @@ -0,0 +1,3 @@ +# Minimal action server cookbook recipes + +This package contains a few examples which show how to create action servers. diff --git a/src/example/rclcpp/actions/minimal_action_server/member_functions.cpp b/src/example/rclcpp/actions/minimal_action_server/member_functions.cpp new file mode 100644 index 0000000..f1266af --- /dev/null +++ b/src/example/rclcpp/actions/minimal_action_server/member_functions.cpp @@ -0,0 +1,124 @@ +// Copyright 2019 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. + +#include +#include +#include + +#include "example_interfaces/action/fibonacci.hpp" +#include "rclcpp/rclcpp.hpp" +// TODO(jacobperron): Remove this once it is included as part of 'rclcpp.hpp' +#include "rclcpp_action/rclcpp_action.hpp" + +class MinimalActionServer : public rclcpp::Node +{ +public: + using Fibonacci = example_interfaces::action::Fibonacci; + using GoalHandleFibonacci = rclcpp_action::ServerGoalHandle; + + explicit MinimalActionServer(const rclcpp::NodeOptions & options = rclcpp::NodeOptions()) + : Node("minimal_action_server", options) + { + using namespace std::placeholders; + + this->action_server_ = rclcpp_action::create_server( + this->get_node_base_interface(), + this->get_node_clock_interface(), + this->get_node_logging_interface(), + this->get_node_waitables_interface(), + "fibonacci", + std::bind(&MinimalActionServer::handle_goal, this, _1, _2), + std::bind(&MinimalActionServer::handle_cancel, this, _1), + std::bind(&MinimalActionServer::handle_accepted, this, _1)); + } + +private: + rclcpp_action::Server::SharedPtr action_server_; + + rclcpp_action::GoalResponse handle_goal( + const rclcpp_action::GoalUUID & uuid, + std::shared_ptr goal) + { + RCLCPP_INFO(this->get_logger(), "Received goal request with order %d", goal->order); + (void)uuid; + // Let's reject sequences that are over 9000 + if (goal->order > 9000) { + return rclcpp_action::GoalResponse::REJECT; + } + return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE; + } + + rclcpp_action::CancelResponse handle_cancel( + const std::shared_ptr goal_handle) + { + RCLCPP_INFO(this->get_logger(), "Received request to cancel goal"); + (void)goal_handle; + return rclcpp_action::CancelResponse::ACCEPT; + } + + void execute(const std::shared_ptr goal_handle) + { + RCLCPP_INFO(this->get_logger(), "Executing goal"); + rclcpp::Rate loop_rate(1); + const auto goal = goal_handle->get_goal(); + auto feedback = std::make_shared(); + auto & sequence = feedback->sequence; + sequence.push_back(0); + sequence.push_back(1); + auto result = std::make_shared(); + + for (int i = 1; (i < goal->order) && rclcpp::ok(); ++i) { + // Check if there is a cancel request + if (goal_handle->is_canceling()) { + result->sequence = sequence; + goal_handle->canceled(result); + RCLCPP_INFO(this->get_logger(), "Goal Canceled"); + return; + } + // Update sequence + sequence.push_back(sequence[i] + sequence[i - 1]); + // Publish feedback + goal_handle->publish_feedback(feedback); + RCLCPP_INFO(this->get_logger(), "Publish Feedback"); + + loop_rate.sleep(); + } + + // Check if goal is done + if (rclcpp::ok()) { + result->sequence = sequence; + goal_handle->succeed(result); + RCLCPP_INFO(this->get_logger(), "Goal Succeeded"); + } + } + + void handle_accepted(const std::shared_ptr goal_handle) + { + using namespace std::placeholders; + // this needs to return quickly to avoid blocking the executor, so spin up a new thread + std::thread{std::bind(&MinimalActionServer::execute, this, _1), goal_handle}.detach(); + } +}; // class MinimalActionServer + +int main(int argc, char ** argv) +{ + rclcpp::init(argc, argv); + + auto action_server = std::make_shared(); + + rclcpp::spin(action_server); + + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/actions/minimal_action_server/not_composable.cpp b/src/example/rclcpp/actions/minimal_action_server/not_composable.cpp new file mode 100644 index 0000000..b82cbf8 --- /dev/null +++ b/src/example/rclcpp/actions/minimal_action_server/not_composable.cpp @@ -0,0 +1,109 @@ +// 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. + +#include +#include + +#include "example_interfaces/action/fibonacci.hpp" +#include "rclcpp/rclcpp.hpp" +// TODO(jacobperron): Remove this once it is included as part of 'rclcpp.hpp' +#include "rclcpp_action/rclcpp_action.hpp" + +using Fibonacci = example_interfaces::action::Fibonacci; +using GoalHandleFibonacci = rclcpp_action::ServerGoalHandle; + +rclcpp_action::GoalResponse handle_goal( + const rclcpp_action::GoalUUID & uuid, std::shared_ptr goal) +{ + RCLCPP_INFO(rclcpp::get_logger("server"), "Got goal request with order %d", goal->order); + (void)uuid; + // Let's reject sequences that are over 9000 + if (goal->order > 9000) { + return rclcpp_action::GoalResponse::REJECT; + } + return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE; +} + +rclcpp_action::CancelResponse handle_cancel( + const std::shared_ptr goal_handle) +{ + RCLCPP_INFO(rclcpp::get_logger("server"), "Got request to cancel goal"); + (void)goal_handle; + return rclcpp_action::CancelResponse::ACCEPT; +} + +void execute( + const std::shared_ptr goal_handle) +{ + RCLCPP_INFO(rclcpp::get_logger("server"), "Executing goal"); + rclcpp::Rate loop_rate(1); + const auto goal = goal_handle->get_goal(); + auto feedback = std::make_shared(); + auto & sequence = feedback->sequence; + sequence.push_back(0); + sequence.push_back(1); + auto result = std::make_shared(); + + for (int i = 1; (i < goal->order) && rclcpp::ok(); ++i) { + // Check if there is a cancel request + if (goal_handle->is_canceling()) { + result->sequence = sequence; + goal_handle->canceled(result); + RCLCPP_INFO(rclcpp::get_logger("server"), "Goal Canceled"); + return; + } + // Update sequence + sequence.push_back(sequence[i] + sequence[i - 1]); + // Publish feedback + goal_handle->publish_feedback(feedback); + RCLCPP_INFO(rclcpp::get_logger("server"), "Publish Feedback"); + + loop_rate.sleep(); + } + + // Check if goal is done + if (rclcpp::ok()) { + result->sequence = sequence; + goal_handle->succeed(result); + RCLCPP_INFO(rclcpp::get_logger("server"), "Goal Succeeded"); + } +} + +void handle_accepted(const std::shared_ptr goal_handle) +{ + // this needs to return quickly to avoid blocking the executor, so spin up a new thread + std::thread{execute, goal_handle}.detach(); +} + +int main(int argc, char ** argv) +{ + rclcpp::init(argc, argv); + auto node = rclcpp::Node::make_shared("minimal_action_server"); + + // Create an action server with three callbacks + // 'handle_goal' and 'handle_cancel' are called by the Executor (rclcpp::spin) + // 'execute' is called whenever 'handle_goal' returns by accepting a goal + // Calls to 'execute' are made in an available thread from a pool of four. + auto action_server = rclcpp_action::create_server( + node, + "fibonacci", + handle_goal, + handle_cancel, + handle_accepted); + + rclcpp::spin(node); + + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/actions/minimal_action_server/package.xml b/src/example/rclcpp/actions/minimal_action_server/package.xml new file mode 100644 index 0000000..6c91114 --- /dev/null +++ b/src/example/rclcpp/actions/minimal_action_server/package.xml @@ -0,0 +1,28 @@ + + + + examples_rclcpp_minimal_action_server + 0.19.6 + Minimal action server examples + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Jacob Perron + Shane Loretz + + ament_cmake + + example_interfaces + rclcpp + rclcpp_action + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/example/rclcpp/composition/minimal_composition/CHANGELOG.rst b/src/example/rclcpp/composition/minimal_composition/CHANGELOG.rst new file mode 100644 index 0000000..e3cd7fc --- /dev/null +++ b/src/example/rclcpp/composition/minimal_composition/CHANGELOG.rst @@ -0,0 +1,152 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclcpp_minimal_composition +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* Update the examples to C++17. (`#353 `_) +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash, Chris Lalancette + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- +* Added common linters (`#265 `_) +* Contributors: Alejandro Hernández Cordero + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ + +0.9.0 (2020-04-30) +------------------ +* Restructure rclcpp folders (`#264 `_) +* Contributors: Marya Belanger + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ + +0.7.3 (2019-05-29) +------------------ + +0.7.2 (2019-05-20) +------------------ + +0.7.1 (2019-05-08) +------------------ +* Avoid deprecated API's by providing history settings (`#240 `_) +* Contributors: William Woodall + +0.7.0 (2019-04-14) +------------------ +* Updated minimal_composition example for rclcpp_components. (`#232 `_) +* Contributors: Michael Carroll + +0.6.2 (2019-02-08) +------------------ + +0.6.0 (2018-11-20) +------------------ +* Added library path environment hook. (`#215 `_) +* Added semicolons to all RCLCPP and RCUTILS macros. (`#214 `_) +* Contributors: Chris Lalancette, Steven! Ragnarök + +0.5.1 (2018-06-27) +------------------ +* make Mikael Arguedas the maintainer (`#212 `_) +* Contributors: Mikael Arguedas + +0.5.0 (2018-06-26) +------------------ +* Change #if to #ifdef `#206 `_ +* Deprecate class loader headers (`#204 `_) +* Use std::chrono_literals only (`#197 `_) +* Contributors: Michael Carroll, Mikael Arguedas, Yutaka Kondo + +0.4.0 (2017-12-08) +------------------ +* Use logging (`#190 `_) +* 0.0.3 +* call shutdown before exiting (`#179 `_) +* 0.0.2 +* rename executables with shorter names (`#177 `_) +* install executables in package specific path (`#173 `_) +* use CMAKE_X_STANDARD and check compiler rather than platform +* use chrono types and literals (`#158 `_) +* c++14 not c++11 (`#157 `_) +* minimal example of how to compose nodes at compile or run time (`#145 `_) +* Contributors: Dirk Thomas, Mikael Arguedas, Morgan Quigley, William Woodall diff --git a/src/example/rclcpp/composition/minimal_composition/CMakeLists.txt b/src/example/rclcpp/composition/minimal_composition/CMakeLists.txt new file mode 100644 index 0000000..849f102 --- /dev/null +++ b/src/example/rclcpp/composition/minimal_composition/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.5) +project(examples_rclcpp_minimal_composition) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(rclcpp_components REQUIRED) +find_package(std_msgs REQUIRED) + +include_directories(include) + +add_library(composition_nodes SHARED + src/publisher_node.cpp + src/subscriber_node.cpp) +target_compile_definitions(composition_nodes + PRIVATE "MINIMAL_COMPOSITION_DLL") +ament_target_dependencies(composition_nodes rclcpp rclcpp_components std_msgs) + +# This package installs libraries without exporting them. +# Export the library path to ensure that the installed libraries are available. +if(NOT WIN32) + ament_environment_hooks( + "${ament_cmake_package_templates_ENVIRONMENT_HOOK_LIBRARY_PATH}") +endif() + +add_executable(composition_publisher src/standalone_publisher.cpp) +target_link_libraries(composition_publisher composition_nodes) +ament_target_dependencies(composition_publisher + rclcpp) + +add_executable(composition_subscriber src/standalone_subscriber.cpp) +target_link_libraries(composition_subscriber composition_nodes) +ament_target_dependencies(composition_subscriber + rclcpp) + +add_executable(composition_composed src/composed.cpp) +target_link_libraries(composition_composed composition_nodes) +ament_target_dependencies(composition_composed rclcpp class_loader) + +install(TARGETS + composition_nodes + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) + +install(TARGETS + composition_publisher + composition_subscriber + composition_composed + DESTINATION lib/${PROJECT_NAME}) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/example/rclcpp/composition/minimal_composition/include/minimal_composition/publisher_node.hpp b/src/example/rclcpp/composition/minimal_composition/include/minimal_composition/publisher_node.hpp new file mode 100644 index 0000000..95c29c4 --- /dev/null +++ b/src/example/rclcpp/composition/minimal_composition/include/minimal_composition/publisher_node.hpp @@ -0,0 +1,34 @@ +// Copyright 2016 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. + +#ifndef MINIMAL_COMPOSITION__PUBLISHER_NODE_HPP_ +#define MINIMAL_COMPOSITION__PUBLISHER_NODE_HPP_ + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" +#include "minimal_composition/visibility.h" + +class PublisherNode : public rclcpp::Node +{ +public: + MINIMAL_COMPOSITION_PUBLIC PublisherNode(rclcpp::NodeOptions options); + +private: + void on_timer(); + size_t count_; + rclcpp::Publisher::SharedPtr publisher_; + rclcpp::TimerBase::SharedPtr timer_; +}; + +#endif // MINIMAL_COMPOSITION__PUBLISHER_NODE_HPP_ diff --git a/src/example/rclcpp/composition/minimal_composition/include/minimal_composition/subscriber_node.hpp b/src/example/rclcpp/composition/minimal_composition/include/minimal_composition/subscriber_node.hpp new file mode 100644 index 0000000..e521529 --- /dev/null +++ b/src/example/rclcpp/composition/minimal_composition/include/minimal_composition/subscriber_node.hpp @@ -0,0 +1,31 @@ +// Copyright 2016 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. + +#ifndef MINIMAL_COMPOSITION__SUBSCRIBER_NODE_HPP_ +#define MINIMAL_COMPOSITION__SUBSCRIBER_NODE_HPP_ + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" +#include "minimal_composition/visibility.h" + +class SubscriberNode : public rclcpp::Node +{ +public: + MINIMAL_COMPOSITION_PUBLIC SubscriberNode(rclcpp::NodeOptions options); + +private: + rclcpp::Subscription::SharedPtr subscription_; +}; + +#endif // MINIMAL_COMPOSITION__SUBSCRIBER_NODE_HPP_ diff --git a/src/example/rclcpp/composition/minimal_composition/include/minimal_composition/visibility.h b/src/example/rclcpp/composition/minimal_composition/include/minimal_composition/visibility.h new file mode 100644 index 0000000..4d8047c --- /dev/null +++ b/src/example/rclcpp/composition/minimal_composition/include/minimal_composition/visibility.h @@ -0,0 +1,66 @@ +// Copyright 2016 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. + +#ifndef MINIMAL_COMPOSITION__VISIBILITY_H_ +#define MINIMAL_COMPOSITION__VISIBILITY_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +// This logic was borrowed (then namespaced) from the examples on the gcc wiki: +// https://gcc.gnu.org/wiki/Visibility + +#if defined _WIN32 || defined __CYGWIN__ + + #ifdef __GNUC__ + #define MINIMAL_COMPOSITION_EXPORT __attribute__ ((dllexport)) + #define MINIMAL_COMPOSITION_IMPORT __attribute__ ((dllimport)) + #else + #define MINIMAL_COMPOSITION_EXPORT __declspec(dllexport) + #define MINIMAL_COMPOSITION_IMPORT __declspec(dllimport) + #endif + + #ifdef MINIMAL_COMPOSITION_DLL + #define MINIMAL_COMPOSITION_PUBLIC MINIMAL_COMPOSITION_EXPORT + #else + #define MINIMAL_COMPOSITION_PUBLIC MINIMAL_COMPOSITION_IMPORT + #endif + + #define MINIMAL_COMPOSITION_PUBLIC_TYPE MINIMAL_COMPOSITION_PUBLIC + + #define MINIMAL_COMPOSITION_LOCAL + +#else + + #define MINIMAL_COMPOSITION_EXPORT __attribute__ ((visibility("default"))) + #define MINIMAL_COMPOSITION_IMPORT + + #if __GNUC__ >= 4 + #define MINIMAL_COMPOSITION_PUBLIC __attribute__ ((visibility("default"))) + #define MINIMAL_COMPOSITION_LOCAL __attribute__ ((visibility("hidden"))) + #else + #define MINIMAL_COMPOSITION_PUBLIC + #define MINIMAL_COMPOSITION_LOCAL + #endif + + #define MINIMAL_COMPOSITION_PUBLIC_TYPE +#endif + +#ifdef __cplusplus +} +#endif + +#endif // MINIMAL_COMPOSITION__VISIBILITY_H_ diff --git a/src/example/rclcpp/composition/minimal_composition/package.xml b/src/example/rclcpp/composition/minimal_composition/package.xml new file mode 100644 index 0000000..d0d2110 --- /dev/null +++ b/src/example/rclcpp/composition/minimal_composition/package.xml @@ -0,0 +1,35 @@ + + + + examples_rclcpp_minimal_composition + 0.19.6 + Minimalist examples of composing nodes in the same + process + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Jacob Perron + Mikael Arguedas + Morgan Quigley + Shane Loretz + + ament_cmake + + rclcpp + rclcpp_components + std_msgs + + rclcpp + rclcpp_components + std_msgs + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/example/rclcpp/composition/minimal_composition/src/composed.cpp b/src/example/rclcpp/composition/minimal_composition/src/composed.cpp new file mode 100644 index 0000000..dba1ac0 --- /dev/null +++ b/src/example/rclcpp/composition/minimal_composition/src/composed.cpp @@ -0,0 +1,32 @@ +// Copyright 2016 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. + +#include +#include "minimal_composition/publisher_node.hpp" +#include "minimal_composition/subscriber_node.hpp" +#include "rclcpp/rclcpp.hpp" + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::executors::SingleThreadedExecutor exec; + rclcpp::NodeOptions options; + auto publisher_node = std::make_shared(options); + auto subscriber_node = std::make_shared(options); + exec.add_node(publisher_node); + exec.add_node(subscriber_node); + exec.spin(); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/composition/minimal_composition/src/publisher_node.cpp b/src/example/rclcpp/composition/minimal_composition/src/publisher_node.cpp new file mode 100644 index 0000000..b21917a --- /dev/null +++ b/src/example/rclcpp/composition/minimal_composition/src/publisher_node.cpp @@ -0,0 +1,41 @@ +// Copyright 2016 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. + +#include + +#include "minimal_composition/publisher_node.hpp" +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using namespace std::chrono_literals; + +PublisherNode::PublisherNode(rclcpp::NodeOptions options) +: Node("publisher_node", options), count_(0) +{ + publisher_ = create_publisher("topic", 10); + timer_ = create_wall_timer( + 500ms, std::bind(&PublisherNode::on_timer, this)); +} + +void PublisherNode::on_timer() +{ + auto message = std_msgs::msg::String(); + message.data = "Hello, world! " + std::to_string(count_++); + RCLCPP_INFO(this->get_logger(), "Publisher: '%s'", message.data.c_str()); + publisher_->publish(message); +} + +#include "rclcpp_components/register_node_macro.hpp" + +RCLCPP_COMPONENTS_REGISTER_NODE(PublisherNode) diff --git a/src/example/rclcpp/composition/minimal_composition/src/standalone_publisher.cpp b/src/example/rclcpp/composition/minimal_composition/src/standalone_publisher.cpp new file mode 100644 index 0000000..3eb4eb0 --- /dev/null +++ b/src/example/rclcpp/composition/minimal_composition/src/standalone_publisher.cpp @@ -0,0 +1,25 @@ +// Copyright 2016 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. + +#include +#include "minimal_composition/publisher_node.hpp" +#include "rclcpp/rclcpp.hpp" + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared(rclcpp::NodeOptions())); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/composition/minimal_composition/src/standalone_subscriber.cpp b/src/example/rclcpp/composition/minimal_composition/src/standalone_subscriber.cpp new file mode 100644 index 0000000..fca148d --- /dev/null +++ b/src/example/rclcpp/composition/minimal_composition/src/standalone_subscriber.cpp @@ -0,0 +1,25 @@ +// Copyright 2016 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. + +#include +#include "minimal_composition/subscriber_node.hpp" +#include "rclcpp/rclcpp.hpp" + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared(rclcpp::NodeOptions())); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/composition/minimal_composition/src/subscriber_node.cpp b/src/example/rclcpp/composition/minimal_composition/src/subscriber_node.cpp new file mode 100644 index 0000000..0f5e32c --- /dev/null +++ b/src/example/rclcpp/composition/minimal_composition/src/subscriber_node.cpp @@ -0,0 +1,32 @@ +// Copyright 2016 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. + +#include "minimal_composition/subscriber_node.hpp" +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +SubscriberNode::SubscriberNode(rclcpp::NodeOptions options) +: Node("subscriber_node", options) +{ + subscription_ = create_subscription( + "topic", + 10, + [this](std_msgs::msg::String::UniquePtr msg) { + RCLCPP_INFO(this->get_logger(), "Subscriber: '%s'", msg->data.c_str()); + }); +} + +#include "rclcpp_components/register_node_macro.hpp" + +RCLCPP_COMPONENTS_REGISTER_NODE(SubscriberNode) diff --git a/src/example/rclcpp/executors/cbg_executor/CHANGELOG.rst b/src/example/rclcpp/executors/cbg_executor/CHANGELOG.rst new file mode 100644 index 0000000..5b35bb1 --- /dev/null +++ b/src/example/rclcpp/executors/cbg_executor/CHANGELOG.rst @@ -0,0 +1,82 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclcpp_cbg_executor +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* Update the examples to C++17. (`#353 `_) +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash, Chris Lalancette + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- +* Improve scheduling configuration of examples_rclcpp_cbg_executor package (`#331 `_) +* Added jitter measurement to examples_rclcpp_cbg_executor. (`#328 `_) +* Contributors: Ralph Lange + +0.14.0 (2022-01-14) +------------------- + +0.13.0 (2021-10-18) +------------------- +* Fix deprecated subscriber callbacks (`#323 `_) +* Contributors: Abrar Rahman Protyasha + +0.12.0 (2021-08-05) +------------------- +* Remove use of get_callback_groups(). (`#320 `_) +* Contributors: Chris Lalancette + +0.11.2 (2021-04-26) +------------------- + +0.11.1 (2021-04-12) +------------------- +* Fix clang warnings about type mismatches. (`#309 `_) +* Contributors: Chris Lalancette + +0.11.0 (2021-04-06) +------------------- +* Support for cbg_executor package on QNX (`#305 `_) +* Contributors: joshua-qnx + +0.10.3 (2021-03-18) +------------------- +* Demo for callback-group-level executor concept. (`#302 `_) +* Contributors: Ralph Lange diff --git a/src/example/rclcpp/executors/cbg_executor/CMakeLists.txt b/src/example/rclcpp/executors/cbg_executor/CMakeLists.txt new file mode 100644 index 0000000..57157b2 --- /dev/null +++ b/src/example/rclcpp/executors/cbg_executor/CMakeLists.txt @@ -0,0 +1,56 @@ +cmake_minimum_required(VERSION 3.5) +project(examples_rclcpp_cbg_executor) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) + +add_executable( + ping + src/ping.cpp + src/examples_rclcpp_cbg_executor/ping_node.cpp +) +target_include_directories(ping PUBLIC include) +ament_target_dependencies(ping rclcpp std_msgs) + +add_executable( + pong + src/pong.cpp + src/examples_rclcpp_cbg_executor/pong_node.cpp +) +target_include_directories(pong PUBLIC include) +ament_target_dependencies(pong rclcpp std_msgs) + +add_executable( + ping_pong + src/ping_pong.cpp + src/examples_rclcpp_cbg_executor/ping_node.cpp + src/examples_rclcpp_cbg_executor/pong_node.cpp +) +target_include_directories(ping_pong PUBLIC include) +ament_target_dependencies(ping_pong rclcpp std_msgs) + +install(TARGETS ping pong ping_pong + DESTINATION lib/${PROJECT_NAME} +) +install( + DIRECTORY include/ + DESTINATION include +) +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_export_dependencies(rclcpp std_msgs) +ament_package() diff --git a/src/example/rclcpp/executors/cbg_executor/README.md b/src/example/rclcpp/executors/cbg_executor/README.md new file mode 100644 index 0000000..ae8981f --- /dev/null +++ b/src/example/rclcpp/executors/cbg_executor/README.md @@ -0,0 +1,100 @@ +# examples_rclcpp_cbg_executor + +The *examples_rclcpp_cbg_executor* package provides a demo and test bench for the *Callback-group-level Executor* concept. This concept was developed in 2018 and has been integrated in ROS 2 mainline in 2020, i.e., is available from ROS 2 Galactic on. It does not add a new Executor but leverages callback groups for refining the Executor API to callback-group-level granularity. + +This allows a single node to have callbacks with different real-time requirements assigned to different Executor instances – within one process. Thus, an Executor instance can be dedicated to one or few specific callback groups and the Executor’s thread (or threads) can be prioritized according to the real-time requirements of these groups. For example, all critical callbacks may be handled by an Executor instance based on an thread running at the highest scheduler priority. + +## Introduction to demo + +The demo comprises a *Ping Node* and a *Pong Node* which exchange messages on two communication paths simultaneously. There is a high priority path formed by the topics *high_ping* and *high_pong* and a low priority path formed by *low_ping* and *low_pong*, respectively. + +![](doc/ping_pong_diagram.png) + +The Ping Node sends ping messages on both paths simultaneously at a configurable rate. The Pong Node takes these ping messages and replies each of them. Before sending a reply, it burns a configurable number of CPU cycles (thereby varying the processor load) to simulate some message processing. + +All callbacks of the Ping Node (i.e., for the timer for sending ping messages and for the two subscription on high_pong and low_pong) are handled in one callback group and thus Executor instance. However, the two callbacks of the Pong Node that process the incoming ping messages and answer with a pong message are assigned to two different callback groups. In the main function, these two groups are distributed to two Executor instances and threads. Both threads are pinned to the same CPU (No. 0) and thus share its processing power, but with different scheduler priorities following the names *high* and *low*. + +## Running the demo + +The Ping Node and Pong Node may be either started in one process or in two processes. Please note that on Linux the demo requires sudo privileges to be able to change the thread priorities using `pthread_setschedparam(..)`. + +Running the two nodes in one process: + +```bash +sudo bash +source /opt/ros/[ROS_DISTRO]/setup.bash +ros2 run examples_rclcpp_cbg_executor ping_pong +``` + +Example of a typical output - note the zero pongs received on the low prio path: + +``` +[INFO] [..] [pong_node]: Running experiment from now on for 10 seconds ... +[INFO] [..] [ping_node]: Both paths: Sent out 953 of configured 1000 pings, i.e. 95%. +[INFO] [..] [ping_node]: High prio path: Received 951 pongs, i.e. for 99% of the pings. +[INFO] [..] [ping_node]: High prio path: Average RTT is 14.0ms. +[INFO] [..] [ping_node]: High prio path: Jitter of RTT is 7.460ms. +[INFO] [..] [ping_node]: Low prio path: Received 0 pongs, i.e. for 0% of the pings. +[INFO] [..] [pong_node]: High priority executor thread ran for 9542ms. +[INFO] [..] [pong_node]: Low priority executor thread ran for 0ms. +``` + +Note: On Linux, the two Executor threads, which are both scheduled under `SCHED_FIFO`, can consume only 95% of the CPU time due to [RT throttling](https://wiki.linuxfoundation.org/realtime/documentation/technical_basics/sched_rt_throttling). + +Running the two nodes in separate processes: + +```bash +sudo bash +source /opt/ros/[ROS_DISTRO]/setup.bash +ros2 run examples_rclcpp_cbg_executor ping +``` + +```bash +sudo bash +source /opt/ros/[ROS_DISTRO]/setup.bash +ros2 run examples_rclcpp_cbg_executor pong +``` + +The two processes should be started simultaneously as the experiment runtime is just 10 seconds. + +## Parameters + +There are three parameters to configure the experiment: + +* `ping_period` - period (double value in seconds) for sending out pings on the topics high_ping and low_ping simultaneously in the Ping Node. +* `high_busyloop` - duration (double value in seconds) for burning CPU cycles on receiving a message from high_ping in the Pong Node. +* `low_busyloop` - duration (double value in seconds) for burning CPU cycles on receiving a message from low_ping in the Pong Node. + +The default values are 0.01 seconds for all three parameters. + +Example for changing the values on the command line: + +```bash +ros2 run examples_rclcpp_cbg_executor ping_pong --ros-args -p ping_period:=0.033 -p high_busyloop:=0.025 +``` + +With these values, about (0.033s - 0.025s) / 0.010s = 80% of the ping messages on the low prio path should be processed and answered by a pong message: + +``` +... +[INFO] [..] [ping_node]: Both paths: Sent out 294 of configured 303 pings, i.e. 97%. +[INFO] [..] [ping_node]: High prio path: Received 293 pongs, i.e. for 99% of the pings. +[INFO] [..] [ping_node]: High prio path: Average RTT is 26.2ms. +[INFO] [..] [ping_node]: High prio path: Jitter of RTT is 7.654ms. +[INFO] [..] [ping_node]: Low prio path: Received 216 pongs, i.e. for 73% of the pings. +[INFO] [..] [ping_node]: Low prio path: Average RTT is 202.5ms. +[INFO] [..] [ping_node]: Low prio path: Jitter of RTT is 36.301ms. +... +``` + +## Implementation details + +The Ping Node and the Pong Node are implemented in two classes `PingNode` (see [ping_node.hpp](include/examples_rclcpp_cbg_executor/ping_node.hpp)) and `PongNode` (see [pong_node.hpp](include/examples_rclcpp_cbg_executor/pong_node.hpp)), respectively. In addition to the mentioned timer and subscriptions, the PingNode class provides a function `print_statistics()` to print statistics on the number of sent and received messages on each path and the average round trip times. To burn the specified number of CPU cycles, the PongNode class contains a function `burn_cpu_cycles(duration)` to simulate a given processing time before replying with a pong. + +The Ping and Pong nodes, the two executors, etc. are composed and configured in the `main(..)` function of [main.cpp](src/main.cpp). This function also starts and ends the experiment for a duration of 10 seconds and prints out the throughput and round trip time (RTT) statistics. + +The demo also runs on Windows, where the two threads are prioritized as *above normal* and *below normal*, respectively, which does not require elevated privileges. When running the demo on Linux without sudo privileges, a warning is shown but the execution is not stopped. + +## Known issues + +On macOS the core pinning failed silently in our experiments. Please see the function `configure_native_thread(..)` in [utilities.hpp](src/examples_rclcpp_cbg_executor/utilities.hpp) for details. \ No newline at end of file diff --git a/src/example/rclcpp/executors/cbg_executor/doc/ping_pong_diagram.png b/src/example/rclcpp/executors/cbg_executor/doc/ping_pong_diagram.png new file mode 100644 index 0000000..643617b Binary files /dev/null and b/src/example/rclcpp/executors/cbg_executor/doc/ping_pong_diagram.png differ diff --git a/src/example/rclcpp/executors/cbg_executor/include/examples_rclcpp_cbg_executor/ping_node.hpp b/src/example/rclcpp/executors/cbg_executor/include/examples_rclcpp_cbg_executor/ping_node.hpp new file mode 100644 index 0000000..c8ec52e --- /dev/null +++ b/src/example/rclcpp/executors/cbg_executor/include/examples_rclcpp_cbg_executor/ping_node.hpp @@ -0,0 +1,65 @@ +// Copyright (c) 2020 Robert Bosch GmbH +// +// 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. + +#ifndef EXAMPLES_RCLCPP_CBG_EXECUTOR__PING_NODE_HPP_ +#define EXAMPLES_RCLCPP_CBG_EXECUTOR__PING_NODE_HPP_ + +#include +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/int32.hpp" + +namespace examples_rclcpp_cbg_executor +{ + +struct RTTData +{ + explicit RTTData(const rclcpp::Time & sent) + : sent_(sent) {} + rclcpp::Time sent_{0, 0}; + rclcpp::Time high_received_{0, 0}; + rclcpp::Time low_received_{0, 0}; +}; + +class PingNode : public rclcpp::Node +{ +public: + PingNode(); + + virtual ~PingNode() = default; + + void print_statistics(std::chrono::seconds experiment_duration) const; + +private: + rclcpp::TimerBase::SharedPtr ping_timer_; + rclcpp::Publisher::SharedPtr high_ping_publisher_; + rclcpp::Publisher::SharedPtr low_ping_publisher_; + void send_ping(); + + rclcpp::Subscription::SharedPtr high_pong_subscription_; + void high_pong_received(const std_msgs::msg::Int32::ConstSharedPtr msg); + + rclcpp::Subscription::SharedPtr low_pong_subscription_; + void low_pong_received(const std_msgs::msg::Int32::ConstSharedPtr msg); + + std::vector rtt_data_; +}; + +} // namespace examples_rclcpp_cbg_executor + +#endif // EXAMPLES_RCLCPP_CBG_EXECUTOR__PING_NODE_HPP_ diff --git a/src/example/rclcpp/executors/cbg_executor/include/examples_rclcpp_cbg_executor/pong_node.hpp b/src/example/rclcpp/executors/cbg_executor/include/examples_rclcpp_cbg_executor/pong_node.hpp new file mode 100644 index 0000000..7c02628 --- /dev/null +++ b/src/example/rclcpp/executors/cbg_executor/include/examples_rclcpp_cbg_executor/pong_node.hpp @@ -0,0 +1,55 @@ +// Copyright (c) 2020 Robert Bosch GmbH +// +// 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. + +#ifndef EXAMPLES_RCLCPP_CBG_EXECUTOR__PONG_NODE_HPP_ +#define EXAMPLES_RCLCPP_CBG_EXECUTOR__PONG_NODE_HPP_ + +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/int32.hpp" + +namespace examples_rclcpp_cbg_executor +{ + +class PongNode : public rclcpp::Node +{ +public: + PongNode(); + + virtual ~PongNode() = default; + + rclcpp::CallbackGroup::SharedPtr get_high_prio_callback_group(); + + rclcpp::CallbackGroup::SharedPtr get_low_prio_callback_group(); + +private: + rclcpp::CallbackGroup::SharedPtr low_prio_callback_group_; + + rclcpp::Subscription::SharedPtr high_ping_subscription_; + rclcpp::Publisher::SharedPtr high_pong_publisher_; + void high_ping_received(const std_msgs::msg::Int32::ConstSharedPtr msg); + + rclcpp::Subscription::SharedPtr low_ping_subscription_; + rclcpp::Publisher::SharedPtr low_pong_publisher_; + void low_ping_received(const std_msgs::msg::Int32::ConstSharedPtr msg); + + static void burn_cpu_cycles(std::chrono::nanoseconds duration); +}; + +} // namespace examples_rclcpp_cbg_executor + +#endif // EXAMPLES_RCLCPP_CBG_EXECUTOR__PONG_NODE_HPP_ diff --git a/src/example/rclcpp/executors/cbg_executor/package.xml b/src/example/rclcpp/executors/cbg_executor/package.xml new file mode 100644 index 0000000..8c7ea4f --- /dev/null +++ b/src/example/rclcpp/executors/cbg_executor/package.xml @@ -0,0 +1,26 @@ + + + + examples_rclcpp_cbg_executor + 0.19.6 + Example for multiple Executor instances in one process, using the callback-group-level interface of the Executor class. + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Ralph Lange + + ament_cmake + + rclcpp + std_msgs + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/example/rclcpp/executors/cbg_executor/src/examples_rclcpp_cbg_executor/ping_node.cpp b/src/example/rclcpp/executors/cbg_executor/src/examples_rclcpp_cbg_executor/ping_node.cpp new file mode 100644 index 0000000..4b65cb7 --- /dev/null +++ b/src/example/rclcpp/executors/cbg_executor/src/examples_rclcpp_cbg_executor/ping_node.cpp @@ -0,0 +1,107 @@ +// Copyright (c) 2020 Robert Bosch GmbH +// +// 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. + +#include "examples_rclcpp_cbg_executor/ping_node.hpp" + +#include +#include +#include +#include + +#include "./utilities.hpp" + +namespace examples_rclcpp_cbg_executor +{ + +PingNode::PingNode() +: rclcpp::Node("ping_node") +{ + using std::placeholders::_1; + using std_msgs::msg::Int32; + + this->declare_parameter("ping_period", 0.01); + std::chrono::nanoseconds ping_period = get_nanos_from_secs_parameter(this, "ping_period"); + + ping_timer_ = this->create_wall_timer(ping_period, std::bind(&PingNode::send_ping, this)); + high_ping_publisher_ = this->create_publisher("high_ping", rclcpp::SensorDataQoS()); + low_ping_publisher_ = this->create_publisher("low_ping", rclcpp::SensorDataQoS()); + + high_pong_subscription_ = this->create_subscription( + "high_pong", rclcpp::SensorDataQoS(), std::bind(&PingNode::high_pong_received, this, _1)); + low_pong_subscription_ = this->create_subscription( + "low_pong", rclcpp::SensorDataQoS(), std::bind(&PingNode::low_pong_received, this, _1)); +} + +void PingNode::send_ping() +{ + std_msgs::msg::Int32 msg; + msg.data = static_cast(rtt_data_.size()); + rtt_data_.push_back(RTTData(now())); + high_ping_publisher_->publish(msg); + low_ping_publisher_->publish(msg); +} + +void PingNode::high_pong_received(const std_msgs::msg::Int32::ConstSharedPtr msg) +{ + rtt_data_[msg->data].high_received_ = now(); +} + +void PingNode::low_pong_received(const std_msgs::msg::Int32::ConstSharedPtr msg) +{ + rtt_data_[msg->data].low_received_ = now(); +} + +void PingNode::print_statistics(std::chrono::seconds experiment_duration) const +{ + size_t ping_count = rtt_data_.size(); + + std::vector high_rtts; + std::vector low_rtts; + + for (const auto & entry : rtt_data_) { + if (entry.high_received_.nanoseconds() >= entry.sent_.nanoseconds()) { + high_rtts.push_back((entry.high_received_ - entry.sent_).seconds()); + } + if (entry.low_received_.nanoseconds() >= entry.sent_.nanoseconds()) { + low_rtts.push_back((entry.low_received_ - entry.sent_).seconds()); + } + } + + std::chrono::nanoseconds ping_period = get_nanos_from_secs_parameter(this, "ping_period"); + size_t ideal_ping_count = experiment_duration / ping_period; + RCLCPP_INFO( + get_logger(), "Both paths: Sent out %zu of configured %ld pings, i.e. %zu%%.", + ping_count, ideal_ping_count, 100 * ping_count / ideal_ping_count); + RCLCPP_INFO( + get_logger(), "High prio path: Received %zu pongs, i.e. for %zu%% of the pings.", + high_rtts.size(), 100 * high_rtts.size() / ping_count); + if (!high_rtts.empty()) { + double high_rtt_avg = calc_average(high_rtts) * 1000.0; + RCLCPP_INFO(get_logger(), "High prio path: Average RTT is %3.1fms.", high_rtt_avg); + double high_rtt_jitter = calc_std_deviation(high_rtts) * 1000.0; + RCLCPP_INFO(get_logger(), "High prio path: Jitter of RTT is %5.3fms.", high_rtt_jitter); + } + + RCLCPP_INFO( + get_logger(), "Low prio path: Received %zu pongs, i.e. for %zu%% of the pings.", + low_rtts.size(), 100 * low_rtts.size() / ping_count); + if (!low_rtts.empty()) { + double low_rtt_avg = calc_average(low_rtts) * 1000.0; + RCLCPP_INFO(get_logger(), "Low prio path: Average RTT is %3.1fms.", low_rtt_avg); + double low_rtt_jitter = calc_std_deviation(low_rtts) * 1000.0; + RCLCPP_INFO(get_logger(), "Low prio path: Jitter of RTT is %5.3fms.", low_rtt_jitter); + } +} + +} // namespace examples_rclcpp_cbg_executor diff --git a/src/example/rclcpp/executors/cbg_executor/src/examples_rclcpp_cbg_executor/pong_node.cpp b/src/example/rclcpp/executors/cbg_executor/src/examples_rclcpp_cbg_executor/pong_node.cpp new file mode 100644 index 0000000..234ba13 --- /dev/null +++ b/src/example/rclcpp/executors/cbg_executor/src/examples_rclcpp_cbg_executor/pong_node.cpp @@ -0,0 +1,90 @@ +// Copyright (c) 2020 Robert Bosch GmbH +// +// 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. + +#include "examples_rclcpp_cbg_executor/pong_node.hpp" + +#include + +#include +#include + +#include "./utilities.hpp" + +namespace examples_rclcpp_cbg_executor +{ + +PongNode::PongNode() +: rclcpp::Node("pong_node") +{ + using std::placeholders::_1; + using std_msgs::msg::Int32; + + declare_parameter("high_busyloop", 0.01); + high_pong_publisher_ = this->create_publisher("high_pong", rclcpp::SensorDataQoS()); + high_ping_subscription_ = this->create_subscription( + "high_ping", rclcpp::SensorDataQoS(), + std::bind(&PongNode::high_ping_received, this, _1)); + + low_prio_callback_group_ = this->create_callback_group( + rclcpp::CallbackGroupType::MutuallyExclusive); + + declare_parameter("low_busyloop", 0.01); + low_pong_publisher_ = this->create_publisher("low_pong", rclcpp::SensorDataQoS()); + rclcpp::SubscriptionOptionsWithAllocator> options; + options.callback_group = low_prio_callback_group_; + low_ping_subscription_ = this->create_subscription( + "low_ping", rclcpp::SensorDataQoS(), + std::bind(&PongNode::low_ping_received, this, _1), options); +} + +rclcpp::CallbackGroup::SharedPtr PongNode::get_high_prio_callback_group() +{ + return get_node_base_interface()->get_default_callback_group(); // the default callback group. +} + +rclcpp::CallbackGroup::SharedPtr PongNode::get_low_prio_callback_group() +{ + return low_prio_callback_group_; // the second callback group created in the ctor. +} + +void PongNode::high_ping_received(const std_msgs::msg::Int32::ConstSharedPtr msg) +{ + std::chrono::nanoseconds busyloop = get_nanos_from_secs_parameter(this, "high_busyloop"); + burn_cpu_cycles(busyloop); + high_pong_publisher_->publish(*msg); +} + +void PongNode::low_ping_received(const std_msgs::msg::Int32::ConstSharedPtr msg) +{ + std::chrono::nanoseconds busyloop = get_nanos_from_secs_parameter(this, "low_busyloop"); + burn_cpu_cycles(busyloop); + low_pong_publisher_->publish(*msg); +} + +void PongNode::burn_cpu_cycles(std::chrono::nanoseconds duration) +{ + if (duration > std::chrono::nanoseconds::zero()) { + auto end_time = get_current_thread_time() + duration; + int x = 0; + bool do_again = true; + while (do_again) { + while (x != std::rand() && x % 1000 != 0) { + x++; + } + do_again = (get_current_thread_time() < end_time); + } + } +} + +} // namespace examples_rclcpp_cbg_executor diff --git a/src/example/rclcpp/executors/cbg_executor/src/examples_rclcpp_cbg_executor/utilities.hpp b/src/example/rclcpp/executors/cbg_executor/src/examples_rclcpp_cbg_executor/utilities.hpp new file mode 100644 index 0000000..272295b --- /dev/null +++ b/src/example/rclcpp/executors/cbg_executor/src/examples_rclcpp_cbg_executor/utilities.hpp @@ -0,0 +1,274 @@ +// Copyright (c) 2020 Robert Bosch GmbH +// +// 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. + +#ifndef EXAMPLES_RCLCPP_CBG_EXECUTOR__UTILITIES_HPP_ +#define EXAMPLES_RCLCPP_CBG_EXECUTOR__UTILITIES_HPP_ + +#include + +#include +#include +#include +#include +#include + +#ifdef _WIN32 // i.e., Windows platform. +#include +#elif __APPLE__ // i.e., macOS platform. +#include +#include +#include +#include +#include +#include +#include +#else // i.e., Linux platform. + #ifdef __QNXNTO__ + #include + #include + #endif + #include +#endif + +#include + +namespace examples_rclcpp_cbg_executor +{ + +/// Retrieves the value of the given seconds parameter in std::chrono nanoseconds. +inline std::chrono::nanoseconds get_nanos_from_secs_parameter( + const rclcpp::Node * node, + const std::string & name) +{ + double seconds = 0.0; + node->get_parameter(name, seconds); + auto nanos = std::chrono::nanoseconds(static_cast(seconds * 1000000000.0)); + return nanos; +} + +/// Enum for simple configuration of threads in two priority classes. +enum class ThreadPriority +{ + LOW, + HIGH +}; + +/// Sets the priority of the given native thread to max or min as given. +/// The exact priority value depends on the operating system. On Linux, +/// this requires elevated privileges. +/// Furthermore, if a non-negative CPU id is given, the thread is pinned +/// to that CPU. +template +bool configure_native_thread(T native_handle, ThreadPriority priority, int cpu_id) +{ + bool success = true; +#ifdef _WIN32 // i.e., Windows platform. + success &= (SetThreadPriority(native_handle, (priority == ThreadPriority::HIGH) ? 1 : -1) != 0); + if (cpu_id >= 0) { + DWORD_PTR cpuset = 1; + cpuset <<= cpu_id; + success &= (SetThreadAffinityMask(native_handle, cpuset) != 0); + } +#elif __APPLE__ // i.e., macOS platform. + thread_port_t mach_thread = pthread_mach_thread_np(native_handle); + thread_precedence_policy_data_t precedence_policy; + precedence_policy.importance = (priority == ThreadPriority::HIGH) ? 1 : 0; + kern_return_t ret = thread_policy_set( + mach_thread, THREAD_PRECEDENCE_POLICY, + reinterpret_cast(&precedence_policy), + THREAD_PRECEDENCE_POLICY_COUNT); + success &= (ret == KERN_SUCCESS); + if (cpu_id >= 0) { + // Pinning requires the thread to be suspended. + ret = thread_suspend(mach_thread); + success &= (ret == KERN_SUCCESS); + // Wait a few milliseconds until thread is really suspended. + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + thread_affinity_policy_data_t affinity_policy; + affinity_policy.affinity_tag = cpu_id; + // In our experiments, the following call did not work although it + // returned KERN_SUCCESS. If somebody knows how to fix this, please + // open a pull request! + ret = thread_policy_set( + mach_thread, THREAD_AFFINITY_POLICY, + reinterpret_cast(&affinity_policy), + THREAD_AFFINITY_POLICY_COUNT); + success &= (ret == KERN_SUCCESS); + ret = thread_resume(mach_thread); + success &= (ret == KERN_SUCCESS); + } +#elif __QNXNTO__ // i.e., QNX platform + sched_param params; + int policy; + success &= (pthread_getschedparam(native_handle, &policy, ¶ms) == 0); + if (priority == ThreadPriority::HIGH) { + // Choose a priority value slighly below the middle. + params.sched_priority = + (sched_get_priority_min(SCHED_FIFO) + sched_get_priority_max(SCHED_FIFO)) / 2 - 1; + } else { + // Choose the lowest priority in SCHED_FIFO. This might still be higher than + // the priority of the DDS threads, which are not changed here. + params.sched_priority = sched_get_priority_min(SCHED_FIFO); + } + success &= (pthread_setschedparam(native_handle, SCHED_FIFO, ¶ms) == 0); + if (cpu_id >= 0) { + // Cannot set the runmask if cpu id is greater than or equal to the number of cpus + // so will immediate return + if (cpu_id >= _syspage_ptr->num_cpu) { + return 0; + } + // run_mask is a bit mask to set which cpu a thread runs on + // where each bit corresponds to a cpu core + int64_t run_mask = 0x01; + run_mask <<= cpu_id; + + // Function used to change thread affinity of thread associated with native_handle + if (ThreadCtlExt( + 0, native_handle, _NTO_TCTL_RUNMASK, + reinterpret_cast(run_mask)) == -1) + { + success &= 0; + } else { + success &= 1; + } + } +#else // i.e., Linux platform. + sched_param params; + int policy; + success &= (pthread_getschedparam(native_handle, &policy, ¶ms) == 0); + if (priority == ThreadPriority::HIGH) { + // Should be a value of 49 on standard Linux platforms, which is just below + // the default priority of 50 for threaded interrupt handling. + params.sched_priority = + (sched_get_priority_min(SCHED_FIFO) + sched_get_priority_max(SCHED_FIFO)) / 2 - 1; + } else { + // Choose the lowest priority under SCHED_FIFO. This will still be higher than + // the priority of the DDS threads, which are not changed here. Normally + // the DDS threads will be executed under SCHED_OTHER at nice value 0. + // Note that changing the priority below the default user-space priority requires + // increasing the nice level. This has not been implemented here for two reasons: + // First, the Linux API does not allow to get the Linux-specific thread ID (TID) + // for changing the nice value from an arbitrary pthread_t pointer, but only from the + // current thread by gettid(). Second, a low prio Executor thread under SCHED_OTHER + // would always get 50 ms per second due to RT throttling if not configured + // otherwise. This would be difficult to explain in a demo. + params.sched_priority = sched_get_priority_min(SCHED_FIFO); + } + success &= (pthread_setschedparam(native_handle, SCHED_FIFO, ¶ms) == 0); + if (cpu_id >= 0) { + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(cpu_id, &cpuset); + success &= (pthread_setaffinity_np(native_handle, sizeof(cpu_set_t), &cpuset) == 0); + } +#endif + return success; +} + +/// Sets the priority of the given thread to max or min as given. The exact +/// scheduler priority depends on the operating system. On Linux, this +/// requires elevated privileges. +/// Furthermore, if a non-negative CPU id is given, the thread is pinned +/// to that CPU. +inline bool configure_thread(std::thread & thread, ThreadPriority priority, int cpu_id) +{ + return configure_native_thread(thread.native_handle(), priority, cpu_id); +} + +/// Returns the time of the given native thread handle as std::chrono +/// timestamp. This allows measuring the execution time of this thread. +template +std::chrono::nanoseconds get_native_thread_time(T native_handle) +{ +#ifdef _WIN32 // i.e., Windows platform. + FILETIME creation_filetime; + FILETIME exit_filetime; + FILETIME kernel_filetime; + FILETIME user_filetime; + GetThreadTimes( + native_handle, &creation_filetime, &exit_filetime, &kernel_filetime, &user_filetime); + ULARGE_INTEGER kernel_time; + kernel_time.LowPart = kernel_filetime.dwLowDateTime; + kernel_time.HighPart = kernel_filetime.dwHighDateTime; + ULARGE_INTEGER user_time; + user_time.LowPart = user_filetime.dwLowDateTime; + user_time.HighPart = user_filetime.dwHighDateTime; + std::chrono::nanoseconds t(100); // Unit in FILETIME is 100ns. + t *= (kernel_time.QuadPart + user_time.QuadPart); + return t; +#elif __APPLE__ // i.e., macOS platform. + thread_port_t mach_thread = pthread_mach_thread_np(native_handle); + thread_basic_info_data_t info; + mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT; + std::chrono::nanoseconds t(0); + if (thread_info( + mach_thread, THREAD_BASIC_INFO, reinterpret_cast(&info), + &count) == KERN_SUCCESS) + { + t += std::chrono::seconds(info.user_time.seconds); + t += std::chrono::microseconds(info.user_time.microseconds); + t += std::chrono::seconds(info.system_time.seconds); + t += std::chrono::microseconds(info.system_time.microseconds); + } + return t; +#else // i.e., Linux platform. + clockid_t id; + pthread_getcpuclockid(native_handle, &id); + timespec spec; + clock_gettime(id, &spec); + return std::chrono::seconds{spec.tv_sec} + std::chrono::nanoseconds{spec.tv_nsec}; +#endif +} + +/// Returns the time of the given thread as std::chrono timestamp. +/// This allows measuring the execution time of this thread. +inline std::chrono::nanoseconds get_thread_time(std::thread & thread) +{ + return get_native_thread_time(thread.native_handle()); +} + +/// Returns the time of the current thread as std::chrono timestamp. +/// This allows measuring the execution time of this thread. +inline std::chrono::nanoseconds get_current_thread_time() +{ +#ifdef _WIN32 // i.e., Windows platform. + return get_native_thread_time(GetCurrentThread()); +#elif __APPLE__ // i.e., macOS platform. + return get_native_thread_time(pthread_self()); +#else // i.e., Linux platform. + return get_native_thread_time(pthread_self()); +#endif +} + +/// Calculates the average of the given vector of doubles. +inline double calc_average(const std::vector & v) +{ + double avg = std::accumulate(v.begin(), v.end(), 0.0, std::plus()) / v.size(); + return avg; +} + +/// Calculates the standard deviation of the given vector of doubles. +inline double calc_std_deviation(const std::vector & v) +{ + double mean = calc_average(v); + double sum_squares = 0.0; + for (const double d : v) { + sum_squares += (d - mean) * (d - mean); + } + return std::sqrt(sum_squares / v.size()); +} + +} // namespace examples_rclcpp_cbg_executor + +#endif // EXAMPLES_RCLCPP_CBG_EXECUTOR__UTILITIES_HPP_ diff --git a/src/example/rclcpp/executors/cbg_executor/src/ping.cpp b/src/example/rclcpp/executors/cbg_executor/src/ping.cpp new file mode 100644 index 0000000..99ad487 --- /dev/null +++ b/src/example/rclcpp/executors/cbg_executor/src/ping.cpp @@ -0,0 +1,85 @@ +// Copyright (c) 2020 Robert Bosch GmbH +// +// 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. + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rclcpp/executor.hpp" +#include "rclcpp/rclcpp.hpp" + +#include "examples_rclcpp_cbg_executor/ping_node.hpp" +#include "examples_rclcpp_cbg_executor/utilities.hpp" + +using std::chrono::seconds; +using std::chrono::milliseconds; +using std::chrono::nanoseconds; +using namespace std::chrono_literals; + +using examples_rclcpp_cbg_executor::PingNode; +using examples_rclcpp_cbg_executor::configure_thread; +using examples_rclcpp_cbg_executor::get_thread_time; +using examples_rclcpp_cbg_executor::ThreadPriority; + +/// The main function puts a Ping node in one OS process and runs the +/// experiment. See README.md for an architecture diagram. +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + + // Create one executor within this process. + rclcpp::executors::SingleThreadedExecutor high_prio_executor; + + // Create Ping node instance and add it to high-prio executor. + auto ping_node = std::make_shared(); + high_prio_executor.add_node(ping_node); + + rclcpp::Logger logger = ping_node->get_logger(); + + // Create a thread for the executor ... + auto high_prio_thread = std::thread( + [&]() { + high_prio_executor.spin(); + }); + + // ... and configure it accordinly as high prio and pin it to the first CPU. + const int CPU_ZERO = 0; + bool ret = configure_thread(high_prio_thread, ThreadPriority::HIGH, CPU_ZERO); + if (!ret) { + RCLCPP_WARN(logger, "Failed to configure high priority thread, are you root?"); + } + + const std::chrono::seconds EXPERIMENT_DURATION = 10s; + RCLCPP_INFO_STREAM( + logger, "Running experiment from now on for " << EXPERIMENT_DURATION.count() << " seconds ..."); + std::this_thread::sleep_for(EXPERIMENT_DURATION); + + // ... and stop the experiment. + rclcpp::shutdown(); + high_prio_thread.join(); + + ping_node->print_statistics(EXPERIMENT_DURATION); + + return 0; +} diff --git a/src/example/rclcpp/executors/cbg_executor/src/ping_pong.cpp b/src/example/rclcpp/executors/cbg_executor/src/ping_pong.cpp new file mode 100644 index 0000000..96bc214 --- /dev/null +++ b/src/example/rclcpp/executors/cbg_executor/src/ping_pong.cpp @@ -0,0 +1,125 @@ +// Copyright (c) 2020 Robert Bosch GmbH +// +// 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. + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rclcpp/executor.hpp" +#include "rclcpp/rclcpp.hpp" + +#include "examples_rclcpp_cbg_executor/ping_node.hpp" +#include "examples_rclcpp_cbg_executor/pong_node.hpp" +#include "examples_rclcpp_cbg_executor/utilities.hpp" + +using std::chrono::seconds; +using std::chrono::milliseconds; +using std::chrono::nanoseconds; +using namespace std::chrono_literals; + +using examples_rclcpp_cbg_executor::PingNode; +using examples_rclcpp_cbg_executor::PongNode; +using examples_rclcpp_cbg_executor::configure_thread; +using examples_rclcpp_cbg_executor::get_thread_time; +using examples_rclcpp_cbg_executor::ThreadPriority; + +/// The main function composes a Ping node and a Pong node in one OS process +/// and runs the experiment. See README.md for an architecture diagram. +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + + // Create two executors within this process. + rclcpp::executors::SingleThreadedExecutor high_prio_executor; + rclcpp::executors::SingleThreadedExecutor low_prio_executor; + + // Create Ping node instance and add it to high-prio executor. + auto ping_node = std::make_shared(); + high_prio_executor.add_node(ping_node); + + // Create Pong node instance and add it the one of its callback groups + // to the high-prio executor and the other to the low-prio executor. + auto pong_node = std::make_shared(); + high_prio_executor.add_callback_group( + pong_node->get_high_prio_callback_group(), pong_node->get_node_base_interface()); + low_prio_executor.add_callback_group( + pong_node->get_low_prio_callback_group(), pong_node->get_node_base_interface()); + + rclcpp::Logger logger = pong_node->get_logger(); + + // Create a thread for each of the two executors ... + auto high_prio_thread = std::thread( + [&]() { + high_prio_executor.spin(); + }); + auto low_prio_thread = std::thread( + [&]() { + low_prio_executor.spin(); + }); + + // ... and configure them accordinly as high and low prio and pin them to the + // first CPU. Hence, the two executors compete about this computational resource. + const int CPU_ZERO = 0; + bool ret = configure_thread(high_prio_thread, ThreadPriority::HIGH, CPU_ZERO); + if (!ret) { + RCLCPP_WARN(logger, "Failed to configure high priority thread, are you root?"); + } + ret = configure_thread(low_prio_thread, ThreadPriority::LOW, CPU_ZERO); + if (!ret) { + RCLCPP_WARN(logger, "Failed to configure low priority thread, are you root?"); + } + + // Creating the threads immediately started them. + // Therefore, get start CPU time of each thread now. + nanoseconds high_prio_thread_begin = get_thread_time(high_prio_thread); + nanoseconds low_prio_thread_begin = get_thread_time(low_prio_thread); + + const std::chrono::seconds EXPERIMENT_DURATION = 10s; + RCLCPP_INFO_STREAM( + logger, "Running experiment from now on for " << EXPERIMENT_DURATION.count() << " seconds ..."); + std::this_thread::sleep_for(EXPERIMENT_DURATION); + + // Get end CPU time of each thread ... + nanoseconds high_prio_thread_end = get_thread_time(high_prio_thread); + nanoseconds low_prio_thread_end = get_thread_time(low_prio_thread); + + // ... and stop the experiment. + rclcpp::shutdown(); + high_prio_thread.join(); + low_prio_thread.join(); + + ping_node->print_statistics(EXPERIMENT_DURATION); + + // Print CPU times. + int64_t high_prio_thread_duration_ms = std::chrono::duration_cast( + high_prio_thread_end - high_prio_thread_begin).count(); + int64_t low_prio_thread_duration_ms = std::chrono::duration_cast( + low_prio_thread_end - low_prio_thread_begin).count(); + RCLCPP_INFO( + logger, "High priority executor thread ran for %" PRId64 "ms.", high_prio_thread_duration_ms); + RCLCPP_INFO( + logger, "Low priority executor thread ran for %" PRId64 "ms.", low_prio_thread_duration_ms); + + return 0; +} diff --git a/src/example/rclcpp/executors/cbg_executor/src/pong.cpp b/src/example/rclcpp/executors/cbg_executor/src/pong.cpp new file mode 100644 index 0000000..f99ed5d --- /dev/null +++ b/src/example/rclcpp/executors/cbg_executor/src/pong.cpp @@ -0,0 +1,117 @@ +// Copyright (c) 2020 Robert Bosch GmbH +// +// 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. + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rclcpp/executor.hpp" +#include "rclcpp/rclcpp.hpp" + +#include "examples_rclcpp_cbg_executor/pong_node.hpp" +#include "examples_rclcpp_cbg_executor/utilities.hpp" + +using std::chrono::seconds; +using std::chrono::milliseconds; +using std::chrono::nanoseconds; +using namespace std::chrono_literals; + +using examples_rclcpp_cbg_executor::PongNode; +using examples_rclcpp_cbg_executor::configure_thread; +using examples_rclcpp_cbg_executor::get_thread_time; +using examples_rclcpp_cbg_executor::ThreadPriority; + +/// The main function puts a Pong node in one OS process and runs the +/// experiment. See README.md for an architecture diagram. +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + + // Create two executors within this process. + rclcpp::executors::SingleThreadedExecutor high_prio_executor; + rclcpp::executors::SingleThreadedExecutor low_prio_executor; + + // Create Pong node instance and add it the one of its callback groups + // to the high-prio executor and the other to the low-prio executor. + auto pong_node = std::make_shared(); + high_prio_executor.add_callback_group( + pong_node->get_high_prio_callback_group(), pong_node->get_node_base_interface()); + low_prio_executor.add_callback_group( + pong_node->get_low_prio_callback_group(), pong_node->get_node_base_interface()); + + rclcpp::Logger logger = pong_node->get_logger(); + + // Create a thread for each of the two executors ... + auto high_prio_thread = std::thread( + [&]() { + high_prio_executor.spin(); + }); + auto low_prio_thread = std::thread( + [&]() { + low_prio_executor.spin(); + }); + + // ... and configure them accordinly as high and low prio and pin them to the + // first CPU. Hence, the two executors compete about this computational resource. + const int CPU_ZERO = 0; + bool ret = configure_thread(high_prio_thread, ThreadPriority::HIGH, CPU_ZERO); + if (!ret) { + RCLCPP_WARN(logger, "Failed to configure high priority thread, are you root?"); + } + ret = configure_thread(low_prio_thread, ThreadPriority::LOW, CPU_ZERO); + if (!ret) { + RCLCPP_WARN(logger, "Failed to configure low priority thread, are you root?"); + } + + // Creating the threads immediately started them. + // Therefore, get start CPU time of each thread now. + nanoseconds high_prio_thread_begin = get_thread_time(high_prio_thread); + nanoseconds low_prio_thread_begin = get_thread_time(low_prio_thread); + + const std::chrono::seconds EXPERIMENT_DURATION = 10s; + RCLCPP_INFO_STREAM( + logger, "Running experiment from now on for " << EXPERIMENT_DURATION.count() << " seconds ..."); + std::this_thread::sleep_for(EXPERIMENT_DURATION); + + // Get end CPU time of each thread ... + nanoseconds high_prio_thread_end = get_thread_time(high_prio_thread); + nanoseconds low_prio_thread_end = get_thread_time(low_prio_thread); + + // ... and stop the experiment. + rclcpp::shutdown(); + high_prio_thread.join(); + low_prio_thread.join(); + + // Print CPU times. + int64_t high_prio_thread_duration_ms = std::chrono::duration_cast( + high_prio_thread_end - high_prio_thread_begin).count(); + int64_t low_prio_thread_duration_ms = std::chrono::duration_cast( + low_prio_thread_end - low_prio_thread_begin).count(); + RCLCPP_INFO( + logger, "High priority executor thread ran for %" PRId64 "ms.", high_prio_thread_duration_ms); + RCLCPP_INFO( + logger, "Low priority executor thread ran for %" PRId64 "ms.", low_prio_thread_duration_ms); + + return 0; +} diff --git a/src/example/rclcpp/executors/multithreaded_executor/CHANGELOG.rst b/src/example/rclcpp/executors/multithreaded_executor/CHANGELOG.rst new file mode 100644 index 0000000..8bf9256 --- /dev/null +++ b/src/example/rclcpp/executors/multithreaded_executor/CHANGELOG.rst @@ -0,0 +1,110 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclcpp_multithreaded_executor +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* Update the examples to C++17. (`#353 `_) +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash, Chris Lalancette + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande + +0.13.0 (2021-10-18) +------------------- +* Fix deprecated subscriber callbacks (`#323 `_) +* Contributors: Abrar Rahman Protyasha + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- +* Use `char *` in logging macros (`#295 `_) +* Contributors: Audrow Nash + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- +* Added common linters (`#265 `_) +* Contributors: Alejandro Hernández Cordero + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ +* Initialized count in examples_rclcpp_multithreaded_executor (`#269 `_) +* Contributors: Alejandro Hernández Cordero + +0.9.0 (2020-04-30) +------------------ +* avoid new deprecations (`#267 `_) +* Restructure rclcpp folders (`#264 `_) +* Contributors: Marya Belanger, William Woodall + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ +* Implemented Multithreaded Executor example (`#251 `_) +* Contributors: jhdcs diff --git a/src/example/rclcpp/executors/multithreaded_executor/CMakeLists.txt b/src/example/rclcpp/executors/multithreaded_executor/CMakeLists.txt new file mode 100644 index 0000000..b6a6de4 --- /dev/null +++ b/src/example/rclcpp/executors/multithreaded_executor/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.5) +project(examples_rclcpp_multithreaded_executor) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) + +add_executable(multithreaded_executor multithreaded_executor.cpp) +ament_target_dependencies(multithreaded_executor rclcpp std_msgs) + +install(TARGETS + multithreaded_executor + DESTINATION lib/${PROJECT_NAME} +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/example/rclcpp/executors/multithreaded_executor/multithreaded_executor.cpp b/src/example/rclcpp/executors/multithreaded_executor/multithreaded_executor.cpp new file mode 100644 index 0000000..2414dc3 --- /dev/null +++ b/src/example/rclcpp/executors/multithreaded_executor/multithreaded_executor.cpp @@ -0,0 +1,177 @@ +// Copyright 2020 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. + +#include +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using namespace std::chrono_literals; + +/** + * A small convenience function for converting a thread ID to a string + **/ +std::string string_thread_id() +{ + auto hashed = std::hash()(std::this_thread::get_id()); + return std::to_string(hashed); +} + +/* For this example, we will be creating a publishing node like the one in minimal_publisher. + * We will have a single subscriber node running 2 threads. Each thread loops at different speeds, and + * just repeats what it sees from the publisher to the screen. + */ + +class PublisherNode : public rclcpp::Node +{ +public: + PublisherNode() + : Node("PublisherNode"), count_(0) + { + publisher_ = this->create_publisher("topic", 10); + auto timer_callback = + [this]() -> void { + auto message = std_msgs::msg::String(); + message.data = "Hello World! " + std::to_string(this->count_++); + + // Extract current thread + auto curr_thread = string_thread_id(); + + // Prep display message + RCLCPP_INFO( + this->get_logger(), "\n<> Publishing '%s'", + curr_thread.c_str(), message.data.c_str()); + this->publisher_->publish(message); + }; + timer_ = this->create_wall_timer(500ms, timer_callback); + } + +private: + rclcpp::TimerBase::SharedPtr timer_; + rclcpp::Publisher::SharedPtr publisher_; + size_t count_; +}; + +class DualThreadedNode : public rclcpp::Node +{ +public: + DualThreadedNode() + : Node("DualThreadedNode") + { + /* These define the callback groups + * They don't really do much on their own, but they have to exist in order to + * assign callbacks to them. They're also what the executor looks for when trying to run multiple threads + */ + callback_group_subscriber1_ = this->create_callback_group( + rclcpp::CallbackGroupType::MutuallyExclusive); + callback_group_subscriber2_ = this->create_callback_group( + rclcpp::CallbackGroupType::MutuallyExclusive); + + // Each of these callback groups is basically a thread + // Everything assigned to one of them gets bundled into the same thread + auto sub1_opt = rclcpp::SubscriptionOptions(); + sub1_opt.callback_group = callback_group_subscriber1_; + auto sub2_opt = rclcpp::SubscriptionOptions(); + sub2_opt.callback_group = callback_group_subscriber2_; + + subscription1_ = this->create_subscription( + "topic", + rclcpp::QoS(10), + // std::bind is sort of C++'s way of passing a function + // If you're used to function-passing, skip these comments + std::bind( + &DualThreadedNode::subscriber1_cb, // First parameter is a reference to the function + this, // What the function should be bound to + std::placeholders::_1), // At this point we're not positive of all the + // parameters being passed + // So we just put a generic placeholder + // into the binder + // (since we know we need ONE parameter) + sub1_opt); // This is where we set the callback group. + // This subscription will run with callback group subscriber1 + + subscription2_ = this->create_subscription( + "topic", + rclcpp::QoS(10), + std::bind( + &DualThreadedNode::subscriber2_cb, + this, + std::placeholders::_1), + sub2_opt); + } + +private: + /** + * Simple function for generating a timestamp + * Used for somewhat ineffectually demonstrating that the multithreading doesn't cripple performace + */ + std::string timing_string() + { + rclcpp::Time time = this->now(); + return std::to_string(time.nanoseconds()); + } + + /** + * Every time the Publisher publishes something, all subscribers to the topic get poked + * This function gets called when Subscriber1 is poked (due to the std::bind we used when defining it) + */ + void subscriber1_cb(const std_msgs::msg::String::ConstSharedPtr msg) + { + auto message_received_at = timing_string(); + + // Extract current thread + RCLCPP_INFO( + this->get_logger(), "THREAD %s => Heard '%s' at %s", + string_thread_id().c_str(), msg->data.c_str(), message_received_at.c_str()); + } + + /** + * This function gets called when Subscriber2 is poked + * Since it's running on a separate thread than Subscriber 1, it will run at (more-or-less) the same time! + */ + void subscriber2_cb(const std_msgs::msg::String::ConstSharedPtr msg) + { + auto message_received_at = timing_string(); + + // Prep display message + RCLCPP_INFO( + this->get_logger(), "THREAD %s => Heard '%s' at %s", + string_thread_id().c_str(), msg->data.c_str(), message_received_at.c_str()); + } + + rclcpp::CallbackGroup::SharedPtr callback_group_subscriber1_; + rclcpp::CallbackGroup::SharedPtr callback_group_subscriber2_; + rclcpp::Subscription::SharedPtr subscription1_; + rclcpp::Subscription::SharedPtr subscription2_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + + // You MUST use the MultiThreadedExecutor to use, well, multiple threads + rclcpp::executors::MultiThreadedExecutor executor; + auto pubnode = std::make_shared(); + auto subnode = std::make_shared(); // This contains BOTH subscriber callbacks. + // They will still run on different threads + // One Node. Two callbacks. Two Threads + executor.add_node(pubnode); + executor.add_node(subnode); + executor.spin(); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/executors/multithreaded_executor/package.xml b/src/example/rclcpp/executors/multithreaded_executor/package.xml new file mode 100644 index 0000000..23f43bf --- /dev/null +++ b/src/example/rclcpp/executors/multithreaded_executor/package.xml @@ -0,0 +1,30 @@ + + + + examples_rclcpp_multithreaded_executor + 0.19.6 + Package containing example of how to implement a multithreaded executor + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Jacob Hassold + Shane Loretz + + ament_cmake + + rclcpp + std_msgs + + rclcpp + std_msgs + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/example/rclcpp/services/async_client/CHANGELOG.rst b/src/example/rclcpp/services/async_client/CHANGELOG.rst new file mode 100644 index 0000000..8593320 --- /dev/null +++ b/src/example/rclcpp/services/async_client/CHANGELOG.rst @@ -0,0 +1,130 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclcpp_async_client +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* Update the examples to C++17. (`#353 `_) +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash, Chris Lalancette + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande + +0.13.0 (2021-10-18) +------------------- +* Add example of how to prune old requests in client API (`#322 `_) +* Contributors: Ivan Santiago Paunovic + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- + +0.10.0 (2020-09-21) +------------------- + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ + +0.9.0 (2020-04-30) +------------------ + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ + +0.8.0 (2019-09-26) +------------------ + +0.7.3 (2019-05-29) +------------------ + +0.7.2 (2019-05-20) +------------------ + +0.7.1 (2019-05-08) +------------------ + +0.7.0 (2019-04-14) +------------------ + +0.6.2 (2019-02-08) +------------------ + +0.6.1 (2018-12-07) +------------------ + +0.6.0 (2018-11-20) +------------------ + +0.5.1 (2018-06-27) +------------------ + +0.5.0 (2018-06-26) +------------------ + +0.4.0 (2017-12-08) +------------------ diff --git a/src/example/rclcpp/services/async_client/CMakeLists.txt b/src/example/rclcpp/services/async_client/CMakeLists.txt new file mode 100644 index 0000000..be49a41 --- /dev/null +++ b/src/example/rclcpp/services/async_client/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.5) +project(examples_rclcpp_async_client) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) +find_package(example_interfaces REQUIRED) +find_package(rclcpp REQUIRED) + +add_executable(client_main main.cpp) +ament_target_dependencies(client_main rclcpp example_interfaces) + +install(TARGETS client_main + DESTINATION lib/${PROJECT_NAME}) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/example/rclcpp/services/async_client/main.cpp b/src/example/rclcpp/services/async_client/main.cpp new file mode 100644 index 0000000..8922dd7 --- /dev/null +++ b/src/example/rclcpp/services/async_client/main.cpp @@ -0,0 +1,196 @@ +// Copyright 2016 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. + +#include +#include +#include +#include +#include +#include +#include + +#include "example_interfaces/srv/add_two_ints.hpp" +#include "rclcpp/rclcpp.hpp" + +using AddTwoInts = example_interfaces::srv::AddTwoInts; +using namespace std::chrono_literals; + +class ClientNode : public rclcpp::Node +{ +public: + explicit ClientNode(const rclcpp::NodeOptions & options = rclcpp::NodeOptions{}) + : Node("add_two_ints_async_client", options) + { + setvbuf(stdout, NULL, _IONBF, BUFSIZ); + client_ = create_client("add_two_ints"); + // The client stores all pending requests internally, but those aren't cleaned up + // automatically if the server never responds. + // We create a timer that prunes all old requests each 5s, you can get the request ids + // of the requests that weren't completed here. + timer_ = this->create_wall_timer( + 5s, + [this]() { + std::vector pruned_requests; + // Prune all requests older than 5s. + size_t n_pruned = this->client_->prune_requests_older_than( + std::chrono::system_clock::now() - 5s, &pruned_requests); + if (n_pruned) { + RCLCPP_INFO( + this->get_logger(), + "The server hasn't replied for more than 5s, %zu requests were discarded, " + "the discarded requests numbers are:", + n_pruned); + for (const auto & req_num : pruned_requests) { + RCLCPP_INFO(this->get_logger(), "\t%" PRId64, req_num); + } + } + }); + } + + bool + wait_for_service_server() + { + while (!client_->wait_for_service(std::chrono::seconds(1))) { + if (!rclcpp::ok()) { + RCLCPP_ERROR(this->get_logger(), "client interrupted while waiting for service to appear."); + return false; + } + RCLCPP_INFO(this->get_logger(), "waiting for service to appear..."); + } + return true; + } + + void + queue_async_request(int64_t a, int64_t b) + { + auto request = std::make_shared(); + request->a = a; + request->b = b; + + // We give the async_send_request() method a callback that will get executed once the response + // is received. + // This way we can return immediately from this method and allow other work to be done by the + // executor in `spin` while waiting for the response. + using ServiceResponseFuture = + rclcpp::Client::SharedFutureWithRequest; + auto response_received_callback = + [logger = this->get_logger()](ServiceResponseFuture future) { + auto request_response_pair = future.get(); + RCLCPP_INFO( + logger, + "Result of %" PRId64 " + %" PRId64 " is: %" PRId64, + request_response_pair.first->a, + request_response_pair.first->b, + request_response_pair.second->sum); + }; + auto result = client_->async_send_request( + request, std::move(response_received_callback)); + RCLCPP_INFO( + this->get_logger(), + "Sending a request to the server (request_id =%" PRId64 + "), we're going to let you know the result when ready!", + result.request_id); + } + +private: + rclcpp::Client::SharedPtr client_; + rclcpp::TimerBase::SharedPtr timer_; +}; + +bool +read_more(std::string & buffer, const rclcpp::Logger & logger) +{ + buffer.clear(); + std::getline(std::cin, buffer); + if (std::cin.fail() || std::cin.eof()) { + RCLCPP_INFO(logger, "\nProgram was interrupted, bye!"); + return false; + } + return true; +} + +std::optional +read_number(std::string & buffer, const rclcpp::Logger & logger) +{ + while (1) { + size_t pos; + if (!read_more(buffer, logger)) { + return std::nullopt; + } + if (buffer == "q") { + return std::nullopt; + } + auto ret = std::stoll(buffer, &pos); + if (ret > std::numeric_limits().max()) { + RCLCPP_INFO( + logger, + "The input number should be less or equal than %" PRId64 + "\n Please try again: ", + std::numeric_limits().max()); + continue; + } + if (pos != buffer.size()) { + RCLCPP_INFO( + logger, + "The input should be a number not: %s\n Please try again: ", buffer.c_str()); + continue; + } + return ret; + } +} + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + auto node = std::make_shared(); + const auto & logger = node->get_logger(); + RCLCPP_INFO(logger, "waiting for service to appear..."); + if (!node->wait_for_service_server()) { + return 1; + } + RCLCPP_INFO(logger, "Server is available!!!"); + RCLCPP_INFO(logger, "We are going to add two numbers, insert the first one (or 'q' to exit): "); + int64_t a; + int64_t b; + std::promise stop_async_spinner; + std::thread async_spinner_thread( + [stop_token = stop_async_spinner.get_future(), node]() { + rclcpp::executors::SingleThreadedExecutor executor; + executor.add_node(node); + executor.spin_until_future_complete(stop_token); + }); + while (1) { + std::string buffer; + auto optional_number = read_number(buffer, logger); + if (!optional_number) { + break; + } + a = *optional_number; + RCLCPP_INFO(logger, "Insert the second number (or 'q' to exit): "); + optional_number = read_number(buffer, logger); + if (!optional_number) { + break; + } + b = *optional_number; + node->queue_async_request(a, b); + RCLCPP_INFO( + logger, + "You can prepare another request to add two numbers, insert the first one (or 'q' to exit):" + ); + } + stop_async_spinner.set_value(); + async_spinner_thread.join(); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/services/async_client/package.xml b/src/example/rclcpp/services/async_client/package.xml new file mode 100644 index 0000000..d6db73c --- /dev/null +++ b/src/example/rclcpp/services/async_client/package.xml @@ -0,0 +1,31 @@ + + + + examples_rclcpp_async_client + 0.19.6 + Example of an async service client + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Ivan Paunovic + Shane Loretz + William Woodall + + ament_cmake + + rclcpp + example_interfaces + + rclcpp + example_interfaces + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/example/rclcpp/services/minimal_client/CHANGELOG.rst b/src/example/rclcpp/services/minimal_client/CHANGELOG.rst new file mode 100644 index 0000000..510e091 --- /dev/null +++ b/src/example/rclcpp/services/minimal_client/CHANGELOG.rst @@ -0,0 +1,152 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclcpp_minimal_client +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* Update the examples to C++17. (`#353 `_) +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash, Chris Lalancette + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande + +0.13.0 (2021-10-18) +------------------- +* Add example of how to prune old requests in client API (`#322 `_) +* Contributors: Ivan Santiago Paunovic + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- +* Make sure to include what you use in all examples. (`#284 `_) +* Added common linters (`#265 `_) +* Contributors: Alejandro Hernández Cordero, Chris Lalancette + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ + +0.9.0 (2020-04-30) +------------------ +* avoid new deprecations (`#267 `_) +* Restructure rclcpp folders (`#264 `_) +* Contributors: Marya Belanger, William Woodall + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ + +0.7.3 (2019-05-29) +------------------ + +0.7.2 (2019-05-20) +------------------ + +0.7.1 (2019-05-08) +------------------ + +0.7.0 (2019-04-14) +------------------ + +0.6.2 (2019-02-08) +------------------ + +0.6.0 (2018-11-20) +------------------ +* Added semicolons to all RCLCPP and RCUTILS macros. (`#214 `_) +* Contributors: Chris Lalancette + + +0.5.1 (2018-06-27) +------------------ +* make Mikael Arguedas the maintainer (`#212 `_) +* Contributors: Mikael Arguedas + +0.5.0 (2018-06-26) +------------------ + +0.4.0 (2017-12-08) +------------------ +* Remove node:: namespace (`#192 `_) +* Use logging (`#190 `_) +* 0.0.3 +* call shutdown before exiting (`#179 `_) +* 0.0.2 +* rename executables with shorter names (`#177 `_) +* install executables in package specific path `#173 `_ +* use CMAKE_X_STANDARD and check compiler rather than platform +* use same node_names and service names in cpp and python (`#172 `_) +* add pedantic flag +* use chrono types and literals (`#158 `_) +* c++14 not c++11 (`#157 `_) +* fix `#138 `_ which caused problems on windows because it wasn't cross-platform (`#148 `_) +* Minimal service and client (`#138 `_) +* Contributors: Dirk Thomas, Mikael Arguedas, Morgan Quigley, William Woodall, dhood diff --git a/src/example/rclcpp/services/minimal_client/CMakeLists.txt b/src/example/rclcpp/services/minimal_client/CMakeLists.txt new file mode 100644 index 0000000..ddc4d60 --- /dev/null +++ b/src/example/rclcpp/services/minimal_client/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.5) +project(examples_rclcpp_minimal_client) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) +find_package(example_interfaces REQUIRED) +find_package(rclcpp REQUIRED) + +add_executable(client_main main.cpp) +ament_target_dependencies(client_main rclcpp example_interfaces) + +install(TARGETS client_main + DESTINATION lib/${PROJECT_NAME}) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/example/rclcpp/services/minimal_client/main.cpp b/src/example/rclcpp/services/minimal_client/main.cpp new file mode 100644 index 0000000..34b0ed2 --- /dev/null +++ b/src/example/rclcpp/services/minimal_client/main.cpp @@ -0,0 +1,53 @@ +// Copyright 2016 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. + +#include +#include +#include + +#include "example_interfaces/srv/add_two_ints.hpp" +#include "rclcpp/rclcpp.hpp" + +using AddTwoInts = example_interfaces::srv::AddTwoInts; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + auto node = rclcpp::Node::make_shared("minimal_client"); + auto client = node->create_client("add_two_ints"); + while (!client->wait_for_service(std::chrono::seconds(1))) { + if (!rclcpp::ok()) { + RCLCPP_ERROR(node->get_logger(), "client interrupted while waiting for service to appear."); + return 1; + } + RCLCPP_INFO(node->get_logger(), "waiting for service to appear..."); + } + auto request = std::make_shared(); + request->a = 41; + request->b = 1; + auto result_future = client->async_send_request(request); + if (rclcpp::spin_until_future_complete(node, result_future) != + rclcpp::FutureReturnCode::SUCCESS) + { + RCLCPP_ERROR(node->get_logger(), "service call failed :("); + client->remove_pending_request(result_future); + return 1; + } + auto result = result_future.get(); + RCLCPP_INFO( + node->get_logger(), "result of %" PRId64 " + %" PRId64 " = %" PRId64, + request->a, request->b, result->sum); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/services/minimal_client/package.xml b/src/example/rclcpp/services/minimal_client/package.xml new file mode 100644 index 0000000..a928cfc --- /dev/null +++ b/src/example/rclcpp/services/minimal_client/package.xml @@ -0,0 +1,32 @@ + + + + examples_rclcpp_minimal_client + 0.19.6 + Examples of minimal service clients + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Jacob Perron + Mikael Arguedas + Morgan Quigley + Shane Loretz + + ament_cmake + + rclcpp + example_interfaces + + rclcpp + example_interfaces + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/example/rclcpp/services/minimal_service/CHANGELOG.rst b/src/example/rclcpp/services/minimal_service/CHANGELOG.rst new file mode 100644 index 0000000..135be1c --- /dev/null +++ b/src/example/rclcpp/services/minimal_service/CHANGELOG.rst @@ -0,0 +1,148 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclcpp_minimal_service +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* Update the examples to C++17. (`#353 `_) +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash, Chris Lalancette + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- +* Make sure to include what you use in all examples. (`#284 `_) +* Added common linters (`#265 `_) +* Contributors: Alejandro Hernández Cordero, Chris Lalancette + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ + +0.9.0 (2020-04-30) +------------------ +* Restructure rclcpp folders (`#264 `_) +* Contributors: Marya Belanger + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ + +0.7.3 (2019-05-29) +------------------ + +0.7.2 (2019-05-20) +------------------ + +0.7.1 (2019-05-08) +------------------ + +0.7.0 (2019-04-14) +------------------ + +0.6.2 (2019-02-08) +------------------ + +0.6.0 (2018-11-20) +------------------ +* Added semicolons to all RCLCPP and RCUTILS macros. (`#214 `_) +* Contributors: Chris Lalancette + +0.5.1 (2018-06-27) +------------------ +* make Mikael Arguedas the maintainer (`#212 `_) +* Contributors: Mikael Arguedas + +0.5.0 (2018-06-26) +------------------ + +0.4.0 (2017-12-08) +------------------ +* use global node not local (`#195 `_) +* Use logging (`#190 `_) +* 0.0.3 +* call shutdown before exiting (`#179 `_) +* 0.0.2 +* rename executables with shorter names (`#177 `_) +* install executables in package specific path `#173 `_ +* use CMAKE_X_STANDARD and check compiler rather than platform +* use same node_names and service names in cpp and python (`#172 `_) +* add pedantic flag +* remove unused srv file (`#166 `_) +* c++14 not c++11 (`#157 `_) +* fix `#138 `_ which caused problems on windows because it wasn't cross-platform (`#148 `_) +* Minimal service and client (`#138 `_) +* Contributors: Dirk Thomas, Mikael Arguedas, Morgan Quigley, William Woodall diff --git a/src/example/rclcpp/services/minimal_service/CMakeLists.txt b/src/example/rclcpp/services/minimal_service/CMakeLists.txt new file mode 100644 index 0000000..0ecfe3f --- /dev/null +++ b/src/example/rclcpp/services/minimal_service/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.5) +project(examples_rclcpp_minimal_service) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) +find_package(example_interfaces REQUIRED) +find_package(rclcpp REQUIRED) + +add_executable(service_main main.cpp) +ament_target_dependencies(service_main rclcpp example_interfaces) + +install(TARGETS service_main + DESTINATION lib/${PROJECT_NAME}) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/example/rclcpp/services/minimal_service/README.md b/src/example/rclcpp/services/minimal_service/README.md new file mode 100644 index 0000000..a1b7123 --- /dev/null +++ b/src/example/rclcpp/services/minimal_service/README.md @@ -0,0 +1,3 @@ +# Minimal "addition\_server" cookbook recipes + +This package contains a few examples which show how to create services. diff --git a/src/example/rclcpp/services/minimal_service/main.cpp b/src/example/rclcpp/services/minimal_service/main.cpp new file mode 100644 index 0000000..3cf38fb --- /dev/null +++ b/src/example/rclcpp/services/minimal_service/main.cpp @@ -0,0 +1,45 @@ +// Copyright 2016 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. + +#include +#include + +#include "example_interfaces/srv/add_two_ints.hpp" +#include "rclcpp/rclcpp.hpp" + +using AddTwoInts = example_interfaces::srv::AddTwoInts; +rclcpp::Node::SharedPtr g_node = nullptr; + +void handle_service( + const std::shared_ptr request_header, + const std::shared_ptr request, + const std::shared_ptr response) +{ + (void)request_header; + RCLCPP_INFO( + g_node->get_logger(), + "request: %" PRId64 " + %" PRId64, request->a, request->b); + response->sum = request->a + request->b; +} + +int main(int argc, char ** argv) +{ + rclcpp::init(argc, argv); + g_node = rclcpp::Node::make_shared("minimal_service"); + auto server = g_node->create_service("add_two_ints", handle_service); + rclcpp::spin(g_node); + rclcpp::shutdown(); + g_node = nullptr; + return 0; +} diff --git a/src/example/rclcpp/services/minimal_service/package.xml b/src/example/rclcpp/services/minimal_service/package.xml new file mode 100644 index 0000000..bd434c6 --- /dev/null +++ b/src/example/rclcpp/services/minimal_service/package.xml @@ -0,0 +1,32 @@ + + + + examples_rclcpp_minimal_service + 0.19.6 + A minimal service server which adds two numbers + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Jacob Perron + Mikael Arguedas + Morgan Quigley + Shane Loretz + + ament_cmake + + rclcpp + example_interfaces + + rclcpp + example_interfaces + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/example/rclcpp/timers/minimal_timer/CHANGELOG.rst b/src/example/rclcpp/timers/minimal_timer/CHANGELOG.rst new file mode 100644 index 0000000..a43e013 --- /dev/null +++ b/src/example/rclcpp/timers/minimal_timer/CHANGELOG.rst @@ -0,0 +1,148 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclcpp_minimal_timer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* Update the examples to C++17. (`#353 `_) +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash, Chris Lalancette + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- +* Added common linters (`#265 `_) +* Contributors: Alejandro Hernández Cordero + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ + +0.9.0 (2020-04-30) +------------------ +* Restructure rclcpp folders (`#264 `_) +* Contributors: Marya Belanger + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ + +0.7.3 (2019-05-29) +------------------ + +0.7.2 (2019-05-20) +------------------ + +0.7.1 (2019-05-08) +------------------ +* Avoid deprecated API's by providing history settings (`#240 `_) +* Contributors: William Woodall + +0.7.0 (2019-04-14) +------------------ + +0.6.2 (2019-02-08) +------------------ + +0.6.0 (2018-11-20) +------------------ +* Added semicolons to all RCLCPP and RCUTILS macros. (`#214 `_) +* Contributors: Chris Lalancette + +0.5.1 (2018-06-27) +------------------ +* make Mikael Arguedas the maintainer (`#212 `_) +* Contributors: Mikael Arguedas + +0.5.0 (2018-06-26) +------------------ +* Merge pull request `#198 `_ from youtalk/include-chrono-if-using-chrono-literals + Add #include if using std::chrono_literals +* include chrono if using chrono_literals +* Contributors: Mikael Arguedas, Yutaka Kondo + +0.4.0 (2017-12-08) +------------------ +* Use logging (`#190 `_) +* 0.0.3 +* call shutdown before exiting (`#179 `_) +* 0.0.2 +* rename executables with shorter names (`#177 `_) +* install executables in package specific path `#173 `_ +* use CMAKE_X_STANDARD and check compiler rather than platform +* add pedantic flag +* print hello world from timer callbacks (`#159 `_) +* Contributors: Dirk Thomas, Mikael Arguedas, Morgan Quigley diff --git a/src/example/rclcpp/timers/minimal_timer/CMakeLists.txt b/src/example/rclcpp/timers/minimal_timer/CMakeLists.txt new file mode 100644 index 0000000..4ef7dbf --- /dev/null +++ b/src/example/rclcpp/timers/minimal_timer/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 3.5) +project(examples_rclcpp_minimal_timer) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) + +add_executable(timer_lambda lambda.cpp) +ament_target_dependencies(timer_lambda rclcpp) + +add_executable(timer_member_function member_function.cpp) +ament_target_dependencies(timer_member_function rclcpp) + +install(TARGETS + timer_lambda + timer_member_function + DESTINATION lib/${PROJECT_NAME} +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/example/rclcpp/timers/minimal_timer/README.md b/src/example/rclcpp/timers/minimal_timer/README.md new file mode 100644 index 0000000..6f1e69a --- /dev/null +++ b/src/example/rclcpp/timers/minimal_timer/README.md @@ -0,0 +1,4 @@ +# Minimal timer examples + +This package contains a few different strategies for creating short nodes which have timers. +The `timer_lambda` and `timer_member_function` examples create subclasses of `rclcpp::Node` and set up an `rclcpp::timer` to periodically call functions which just print Hello to the console. They do the same thing, just using different C++ language features. diff --git a/src/example/rclcpp/timers/minimal_timer/lambda.cpp b/src/example/rclcpp/timers/minimal_timer/lambda.cpp new file mode 100644 index 0000000..604a1b7 --- /dev/null +++ b/src/example/rclcpp/timers/minimal_timer/lambda.cpp @@ -0,0 +1,47 @@ +// Copyright 2016 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. + +#include +#include + +#include "rclcpp/rclcpp.hpp" + +using namespace std::chrono_literals; + +/* This example creates a subclass of Node and uses a fancy C++11 lambda + * function to shorten the timer syntax, at the expense of making the + * code somewhat more difficult to understand at first glance if you are + * unaccustomed to C++11 lambda expressions. */ + +class MinimalTimer : public rclcpp::Node +{ +public: + MinimalTimer() + : Node("minimal_timer") + { + auto timer_callback = [this]() -> void {RCLCPP_INFO(this->get_logger(), "Hello, world!");}; + timer_ = create_wall_timer(500ms, timer_callback); + } + +private: + rclcpp::TimerBase::SharedPtr timer_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/timers/minimal_timer/member_function.cpp b/src/example/rclcpp/timers/minimal_timer/member_function.cpp new file mode 100644 index 0000000..6167616 --- /dev/null +++ b/src/example/rclcpp/timers/minimal_timer/member_function.cpp @@ -0,0 +1,49 @@ +// Copyright 2016 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. + +#include +#include + +#include "rclcpp/rclcpp.hpp" + +using namespace std::chrono_literals; + +/* This example creates a subclass of Node and uses std::bind() to register a + * member function as a callback from the timer. */ + +class MinimalTimer : public rclcpp::Node +{ +public: + MinimalTimer() + : Node("minimal_timer") + { + timer_ = create_wall_timer( + 500ms, std::bind(&MinimalTimer::timer_callback, this)); + } + +private: + void timer_callback() + { + RCLCPP_INFO(this->get_logger(), "Hello, world!"); + } + rclcpp::TimerBase::SharedPtr timer_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/timers/minimal_timer/package.xml b/src/example/rclcpp/timers/minimal_timer/package.xml new file mode 100644 index 0000000..8a6be86 --- /dev/null +++ b/src/example/rclcpp/timers/minimal_timer/package.xml @@ -0,0 +1,30 @@ + + + + examples_rclcpp_minimal_timer + 0.19.6 + Examples of minimal nodes which have timers + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Jacob Perron + Mikael Arguedas + Morgan Quigley + Shane Loretz + + ament_cmake + + rclcpp + + rclcpp + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/example/rclcpp/topics/minimal_publisher/CHANGELOG.rst b/src/example/rclcpp/topics/minimal_publisher/CHANGELOG.rst new file mode 100644 index 0000000..180e198 --- /dev/null +++ b/src/example/rclcpp/topics/minimal_publisher/CHANGELOG.rst @@ -0,0 +1,168 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclcpp_minimal_publisher +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- +* wait 5 secs until all subscriptions acknowledge the messages. (`#414 `_) (`#417 `_) + (cherry picked from commit 2c917593d911c0035c354abcff6b1bb9b1aa7ffe) + Co-authored-by: Tomoya Fujita +* Contributors: mergify[bot] + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* Update the examples to C++17. (`#353 `_) +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash, Chris Lalancette + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- +* Add an example about how to use wait_for_all_acked (`#316 `_) +* Contributors: Barry Xu + +0.14.0 (2022-01-14) +------------------- +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- +* Add try&catch statement to unique network flow publisher example (`#313 `_) +* Add type adaption example (`#300 `_) +* Contributors: Audrow Nash, Tomoya Fujita + +0.11.2 (2021-04-26) +------------------- + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- +* Unique network flows (`#296 `_) +* Contributors: Ananya Muddukrishna + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- +* Make sure to include what you use in all examples. (`#284 `_) +* Added common linters (`#265 `_) +* Contributors: Alejandro Hernández Cordero, Chris Lalancette + +0.9.2 (2020-06-01) +------------------ +* Catch possible exception from spin_some (`#266 `_) (`#270 `_) +* Contributors: Tomoya Fujita + +0.9.1 (2020-05-26) +------------------ + +0.9.0 (2020-04-30) +------------------ +* Restructure rclcpp folders (`#264 `_) +* Contributors: Marya Belanger + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ + +0.7.3 (2019-05-29) +------------------ + +0.7.2 (2019-05-20) +------------------ + +0.7.1 (2019-05-08) +------------------ +* Avoid deprecated API's by providing history settings (`#240 `_) +* avoid deprecated publish signature (`#239 `_) +* Contributors: William Woodall + +0.7.0 (2019-04-14) +------------------ + +0.6.2 (2019-02-08) +------------------ + +0.6.0 (2018-11-20) +------------------ +* Added semicolons to all RCLCPP and RCUTILS macros. (`#214 `_) +* Contributors: Chris Lalancette + +0.5.1 (2018-06-27) +------------------ +* make Mikael Arguedas the maintainer (`#212 `_) +* Contributors: Mikael Arguedas + +0.5.0 (2018-06-26) +------------------ +* Add #include if using std::chrono_literals `#198 `_ +* Contributors: Mikael Arguedas, Yutaka Kondo + +0.4.0 (2017-12-08) +------------------ +* Remove node:: namespace (`#192 `_) + connects to `ros2/rclcpp#416 `_ +* Use logging (`#190 `_) +* Switch to using rate (`#188 `_) +* 0.0.3 +* call shutdown before exiting (`#179 `_) +* 0.0.2 +* rename executables with shorter names (`#177 `_) +* install executables in package specific path `#173 `_ +* use CMAKE_X_STANDARD and check compiler rather than platform +* add pedantic flag +* Cpp14 (`#147 `_) + move to C++14 and use standard duration literals +* Minimal service and client (`#138 `_) +* Add examples\_ prefix to package names to avoid future collisions. `#137 `_ +* change talker/listener to minimal_publisher/minimal_subscriber +* Contributors: Dirk Thomas, Mikael Arguedas, Morgan Quigley, dhood diff --git a/src/example/rclcpp/topics/minimal_publisher/CMakeLists.txt b/src/example/rclcpp/topics/minimal_publisher/CMakeLists.txt new file mode 100644 index 0000000..d013600 --- /dev/null +++ b/src/example/rclcpp/topics/minimal_publisher/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 3.5) +project(examples_rclcpp_minimal_publisher) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) + +add_executable(publisher_lambda lambda.cpp) +ament_target_dependencies(publisher_lambda rclcpp std_msgs) + +add_executable(publisher_member_function member_function.cpp) +ament_target_dependencies(publisher_member_function rclcpp std_msgs) + +add_executable(publisher_member_function_with_type_adapter member_function_with_type_adapter.cpp) +ament_target_dependencies(publisher_member_function_with_type_adapter rclcpp std_msgs) + +add_executable(publisher_member_function_with_unique_network_flow_endpoints member_function_with_unique_network_flow_endpoints.cpp) +ament_target_dependencies(publisher_member_function_with_unique_network_flow_endpoints rclcpp std_msgs) + +add_executable(publisher_wait_for_all_acked member_function_with_wait_for_all_acked.cpp) +ament_target_dependencies(publisher_wait_for_all_acked rclcpp std_msgs) + +add_executable(publisher_not_composable not_composable.cpp) +ament_target_dependencies(publisher_not_composable rclcpp std_msgs) + +install(TARGETS + publisher_lambda + publisher_member_function + publisher_member_function_with_type_adapter + publisher_member_function_with_unique_network_flow_endpoints + publisher_wait_for_all_acked + publisher_not_composable + DESTINATION lib/${PROJECT_NAME} +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/example/rclcpp/topics/minimal_publisher/README.md b/src/example/rclcpp/topics/minimal_publisher/README.md new file mode 100644 index 0000000..a8cdda4 --- /dev/null +++ b/src/example/rclcpp/topics/minimal_publisher/README.md @@ -0,0 +1,5 @@ +# Minimal publisher examples + +This package contains a few different strategies for creating short nodes which blast out messages. +The `talker_timer_lambda` and `talker_timer_member_function` recipes create subclasses of `rclcpp::Node` and set up an `rclcpp::timer` to periodically call functions which publish messages. +The `talker_without_subclass` recipe instead instantiates a `rclcpp::Node` object *without* subclassing it, which works, but is not compatible with composition, and thus is no longer the recommended style for ROS 2 coding. diff --git a/src/example/rclcpp/topics/minimal_publisher/lambda.cpp b/src/example/rclcpp/topics/minimal_publisher/lambda.cpp new file mode 100644 index 0000000..324eb96 --- /dev/null +++ b/src/example/rclcpp/topics/minimal_publisher/lambda.cpp @@ -0,0 +1,57 @@ +// Copyright 2016 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. + +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using namespace std::chrono_literals; + +/* This example creates a subclass of Node and uses a fancy C++11 lambda + * function to shorten the callback syntax, at the expense of making the + * code somewhat more difficult to understand at first glance. */ + +class MinimalPublisher : public rclcpp::Node +{ +public: + MinimalPublisher() + : Node("minimal_publisher"), count_(0) + { + publisher_ = this->create_publisher("topic", 10); + auto timer_callback = + [this]() -> void { + auto message = std_msgs::msg::String(); + message.data = "Hello, world! " + std::to_string(this->count_++); + RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str()); + this->publisher_->publish(message); + }; + timer_ = this->create_wall_timer(500ms, timer_callback); + } + +private: + rclcpp::TimerBase::SharedPtr timer_; + rclcpp::Publisher::SharedPtr publisher_; + size_t count_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/topics/minimal_publisher/member_function.cpp b/src/example/rclcpp/topics/minimal_publisher/member_function.cpp new file mode 100644 index 0000000..b211a5e --- /dev/null +++ b/src/example/rclcpp/topics/minimal_publisher/member_function.cpp @@ -0,0 +1,58 @@ +// Copyright 2016 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. + +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using namespace std::chrono_literals; + +/* This example creates a subclass of Node and uses std::bind() to register a + * member function as a callback from the timer. */ + +class MinimalPublisher : public rclcpp::Node +{ +public: + MinimalPublisher() + : Node("minimal_publisher"), count_(0) + { + publisher_ = this->create_publisher("topic", 10); + timer_ = this->create_wall_timer( + 500ms, std::bind(&MinimalPublisher::timer_callback, this)); + } + +private: + void timer_callback() + { + auto message = std_msgs::msg::String(); + message.data = "Hello, world! " + std::to_string(count_++); + RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str()); + publisher_->publish(message); + } + rclcpp::TimerBase::SharedPtr timer_; + rclcpp::Publisher::SharedPtr publisher_; + size_t count_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/topics/minimal_publisher/member_function_with_type_adapter.cpp b/src/example/rclcpp/topics/minimal_publisher/member_function_with_type_adapter.cpp new file mode 100644 index 0000000..39c1007 --- /dev/null +++ b/src/example/rclcpp/topics/minimal_publisher/member_function_with_type_adapter.cpp @@ -0,0 +1,94 @@ +// Copyright 2021 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. + +#include +#include +#include +#include + +#include "rclcpp/type_adapter.hpp" +#include "rclcpp/rclcpp.hpp" + +#include "std_msgs/msg/string.hpp" + +using namespace std::chrono_literals; + +/* Normally a TypeAdapter specialization like this would go in a header + * and be reused by the publisher and subscriber rather than copy-pasted + * like this. We chose to include this here because it makes this example + * more "self-contained". */ + +template<> +struct rclcpp::TypeAdapter +{ + using is_specialized = std::true_type; + using custom_type = std::string; + using ros_message_type = std_msgs::msg::String; + + static + void + convert_to_ros_message( + const custom_type & source, + ros_message_type & destination) + { + destination.data = source; + } + + static + void + convert_to_custom( + const ros_message_type & source, + custom_type & destination) + { + destination = source.data; + } +}; + +/* In this example, a publisher uses a type adapter to use a `std::string` + * in place of a `std_msgs::msg::String` in the argument expected by + * the publish method. Note that publish will also work with a + * `std_msgs::msg::String` argument. */ + +class MinimalPublisher : public rclcpp::Node +{ + using MyAdaptedType = rclcpp::TypeAdapter; + +public: + MinimalPublisher() + : Node("minimal_publisher"), count_(0) + { + publisher_ = this->create_publisher("topic", 10); + timer_ = this->create_wall_timer( + 500ms, std::bind(&MinimalPublisher::timer_callback, this)); + } + +private: + void timer_callback() + { + std::string message = "Hello, world! " + std::to_string(count_++); + RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.c_str()); + publisher_->publish(message); + } + rclcpp::TimerBase::SharedPtr timer_; + rclcpp::Publisher::SharedPtr publisher_; + size_t count_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/topics/minimal_publisher/member_function_with_unique_network_flow_endpoints.cpp b/src/example/rclcpp/topics/minimal_publisher/member_function_with_unique_network_flow_endpoints.cpp new file mode 100644 index 0000000..71437e9 --- /dev/null +++ b/src/example/rclcpp/topics/minimal_publisher/member_function_with_unique_network_flow_endpoints.cpp @@ -0,0 +1,117 @@ +// Copyright 2020 Ericsson AB +// +// 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. + +#include +#include +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "rclcpp/publisher_options.hpp" +#include "std_msgs/msg/string.hpp" + +using namespace std::chrono_literals; + +class MinimalPublisherWithUniqueNetworkFlowEndpoints : public rclcpp::Node +{ +public: + MinimalPublisherWithUniqueNetworkFlowEndpoints() + : Node("minimal_publisher_with_unique_network_flow_endpoints"), count_1_(0), count_2_(0) + { + // Create publisher with unique network flow endpoints + // Enable unique network flow endpoints via options + auto options_1 = rclcpp::PublisherOptions(); + options_1.require_unique_network_flow_endpoints = + RMW_UNIQUE_NETWORK_FLOW_ENDPOINTS_OPTIONALLY_REQUIRED; + publisher_1_ = this->create_publisher("topic_1", 10, options_1); + timer_1_ = this->create_wall_timer( + 500ms, std::bind(&MinimalPublisherWithUniqueNetworkFlowEndpoints::timer_1_callback, this)); + + // Create publisher without unique network flow endpoints + // Unique network flow endpoints are disabled in default options + publisher_2_ = this->create_publisher("topic_2", 10); + timer_2_ = this->create_wall_timer( + 1000ms, std::bind(&MinimalPublisherWithUniqueNetworkFlowEndpoints::timer_2_callback, this)); + + // Catch an exception if implementation does not support get_network_flow_endpoints. + try { + // Get network flow endpoints + auto network_flow_endpoints_1 = publisher_1_->get_network_flow_endpoints(); + auto network_flow_endpoints_2 = publisher_2_->get_network_flow_endpoints(); + + // Print network flow endpoints + print_network_flow_endpoints(network_flow_endpoints_1); + print_network_flow_endpoints(network_flow_endpoints_2); + } catch (const rclcpp::exceptions::RCLError & e) { + RCLCPP_INFO( + this->get_logger(), "%s", e.what()); + } + } + +private: + void timer_1_callback() + { + auto message = std_msgs::msg::String(); + message.data = "Hello, world! " + std::to_string(count_1_++); + + RCLCPP_INFO( + this->get_logger(), "Publishing: '%s'", message.data.c_str()); + publisher_1_->publish(message); + } + void timer_2_callback() + { + auto message = std_msgs::msg::String(); + message.data = "Hej, världen! " + std::to_string(count_2_++); + + RCLCPP_INFO( + this->get_logger(), "Publishing: '%s'", message.data.c_str()); + publisher_2_->publish(message); + } + /// Print network flow endpoints in JSON-like format + void print_network_flow_endpoints( + const std::vector & network_flow_endpoints) const + { + std::ostringstream stream; + stream << "{\"networkFlowEndpoints\": ["; + bool comma_skip = true; + for (auto network_flow_endpoint : network_flow_endpoints) { + if (comma_skip) { + comma_skip = false; + } else { + stream << ","; + } + stream << network_flow_endpoint; + } + stream << "]}"; + RCLCPP_INFO( + this->get_logger(), "%s", + stream.str().c_str()); + } + rclcpp::TimerBase::SharedPtr timer_1_; + rclcpp::TimerBase::SharedPtr timer_2_; + rclcpp::Publisher::SharedPtr publisher_1_; + rclcpp::Publisher::SharedPtr publisher_2_; + size_t count_1_; + size_t count_2_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/topics/minimal_publisher/member_function_with_wait_for_all_acked.cpp b/src/example/rclcpp/topics/minimal_publisher/member_function_with_wait_for_all_acked.cpp new file mode 100644 index 0000000..cde4e43 --- /dev/null +++ b/src/example/rclcpp/topics/minimal_publisher/member_function_with_wait_for_all_acked.cpp @@ -0,0 +1,94 @@ +// Copyright 2021 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. +#include +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using namespace std::chrono_literals; + +/* This example shows how to use wait_for_all_acked for the publisher */ + +class MinimalPublisher : public rclcpp::Node +{ +public: + MinimalPublisher() + : Node("minimal_publisher_with_wait_for_all_acked"), + count_(0), + wait_timeout_(5000) + { + // publisher must set reliable mode + publisher_ = this->create_publisher( + "topic", + rclcpp::QoS(10).reliable()); + + // call wait_for_all_acked before shutdown + using rclcpp::contexts::get_global_default_context; + get_global_default_context()->add_pre_shutdown_callback( + [this]() { + this->timer_->cancel(); + this->wait_for_all_acked(); + }); + + timer_ = this->create_wall_timer( + 500ms, std::bind(&MinimalPublisher::timer_callback, this)); + } + +private: + void wait_for_all_acked() + { + // Confirm all subscribers receive sent messages. + // Note that if no subscription is connected, wait_for_all_acked() always return true. + if (publisher_->wait_for_all_acked(wait_timeout_)) { + RCLCPP_INFO( + this->get_logger(), + "All subscribers acknowledge messages"); + } else { + RCLCPP_INFO( + this->get_logger(), + "Not all subscribers acknowledge messages during %" PRId64 " ms", + static_cast(wait_timeout_.count())); + } + } + + void timer_callback() + { + auto message = std_msgs::msg::String(); + message.data = "Hello, world! " + std::to_string(count_++); + RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str()); + publisher_->publish(message); + + // After sending some messages, you can call wait_for_all_acked() to confirm all subscribers + // acknowledge messages. + } + rclcpp::TimerBase::SharedPtr timer_; + rclcpp::Publisher::SharedPtr publisher_; + size_t count_; + std::chrono::milliseconds wait_timeout_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + + auto publisher = std::make_shared(); + rclcpp::spin(publisher); + rclcpp::shutdown(); + + return 0; +} diff --git a/src/example/rclcpp/topics/minimal_publisher/not_composable.cpp b/src/example/rclcpp/topics/minimal_publisher/not_composable.cpp new file mode 100644 index 0000000..cd565a7 --- /dev/null +++ b/src/example/rclcpp/topics/minimal_publisher/not_composable.cpp @@ -0,0 +1,53 @@ +// Copyright 2016 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. + +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using namespace std::chrono_literals; + +/* We do not recommend this style anymore, because composition of multiple + * nodes in the same executable is not possible. Please see one of the subclass + * examples for the "new" recommended styles. This example is only included + * for completeness because it is similar to "classic" standalone ROS nodes. */ + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + auto node = rclcpp::Node::make_shared("minimal_publisher"); + auto publisher = node->create_publisher("topic", 10); + std_msgs::msg::String message; + auto publish_count = 0; + rclcpp::WallRate loop_rate(500ms); + + while (rclcpp::ok()) { + message.data = "Hello, world! " + std::to_string(publish_count++); + RCLCPP_INFO(node->get_logger(), "Publishing: '%s'", message.data.c_str()); + try { + publisher->publish(message); + rclcpp::spin_some(node); + } catch (const rclcpp::exceptions::RCLError & e) { + RCLCPP_ERROR( + node->get_logger(), + "unexpectedly failed with %s", + e.what()); + } + loop_rate.sleep(); + } + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/topics/minimal_publisher/package.xml b/src/example/rclcpp/topics/minimal_publisher/package.xml new file mode 100644 index 0000000..0a29ee3 --- /dev/null +++ b/src/example/rclcpp/topics/minimal_publisher/package.xml @@ -0,0 +1,32 @@ + + + + examples_rclcpp_minimal_publisher + 0.19.6 + Examples of minimal publisher nodes + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Jacob Perron + Mikael Arguedas + Morgan Quigley + Shane Loretz + + ament_cmake + + rclcpp + std_msgs + + rclcpp + std_msgs + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/example/rclcpp/topics/minimal_subscriber/CHANGELOG.rst b/src/example/rclcpp/topics/minimal_subscriber/CHANGELOG.rst new file mode 100644 index 0000000..9133867 --- /dev/null +++ b/src/example/rclcpp/topics/minimal_subscriber/CHANGELOG.rst @@ -0,0 +1,173 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclcpp_minimal_subscriber +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- +* fix: Fixed compilation after API change of TimerBase::execute (`#375 `_) + Co-authored-by: Janosch Machowinski +* Contributors: jmachowinski + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- +* Split lambda and subscriber def in minimal example (`#363 `_) +* Contributors: Felipe Gomes de Melo + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* Update the examples to C++17. (`#353 `_) +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash, Chris Lalancette + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- +* add ContentFilteredTopic example. (`#341 `_) +* Contributors: Tomoya Fujita + +0.15.0 (2022-03-01) +------------------- +* Use `const&` signature for read-only sub callbacks (`#337 `_) +* Contributors: Abrar Rahman Protyasha + +0.14.0 (2022-01-14) +------------------- +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande + +0.13.0 (2021-10-18) +------------------- +* Fix deprecated subscriber callbacks (`#323 `_) +* Contributors: Abrar Rahman Protyasha + +0.12.0 (2021-08-05) +------------------- +* Add wait set examples (`#315 `_) +* Add type adaption example (`#300 `_) +* Contributors: Audrow Nash, carlossvg + +0.11.2 (2021-04-26) +------------------- + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- +* Unique network flows (`#296 `_) +* Contributors: Ananya Muddukrishna + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- +* Make sure to include what you use in all examples. (`#284 `_) +* Remove a TODO in the not_composable demo. (`#285 `_) +* Add Topic Statistics Example (`#281 `_) +* Added common linters (`#265 `_) +* Contributors: Alejandro Hernández Cordero, Chris Lalancette, Devin Bonnie + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ + +0.9.0 (2020-04-30) +------------------ +* Restructure rclcpp folders (`#264 `_) +* Contributors: Marya Belanger + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ + +0.7.3 (2019-05-29) +------------------ + +0.7.2 (2019-05-20) +------------------ + +0.7.1 (2019-05-08) +------------------ +* Avoid deprecated API's by providing history settings (`#240 `_) +* Contributors: William Woodall + +0.7.0 (2019-04-14) +------------------ + +0.6.2 (2019-02-08) +------------------ +* Updated documentation. (`#225 `_) +* Contributors: Dirk Thomas + +0.6.0 (2018-11-20) +------------------ +* Added semicolons to all RCLCPP and RCUTILS macros. (`#214 `_) +* Contributors: Chris Lalancette + +0.5.1 (2018-06-27) +------------------ +* make Mikael Arguedas the maintainer (`#212 `_) +* Contributors: Mikael Arguedas + +0.5.0 (2018-06-26) +------------------ +* Change the not_composable example to destroy subscription first. (`#210 `_) +* Contributors: Chris Lalancette + +0.4.0 (2017-12-08) +------------------ +* use global node not local (`#195 `_) +* Use logging (`#190 `_) +* 0.0.3 +* call shutdown before exiting (`#179 `_) +* 0.0.2 +* rename executables with shorter names (`#177 `_) +* install executables in package specific path `#173 `_ +* use CMAKE_X_STANDARD and check compiler rather than platform +* add pedantic flag +* Cpp14 (`#147 `_) +* Minimal service and client (`#138 `_) +* Add examples\_ prefix to package names to avoid future collisions. (`#137 `_) +* attempt to improve indentation which includes a c++11 lambda +* fix cmake indentation and c++11 flag ordering +* change talker/listener to minimal_publisher/minimal_subscriber +* Contributors: Dirk Thomas, Mikael Arguedas, Morgan Quigley diff --git a/src/example/rclcpp/topics/minimal_subscriber/CMakeLists.txt b/src/example/rclcpp/topics/minimal_subscriber/CMakeLists.txt new file mode 100644 index 0000000..125b51a --- /dev/null +++ b/src/example/rclcpp/topics/minimal_subscriber/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 3.5) +project(examples_rclcpp_minimal_subscriber) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(rclcpp_components REQUIRED) +find_package(std_msgs REQUIRED) + +add_executable(subscriber_lambda lambda.cpp) +ament_target_dependencies(subscriber_lambda rclcpp std_msgs) + +add_executable(subscriber_member_function member_function.cpp) +ament_target_dependencies(subscriber_member_function rclcpp std_msgs) + +add_executable(subscriber_member_function_with_topic_statistics member_function_with_topic_statistics.cpp) +ament_target_dependencies(subscriber_member_function_with_topic_statistics rclcpp std_msgs) + +add_executable(subscriber_member_function_with_type_adapter member_function_with_type_adapter.cpp) +ament_target_dependencies(subscriber_member_function_with_type_adapter rclcpp std_msgs) + +add_executable(subscriber_member_function_with_unique_network_flow_endpoints member_function_with_unique_network_flow_endpoints.cpp) +ament_target_dependencies(subscriber_member_function_with_unique_network_flow_endpoints rclcpp std_msgs) + +add_executable(subscriber_not_composable not_composable.cpp) +ament_target_dependencies(subscriber_not_composable rclcpp std_msgs) + +add_executable(subscriber_content_filtering content_filtering.cpp) +ament_target_dependencies(subscriber_content_filtering rclcpp std_msgs) + +add_library(wait_set_subscriber_library SHARED + wait_set_subscriber.cpp + static_wait_set_subscriber.cpp + time_triggered_wait_set_subscriber.cpp) +ament_target_dependencies(wait_set_subscriber_library rclcpp rclcpp_components std_msgs) + +rclcpp_components_register_node(wait_set_subscriber_library + PLUGIN "WaitSetSubscriber" + EXECUTABLE wait_set_subscriber) + +rclcpp_components_register_node(wait_set_subscriber_library + PLUGIN "StaticWaitSetSubscriber" + EXECUTABLE static_wait_set_subscriber) + +rclcpp_components_register_node(wait_set_subscriber_library + PLUGIN "TimeTriggeredWaitSetSubscriber" + EXECUTABLE time_triggered_wait_set_subscriber) + +install(TARGETS + wait_set_subscriber_library + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin) + +install(TARGETS + subscriber_lambda + subscriber_member_function + subscriber_member_function_with_topic_statistics + subscriber_member_function_with_type_adapter + subscriber_member_function_with_unique_network_flow_endpoints + subscriber_not_composable + subscriber_content_filtering + DESTINATION lib/${PROJECT_NAME}) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/example/rclcpp/topics/minimal_subscriber/README.md b/src/example/rclcpp/topics/minimal_subscriber/README.md new file mode 100644 index 0000000..fb03cf6 --- /dev/null +++ b/src/example/rclcpp/topics/minimal_subscriber/README.md @@ -0,0 +1,25 @@ +# Minimal subscriber cookbook recipes + +This package contains a few different strategies for creating nodes which receive messages: + * `lambda.cpp` uses a C++11 lambda function + * `member_function.cpp` uses a C++ member function callback + * `not_composable.cpp` uses a global function callback without a Node subclass + * `wait_set_subscriber.cpp` uses a `rclcpp::WaitSet` to wait and poll for data + * `static_wait_set_subscriber.cpp` uses a `rclcpp::StaticWaitSet` to wait and poll for data + * `time_triggered_wait_set_subscriber.cpp` uses a `rclcpp::Waitset` and a timer to poll for data + periodically + * `content_filtering.cpp` uses the content filtering feature for Subscriptions + +Note that `not_composable.cpp` instantiates a `rclcpp::Node` _without_ subclassing it. +This was the typical usage model in ROS 1, but this style of coding is not compatible with composing multiple nodes into a single process. +Thus, it is no longer the recommended style for ROS 2. + +All of these nodes do the same thing: they create a node called `minimal_subscriber` and subscribe to a topic named `topic` which is of datatype `std_msgs/String`. +When a message arrives on that topic, the node prints it to the screen. +We provide multiple examples of different coding styles which achieve this behavior in order to demonstrate that there are many ways to do this in ROS 2. + +The following examples `wait_set_subscriber.cpp`, `static_wait_set_subscriber.cpp` and `time_triggered_wait_set_subscriber.cpp` show how to use a subscription in a node using a `rclcpp` wait-set. +This is not a common use case in ROS 2 so this is not the recommended strategy to use by-default. +This strategy makes sense in some specific situations, for example when the developer needs to have more control over callback order execution, to create custom triggering conditions or to use the timeouts provided by the wait-sets. + +The example `content_filtering.cpp` shows how to use the content filtering feature for Subscriptions. diff --git a/src/example/rclcpp/topics/minimal_subscriber/content_filtering.cpp b/src/example/rclcpp/topics/minimal_subscriber/content_filtering.cpp new file mode 100644 index 0000000..74de76e --- /dev/null +++ b/src/example/rclcpp/topics/minimal_subscriber/content_filtering.cpp @@ -0,0 +1,103 @@ +// Copyright 2022 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. + +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using std::placeholders::_1; + +class MinimalContentFilteringSubscriber : public rclcpp::Node +{ +public: + MinimalContentFilteringSubscriber() + : Node("minimal_contentfiltering_subscriber"), + current_filtering_expression_("data = %0"), + current_expression_parameter_("'Hello, world! 1'"), + count_(10) + { + rclcpp::SubscriptionOptions sub_options; + current_expression_parameter_ = "'Hello, world! " + std::to_string(count_) + "'"; + sub_options.content_filter_options.filter_expression = current_filtering_expression_; + sub_options.content_filter_options.expression_parameters = { + current_expression_parameter_ + }; + + subscription_ = this->create_subscription( + "topic", 10, std::bind(&MinimalContentFilteringSubscriber::topic_callback, this, _1), + sub_options); + + if (!subscription_->is_cft_enabled()) { + RCLCPP_WARN( + this->get_logger(), "Content filter is not enabled since it's not supported"); + } else { + RCLCPP_INFO( + this->get_logger(), + "Subscribed to topic \"%s\" with content filtering", subscription_->get_topic_name()); + print_expression_parameter(); + } + } + +private: + void topic_callback(const std_msgs::msg::String & msg) + { + RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str()); + // update filtering expression parameter + if (subscription_->is_cft_enabled()) { + count_ = count_ + 10; + current_expression_parameter_ = "'Hello, world! " + std::to_string(count_) + "'"; + try { + subscription_->set_content_filter( + current_filtering_expression_, {current_expression_parameter_} + ); + } catch (const std::exception & e) { + RCLCPP_ERROR(this->get_logger(), "%s", e.what()); + } + } + print_expression_parameter(); + } + + void print_expression_parameter(void) const + { + // print filtering expression and parameter stored in subscription + if (subscription_->is_cft_enabled()) { + rclcpp::ContentFilterOptions options; + try { + options = subscription_->get_content_filter(); + RCLCPP_INFO( + this->get_logger(), + "Content filtering expression and parameter are \"%s\" and \"%s\"", + options.filter_expression.c_str(), options.expression_parameters[0].c_str()); + } catch (const std::exception & e) { + RCLCPP_ERROR(this->get_logger(), "%s", e.what()); + } + } + } + + rclcpp::Subscription::SharedPtr subscription_; + std::string current_filtering_expression_; + std::string current_expression_parameter_; + size_t count_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/topics/minimal_subscriber/lambda.cpp b/src/example/rclcpp/topics/minimal_subscriber/lambda.cpp new file mode 100644 index 0000000..16b9083 --- /dev/null +++ b/src/example/rclcpp/topics/minimal_subscriber/lambda.cpp @@ -0,0 +1,44 @@ +// Copyright 2016 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. + +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +class MinimalSubscriber : public rclcpp::Node +{ +public: + MinimalSubscriber() + : Node("minimal_subscriber") + { + auto topic_callback = + [this](std_msgs::msg::String::UniquePtr msg) -> void { + RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str()); + }; + subscription_ = + this->create_subscription("topic", 10, topic_callback); + } + +private: + rclcpp::Subscription::SharedPtr subscription_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/topics/minimal_subscriber/member_function.cpp b/src/example/rclcpp/topics/minimal_subscriber/member_function.cpp new file mode 100644 index 0000000..40f27f7 --- /dev/null +++ b/src/example/rclcpp/topics/minimal_subscriber/member_function.cpp @@ -0,0 +1,47 @@ +// Copyright 2016 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. + +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using std::placeholders::_1; + +class MinimalSubscriber : public rclcpp::Node +{ +public: + MinimalSubscriber() + : Node("minimal_subscriber") + { + subscription_ = this->create_subscription( + "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1)); + } + +private: + void topic_callback(const std_msgs::msg::String & msg) const + { + RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str()); + } + rclcpp::Subscription::SharedPtr subscription_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/topics/minimal_subscriber/member_function_with_topic_statistics.cpp b/src/example/rclcpp/topics/minimal_subscriber/member_function_with_topic_statistics.cpp new file mode 100644 index 0000000..c7523ba --- /dev/null +++ b/src/example/rclcpp/topics/minimal_subscriber/member_function_with_topic_statistics.cpp @@ -0,0 +1,61 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// 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. + +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "rclcpp/subscription_options.hpp" + +#include "std_msgs/msg/string.hpp" + +class MinimalSubscriberWithTopicStatistics : public rclcpp::Node +{ +public: + MinimalSubscriberWithTopicStatistics() + : Node("minimal_subscriber_with_topic_statistics") + { + // manually enable topic statistics via options + auto options = rclcpp::SubscriptionOptions(); + options.topic_stats_options.state = rclcpp::TopicStatisticsState::Enable; + + // configure the collection window and publish period (default 1s) + options.topic_stats_options.publish_period = std::chrono::seconds(10); + + // configure the topic name (default '/statistics') + // options.topic_stats_options.publish_topic = "/topic_statistics" + + auto callback = [this](const std_msgs::msg::String & msg) { + this->topic_callback(msg); + }; + + subscription_ = this->create_subscription( + "topic", 10, callback, options); + } + +private: + void topic_callback(const std_msgs::msg::String & msg) const + { + RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str()); + } + rclcpp::Subscription::SharedPtr subscription_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/topics/minimal_subscriber/member_function_with_type_adapter.cpp b/src/example/rclcpp/topics/minimal_subscriber/member_function_with_type_adapter.cpp new file mode 100644 index 0000000..bb9c892 --- /dev/null +++ b/src/example/rclcpp/topics/minimal_subscriber/member_function_with_type_adapter.cpp @@ -0,0 +1,86 @@ +// Copyright 2021 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. + +#include +#include +#include + +#include "rclcpp/type_adapter.hpp" +#include "rclcpp/rclcpp.hpp" + +#include "std_msgs/msg/string.hpp" + +using std::placeholders::_1; + +/* Normally a TypeAdapter specialization like this would go in a header + * and be reused by the publisher and subscriber rather than copy-pasted + * like this. We chose to include this here because it makes this example + * more "self-contained". */ + +template<> +struct rclcpp::TypeAdapter +{ + using is_specialized = std::true_type; + using custom_type = std::string; + using ros_message_type = std_msgs::msg::String; + + static + void + convert_to_ros_message( + const custom_type & source, + ros_message_type & destination) + { + destination.data = source; + } + + static + void + convert_to_custom( + const ros_message_type & source, + custom_type & destination) + { + destination = source.data; + } +}; + +/* In this example, a subscriber uses a type adapter to use a `std::string` + * in place of a `std_msgs::msg::String` in the subscription's callback. */ + +class MinimalSubscriber : public rclcpp::Node +{ + using MyAdaptedType = rclcpp::TypeAdapter; + +public: + MinimalSubscriber() + : Node("minimal_subscriber") + { + subscription_ = this->create_subscription( + "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1)); + } + +private: + void topic_callback(const std::string & msg) const + { + RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.c_str()); + } + rclcpp::Subscription::SharedPtr subscription_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/topics/minimal_subscriber/member_function_with_unique_network_flow_endpoints.cpp b/src/example/rclcpp/topics/minimal_subscriber/member_function_with_unique_network_flow_endpoints.cpp new file mode 100644 index 0000000..2ee425b --- /dev/null +++ b/src/example/rclcpp/topics/minimal_subscriber/member_function_with_unique_network_flow_endpoints.cpp @@ -0,0 +1,120 @@ +// Copyright 2020 Ericsson AB +// +// 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. + + +#include +#include +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "rclcpp/subscription_options.hpp" +#include "std_msgs/msg/string.hpp" + +using std::placeholders::_1; + +class MinimalSubscriberWithUniqueNetworkFlowEndpoints : public rclcpp::Node +{ +public: + MinimalSubscriberWithUniqueNetworkFlowEndpoints() + : Node("minimal_subscriber_with_unique_network_flow_endpoints") + { + try { + // Create subscription with unique network flow endpoints + // Enable unique network flow endpoints via options + // Since option is strict, expect exception + auto options_1 = rclcpp::SubscriptionOptions(); + options_1.require_unique_network_flow_endpoints = + RMW_UNIQUE_NETWORK_FLOW_ENDPOINTS_STRICTLY_REQUIRED; + + subscription_1_ = this->create_subscription( + "topic_1", 10, std::bind( + &MinimalSubscriberWithUniqueNetworkFlowEndpoints::topic_1_callback, this, + _1), options_1); + + // Create subscription without unique network flow endpoints + // Unique network flow endpoints are disabled by default + auto options_2 = rclcpp::SubscriptionOptions(); + subscription_2_ = this->create_subscription( + "topic_2", 10, std::bind( + &MinimalSubscriberWithUniqueNetworkFlowEndpoints::topic_2_callback, this, + _1), options_2); + + // Get network flow endpoints + auto network_flow_endpoints_1 = subscription_1_->get_network_flow_endpoints(); + auto network_flow_endpoints_2 = subscription_2_->get_network_flow_endpoints(); + + // Check if network flow endpoints are unique + for (auto network_flow_endpoint_1 : network_flow_endpoints_1) { + for (auto network_flow_endpoint_2 : network_flow_endpoints_2) { + if (network_flow_endpoint_1 == network_flow_endpoint_2) { + RCLCPP_ERROR( + this->get_logger(), "Network flow endpoints across subscriptions are not unique"); + break; + } + } + } + + // Print network flow endpoints + print_network_flow_endpoints(network_flow_endpoints_1); + print_network_flow_endpoints(network_flow_endpoints_2); + } catch (const rclcpp::exceptions::RCLError & e) { + RCLCPP_ERROR( + this->get_logger(), + "Error: %s", + e.what()); + } + } + +private: + void topic_1_callback(const std_msgs::msg::String & msg) const + { + RCLCPP_INFO(this->get_logger(), "Topic 1 news: '%s'", msg.data.c_str()); + } + void topic_2_callback(const std_msgs::msg::String & msg) const + { + RCLCPP_INFO(this->get_logger(), "Topic 2 news: '%s'", msg.data.c_str()); + } + /// Print network flow endpoints in JSON-like format + void print_network_flow_endpoints( + const std::vector & network_flow_endpoints) const + { + std::ostringstream stream; + stream << "{\"networkFlowEndpoints\": ["; + bool comma_skip = true; + for (auto network_flow_endpoint : network_flow_endpoints) { + if (comma_skip) { + comma_skip = false; + } else { + stream << ","; + } + stream << network_flow_endpoint; + } + stream << "]}"; + RCLCPP_INFO( + this->get_logger(), "%s", + stream.str().c_str()); + } + rclcpp::Subscription::SharedPtr subscription_1_; + rclcpp::Subscription::SharedPtr subscription_2_; +}; + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::spin(std::make_shared()); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/topics/minimal_subscriber/not_composable.cpp b/src/example/rclcpp/topics/minimal_subscriber/not_composable.cpp new file mode 100644 index 0000000..176f83a --- /dev/null +++ b/src/example/rclcpp/topics/minimal_subscriber/not_composable.cpp @@ -0,0 +1,39 @@ +// Copyright 2016 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. + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +rclcpp::Node::SharedPtr g_node = nullptr; + +/* We do not recommend this style anymore, because composition of multiple + * nodes in the same executable is not possible. Please see one of the subclass + * examples for the "new" recommended styles. This example is only included + * for completeness because it is similar to "classic" standalone ROS nodes. */ + +void topic_callback(const std_msgs::msg::String & msg) +{ + RCLCPP_INFO(g_node->get_logger(), "I heard: '%s'", msg.data.c_str()); +} + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + g_node = rclcpp::Node::make_shared("minimal_subscriber"); + auto subscription = + g_node->create_subscription("topic", 10, topic_callback); + rclcpp::spin(g_node); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/topics/minimal_subscriber/package.xml b/src/example/rclcpp/topics/minimal_subscriber/package.xml new file mode 100644 index 0000000..c80bb8f --- /dev/null +++ b/src/example/rclcpp/topics/minimal_subscriber/package.xml @@ -0,0 +1,34 @@ + + + + examples_rclcpp_minimal_subscriber + 0.19.6 + Examples of minimal subscribers + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Jacob Perron + Mikael Arguedas + Morgan Quigley + Shane Loretz + + ament_cmake + + rclcpp + rclcpp_components + std_msgs + + rclcpp + rclcpp_components + std_msgs + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/example/rclcpp/topics/minimal_subscriber/static_wait_set_subscriber.cpp b/src/example/rclcpp/topics/minimal_subscriber/static_wait_set_subscriber.cpp new file mode 100644 index 0000000..d72c7ab --- /dev/null +++ b/src/example/rclcpp/topics/minimal_subscriber/static_wait_set_subscriber.cpp @@ -0,0 +1,96 @@ +// Copyright 2021, Apex.AI 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. + +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +/* This example creates a subclass of Node and uses static a wait-set based loop to wait on + * a subscription to have messages available and then handles them manually without an executor */ + +class StaticWaitSetSubscriber : public rclcpp::Node +{ + using MyStaticWaitSet = rclcpp::StaticWaitSet<1, 0, 0, 0, 0, 0>; + +public: + explicit StaticWaitSetSubscriber(rclcpp::NodeOptions options) + : Node("static_wait_set_subscriber", options), + subscription_( + [this]() + { + // create subscription with a callback-group not added to the executor + rclcpp::CallbackGroup::SharedPtr cb_group_waitset = this->create_callback_group( + rclcpp::CallbackGroupType::MutuallyExclusive, false); + auto subscription_options = rclcpp::SubscriptionOptions(); + subscription_options.callback_group = cb_group_waitset; + auto subscription_callback = [this](std_msgs::msg::String::UniquePtr msg) { + RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str()); + }; + return this->create_subscription( + "topic", + 10, + subscription_callback, + subscription_options); + } () + ), + wait_set_(std::array{{{subscription_}}}), + thread_(std::thread([this]() -> void {spin_wait_set();})) + { + } + + ~StaticWaitSetSubscriber() + { + if (thread_.joinable()) { + thread_.join(); + } + } + + void spin_wait_set() + { + while (rclcpp::ok()) { + // Wait for the subscriber event to trigger. Set a 1 ms margin to trigger a timeout. + const auto wait_result = wait_set_.wait(std::chrono::milliseconds(501)); + switch (wait_result.kind()) { + case rclcpp::WaitResultKind::Ready: + { + std_msgs::msg::String msg; + rclcpp::MessageInfo msg_info; + if (subscription_->take(msg, msg_info)) { + std::shared_ptr type_erased_msg = std::make_shared(msg); + subscription_->handle_message(type_erased_msg, msg_info); + } + break; + } + case rclcpp::WaitResultKind::Timeout: + if (rclcpp::ok()) { + RCLCPP_WARN(this->get_logger(), "Timeout. No message received after given wait-time"); + } + break; + default: + RCLCPP_ERROR(this->get_logger(), "Error. Wait-set failed."); + } + } + } + +private: + rclcpp::Subscription::SharedPtr subscription_; + MyStaticWaitSet wait_set_; + std::thread thread_; +}; + +#include "rclcpp_components/register_node_macro.hpp" + +RCLCPP_COMPONENTS_REGISTER_NODE(StaticWaitSetSubscriber) diff --git a/src/example/rclcpp/topics/minimal_subscriber/time_triggered_wait_set_subscriber.cpp b/src/example/rclcpp/topics/minimal_subscriber/time_triggered_wait_set_subscriber.cpp new file mode 100644 index 0000000..47e4dc7 --- /dev/null +++ b/src/example/rclcpp/topics/minimal_subscriber/time_triggered_wait_set_subscriber.cpp @@ -0,0 +1,100 @@ +// Copyright 2021, Apex.AI 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. + +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using namespace std::chrono_literals; + +/* This example creates a subclass of Node and uses a wait-set based loop to periodically poll + * for messages in a timer callback using a wait-set based loop. */ + +class TimeTriggeredWaitSetSubscriber : public rclcpp::Node +{ +public: + explicit TimeTriggeredWaitSetSubscriber(rclcpp::NodeOptions options) + : Node("time_triggered_wait_set_subscriber", options) + { + rclcpp::CallbackGroup::SharedPtr cb_group_waitset = this->create_callback_group( + rclcpp::CallbackGroupType::MutuallyExclusive, false); + + auto subscription_options = rclcpp::SubscriptionOptions(); + subscription_options.callback_group = cb_group_waitset; + auto subscription_callback = [this](std_msgs::msg::String::UniquePtr msg) { + RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str()); + }; + subscription_ = this->create_subscription( + "topic", + 10, + subscription_callback, + subscription_options); + auto timer_callback = [this]() -> void { + std_msgs::msg::String msg; + rclcpp::MessageInfo msg_info; + if (subscription_->take(msg, msg_info)) { + std::shared_ptr type_erased_msg = std::make_shared(msg); + subscription_->handle_message(type_erased_msg, msg_info); + } else { + RCLCPP_WARN(this->get_logger(), "No message available"); + } + }; + timer_ = create_wall_timer(500ms, timer_callback, cb_group_waitset); + wait_set_.add_timer(timer_); + thread_ = std::thread([this]() -> void {spin_wait_set();}); + } + + ~TimeTriggeredWaitSetSubscriber() + { + thread_.join(); + } + + void spin_wait_set() + { + while (rclcpp::ok()) { + // Wait for the timer event to trigger. Set a 1 ms margin to trigger a timeout. + const auto wait_result = wait_set_.wait(501ms); + switch (wait_result.kind()) { + case rclcpp::WaitResultKind::Ready: + { + if (wait_result.get_wait_set().get_rcl_wait_set().timers[0U]) { + if (auto data = timer_->call()) { + timer_->execute_callback(data); + } + } + break; + } + case rclcpp::WaitResultKind::Timeout: + if (rclcpp::ok()) { + RCLCPP_WARN(this->get_logger(), "Timeout. No message received after given wait-time"); + } + break; + default: + RCLCPP_ERROR(this->get_logger(), "Error. Wait-set failed."); + } + } + } + +private: + rclcpp::Subscription::SharedPtr subscription_; + rclcpp::TimerBase::SharedPtr timer_; + rclcpp::WaitSet wait_set_; + std::thread thread_; +}; + +#include "rclcpp_components/register_node_macro.hpp" + +RCLCPP_COMPONENTS_REGISTER_NODE(TimeTriggeredWaitSetSubscriber) diff --git a/src/example/rclcpp/topics/minimal_subscriber/wait_set_subscriber.cpp b/src/example/rclcpp/topics/minimal_subscriber/wait_set_subscriber.cpp new file mode 100644 index 0000000..79dddce --- /dev/null +++ b/src/example/rclcpp/topics/minimal_subscriber/wait_set_subscriber.cpp @@ -0,0 +1,88 @@ +// Copyright 2021, Apex.AI 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. + +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +/* This example creates a subclass of Node and uses a wait-set based loop to wait on + * a subscription to have messages available and then handles them manually without an executor */ + +class WaitSetSubscriber : public rclcpp::Node +{ +public: + explicit WaitSetSubscriber(rclcpp::NodeOptions options) + : Node("wait_set_subscriber", options) + { + rclcpp::CallbackGroup::SharedPtr cb_group_waitset = this->create_callback_group( + rclcpp::CallbackGroupType::MutuallyExclusive, false); + auto subscription_options = rclcpp::SubscriptionOptions(); + subscription_options.callback_group = cb_group_waitset; + auto subscription_callback = [this](std_msgs::msg::String::UniquePtr msg) { + RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str()); + }; + subscription_ = this->create_subscription( + "topic", + 10, + subscription_callback, + subscription_options); + wait_set_.add_subscription(subscription_); + thread_ = std::thread([this]() -> void {spin_wait_set();}); + } + + ~WaitSetSubscriber() + { + if (thread_.joinable()) { + thread_.join(); + } + } + + void spin_wait_set() + { + while (rclcpp::ok()) { + // Wait for the subscriber event to trigger. Set a 1 ms margin to trigger a timeout. + const auto wait_result = wait_set_.wait(std::chrono::milliseconds(501)); + switch (wait_result.kind()) { + case rclcpp::WaitResultKind::Ready: + { + std_msgs::msg::String msg; + rclcpp::MessageInfo msg_info; + if (subscription_->take(msg, msg_info)) { + std::shared_ptr type_erased_msg = std::make_shared(msg); + subscription_->handle_message(type_erased_msg, msg_info); + } + break; + } + case rclcpp::WaitResultKind::Timeout: + if (rclcpp::ok()) { + RCLCPP_WARN(this->get_logger(), "Timeout. No message received after given wait-time"); + } + break; + default: + RCLCPP_ERROR(this->get_logger(), "Error. Wait-set failed."); + } + } + } + +private: + rclcpp::Subscription::SharedPtr subscription_; + rclcpp::WaitSet wait_set_; + std::thread thread_; +}; + +#include "rclcpp_components/register_node_macro.hpp" + +RCLCPP_COMPONENTS_REGISTER_NODE(WaitSetSubscriber) diff --git a/src/example/rclcpp/wait_set/CHANGELOG.rst b/src/example/rclcpp/wait_set/CHANGELOG.rst new file mode 100644 index 0000000..20fd640 --- /dev/null +++ b/src/example/rclcpp/wait_set/CHANGELOG.rst @@ -0,0 +1,64 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclcpp_wait_set +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- +* fix: Fixed compilation after API change of TimerBase::execute (`#375 `_) + Co-authored-by: Janosch Machowinski +* Contributors: jmachowinski + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* Update the examples to C++17. (`#353 `_) +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash, Chris Lalancette + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- +* Add test linting to wait_set and fix issues. (`#346 `_) (`#347 `_) +* Contributors: mergify[bot] + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- +* Add wait set examples (`#315 `_) +* Contributors: carlossvg diff --git a/src/example/rclcpp/wait_set/CMakeLists.txt b/src/example/rclcpp/wait_set/CMakeLists.txt new file mode 100644 index 0000000..0d3b9e8 --- /dev/null +++ b/src/example/rclcpp/wait_set/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 3.5) +project(examples_rclcpp_wait_set) + +# Default to C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) +find_package(example_interfaces REQUIRED) +find_package(rclcpp REQUIRED) +find_package(rclcpp_components REQUIRED) +find_package(std_msgs REQUIRED) + +include_directories(include) + +add_library(talker SHARED src/talker.cpp) +target_compile_definitions(talker PRIVATE WAIT_SET_DLL) +ament_target_dependencies(talker rclcpp rclcpp_components std_msgs) +rclcpp_components_register_node( + talker + PLUGIN "Talker" + EXECUTABLE wait_set_talker) + +add_library(listener SHARED src/listener.cpp) +target_compile_definitions(listener PRIVATE WAIT_SET_DLL) +ament_target_dependencies(listener rclcpp rclcpp_components std_msgs) +rclcpp_components_register_node( + listener + PLUGIN "Listener" + EXECUTABLE wait_set_listener) + +add_executable(wait_set src/wait_set.cpp) +ament_target_dependencies(wait_set example_interfaces rclcpp std_msgs) + +add_executable(static_wait_set src/static_wait_set.cpp) +ament_target_dependencies(static_wait_set rclcpp std_msgs) + +add_executable(thread_safe_wait_set src/thread_safe_wait_set.cpp) +ament_target_dependencies(thread_safe_wait_set example_interfaces rclcpp std_msgs) + +add_executable(wait_set_topics_and_timer src/wait_set_topics_and_timer.cpp) +ament_target_dependencies(wait_set_topics_and_timer rclcpp std_msgs) + +add_executable(wait_set_random_order src/wait_set_random_order.cpp) +ament_target_dependencies(wait_set_random_order rclcpp std_msgs) + +add_executable(executor_random_order src/executor_random_order.cpp) +ament_target_dependencies(executor_random_order rclcpp std_msgs) + +add_executable(wait_set_topics_with_different_rates src/wait_set_topics_with_different_rates.cpp) +ament_target_dependencies(wait_set_topics_with_different_rates rclcpp std_msgs) + +add_executable(wait_set_composed src/wait_set_composed.cpp) +target_link_libraries(wait_set_composed talker listener) +ament_target_dependencies(wait_set_composed rclcpp) + +install(TARGETS + talker + listener + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +install(TARGETS + wait_set + static_wait_set + thread_safe_wait_set + wait_set_topics_and_timer + wait_set_random_order + executor_random_order + wait_set_topics_with_different_rates + wait_set_composed + DESTINATION lib/${PROJECT_NAME} +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/example/rclcpp/wait_set/README.md b/src/example/rclcpp/wait_set/README.md new file mode 100644 index 0000000..bf497dd --- /dev/null +++ b/src/example/rclcpp/wait_set/README.md @@ -0,0 +1,22 @@ +# Minimal rclcpp wait-set cookbook recipes + +This package contains a few different strategies for creating nodes which use `rclcpp::waitset` +to wait and handle ROS entities, that is, subscribers, timers, clients, services, guard +conditions and waitables. + + +* `wait_set.cpp`: Simple example showing how to use the default wait-set with a dynamic + storage policy and a sequential (no thread-safe) synchronization policy. +* `static_wait_set.cpp`: Simple example showing how to use the static wait-set with a static + storage policy. +* `thread_safe_wait_set.cpp`: Simple example showing how to use the thread-safe wait-set with a + thread-safe synchronization policy. +* `wait_set_topics_and_timer.cpp`: Simple example using multiple subscriptions, + publishers, and a timer. +* `wait_set_random_order.cpp`: An example showing user-defined + data handling and a random publisher. `executor_random_order.cpp` run the same node logic + using `SingleThreadedExecutor` to compare the data handling order. +* `wait_set_and_executor_composition.cpp`: An example showing how to combine a + `SingleThreadedExecutor` and a wait-set. +* `wait_set_topics_with_different_rate.cpp`: An example showing how to use a custom trigger + condition to handle topics with different topic rates. \ No newline at end of file diff --git a/src/example/rclcpp/wait_set/include/wait_set/listener.hpp b/src/example/rclcpp/wait_set/include/wait_set/listener.hpp new file mode 100644 index 0000000..f4d9d7c --- /dev/null +++ b/src/example/rclcpp/wait_set/include/wait_set/listener.hpp @@ -0,0 +1,37 @@ +// Copyright 2021, Apex.AI 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. + +#ifndef WAIT_SET__LISTENER_HPP_ +#define WAIT_SET__LISTENER_HPP_ + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" +#include "wait_set/visibility.h" + +class Listener : public rclcpp::Node +{ +public: + WAIT_SET_PUBLIC explicit Listener(rclcpp::NodeOptions options); + WAIT_SET_PUBLIC ~Listener() override; + +private: + void spin_wait_set(); + + rclcpp::Subscription::SharedPtr subscription1_; + rclcpp::Subscription::SharedPtr subscription2_; + rclcpp::WaitSet wait_set_; + std::thread thread_; +}; + +#endif // WAIT_SET__LISTENER_HPP_ diff --git a/src/example/rclcpp/wait_set/include/wait_set/random_listener.hpp b/src/example/rclcpp/wait_set/include/wait_set/random_listener.hpp new file mode 100644 index 0000000..79589ad --- /dev/null +++ b/src/example/rclcpp/wait_set/include/wait_set/random_listener.hpp @@ -0,0 +1,50 @@ +// Copyright 2021, Apex.AI 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. + +#ifndef WAIT_SET__RANDOM_LISTENER_HPP_ +#define WAIT_SET__RANDOM_LISTENER_HPP_ + +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +class RandomListener : public rclcpp::Node +{ + using subscription_list = std::vector::SharedPtr>; + +public: + RandomListener() + : Node("random_listener") + { + auto print_msg = [this](std_msgs::msg::String::UniquePtr msg) { + RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str()); + }; + sub1_ = this->create_subscription("topicA", 10, print_msg); + sub2_ = this->create_subscription("topicB", 10, print_msg); + sub3_ = this->create_subscription("topicC", 10, print_msg); + } + + subscription_list get_subscriptions() const + { + return {sub1_, sub2_, sub3_}; + } + +private: + rclcpp::Subscription::SharedPtr sub1_; + rclcpp::Subscription::SharedPtr sub2_; + rclcpp::Subscription::SharedPtr sub3_; +}; +#endif // WAIT_SET__RANDOM_LISTENER_HPP_ diff --git a/src/example/rclcpp/wait_set/include/wait_set/random_talker.hpp b/src/example/rclcpp/wait_set/include/wait_set/random_talker.hpp new file mode 100644 index 0000000..0b58db7 --- /dev/null +++ b/src/example/rclcpp/wait_set/include/wait_set/random_talker.hpp @@ -0,0 +1,74 @@ +// Copyright 2021, Apex.AI 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. + +#ifndef WAIT_SET__RANDOM_TALKER_HPP_ +#define WAIT_SET__RANDOM_TALKER_HPP_ + +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +class RandomTalker : public rclcpp::Node +{ +public: + RandomTalker() + : Node("random_talker"), + pub1_(this->create_publisher("topicA", 10)), + pub2_(this->create_publisher("topicB", 10)), + pub3_(this->create_publisher("topicC", 10)), + rand_engine_(static_cast( + std::abs(std::chrono::system_clock::now().time_since_epoch().count()) + )) + { + publish_functions_.emplace_back( + ([this]() { + std_msgs::msg::String msg; + msg.data = "A"; + RCLCPP_INFO(this->get_logger(), "Publishing: %s", msg.data.c_str()); + pub1_->publish(msg); + })); + publish_functions_.emplace_back( + ([this]() { + std_msgs::msg::String msg; + msg.data = "B"; + RCLCPP_INFO(this->get_logger(), "Publishing: %s", msg.data.c_str()); + pub2_->publish(msg); + })); + publish_functions_.emplace_back( + ([this]() { + std_msgs::msg::String msg; + msg.data = "C"; + RCLCPP_INFO(this->get_logger(), "Publishing: %s", msg.data.c_str()); + pub3_->publish(msg); + })); + auto timer_callback = + [this]() -> void { + std::shuffle(publish_functions_.begin(), publish_functions_.end(), rand_engine_); + for (const auto & f : publish_functions_) {f();} + }; + timer_ = this->create_wall_timer(std::chrono::seconds(1), timer_callback); + } + +private: + rclcpp::TimerBase::SharedPtr timer_; + rclcpp::Publisher::SharedPtr pub1_; + rclcpp::Publisher::SharedPtr pub2_; + rclcpp::Publisher::SharedPtr pub3_; + std::vector> publish_functions_; + std::default_random_engine rand_engine_; +}; +#endif // WAIT_SET__RANDOM_TALKER_HPP_ diff --git a/src/example/rclcpp/wait_set/include/wait_set/talker.hpp b/src/example/rclcpp/wait_set/include/wait_set/talker.hpp new file mode 100644 index 0000000..09c912a --- /dev/null +++ b/src/example/rclcpp/wait_set/include/wait_set/talker.hpp @@ -0,0 +1,33 @@ +// Copyright 2021, Apex.AI 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. + +#ifndef WAIT_SET__TALKER_HPP_ +#define WAIT_SET__TALKER_HPP_ + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" +#include "wait_set/visibility.h" + +class Talker : public rclcpp::Node +{ +public: + WAIT_SET_PUBLIC explicit Talker(rclcpp::NodeOptions options); + +private: + size_t count_; + rclcpp::Publisher::SharedPtr publisher_; + rclcpp::TimerBase::SharedPtr timer_; +}; + +#endif // WAIT_SET__TALKER_HPP_ diff --git a/src/example/rclcpp/wait_set/include/wait_set/visibility.h b/src/example/rclcpp/wait_set/include/wait_set/visibility.h new file mode 100644 index 0000000..a7449f0 --- /dev/null +++ b/src/example/rclcpp/wait_set/include/wait_set/visibility.h @@ -0,0 +1,66 @@ +// Copyright 2021, Apex.AI 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. + +#ifndef WAIT_SET__VISIBILITY_H_ +#define WAIT_SET__VISIBILITY_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +// This logic was borrowed (then namespaced) from the examples on the gcc wiki: +// https://gcc.gnu.org/wiki/Visibility + +#if defined _WIN32 || defined __CYGWIN__ + + #ifdef __GNUC__ + #define WAIT_SET_EXPORT __attribute__ ((dllexport)) + #define WAIT_SET_IMPORT __attribute__ ((dllimport)) + #else + #define WAIT_SET_EXPORT __declspec(dllexport) + #define WAIT_SET_IMPORT __declspec(dllimport) + #endif + + #ifdef WAIT_SET_DLL + #define WAIT_SET_PUBLIC WAIT_SET_EXPORT + #else + #define WAIT_SET_PUBLIC WAIT_SET_IMPORT + #endif + + #define WAIT_SET_PUBLIC_TYPE WAIT_SET_PUBLIC + + #define WAIT_SET_LOCAL + +#else + + #define WAIT_SET_EXPORT __attribute__ ((visibility("default"))) + #define WAIT_SET_IMPORT + + #if __GNUC__ >= 4 + #define WAIT_SET_PUBLIC __attribute__ ((visibility("default"))) + #define WAIT_SET_LOCAL __attribute__ ((visibility("hidden"))) + #else + #define WAIT_SET_PUBLIC + #define WAIT_SET_LOCAL + #endif + + #define WAIT_SET_PUBLIC_TYPE +#endif + +#ifdef __cplusplus +} +#endif + +#endif // WAIT_SET__VISIBILITY_H_ diff --git a/src/example/rclcpp/wait_set/package.xml b/src/example/rclcpp/wait_set/package.xml new file mode 100644 index 0000000..6546ded --- /dev/null +++ b/src/example/rclcpp/wait_set/package.xml @@ -0,0 +1,35 @@ + + + + examples_rclcpp_wait_set + 0.19.6 + Example of how to use the rclcpp::WaitSet directly. + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + William Woodall + + ament_cmake + + example_interfaces + rclcpp + rclcpp_components + std_msgs + + example_interfaces + rclcpp + rclcpp_components + std_msgs + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/example/rclcpp/wait_set/src/executor_random_order.cpp b/src/example/rclcpp/wait_set/src/executor_random_order.cpp new file mode 100644 index 0000000..c2dfb90 --- /dev/null +++ b/src/example/rclcpp/wait_set/src/executor_random_order.cpp @@ -0,0 +1,45 @@ +// Copyright 2021, Apex.AI 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. + +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +#include "wait_set/random_listener.hpp" +#include "wait_set/random_talker.hpp" + +/* For this example, we will be creating a talker node with three publishers which will + * publish the topics A, B, C in random order each time. In this case the messages are handled + * using an executor. The order in which the messages are handled will depend on the message + * arrival and the type of messages available at the moment. + */ + +int32_t main(const int32_t argc, char ** const argv) +{ + rclcpp::init(argc, argv); + + // Note the order of execution would be deterministic if both nodes are spun in the same + // executor (A, B, C). This is because the publishing happens always before the subscription + // handling and the executor handles the messages in the order in which the subscriptions were + // created. Using different threads the handling order depends on the message arrival and + // type of messages available. + auto thread = std::thread([]() {rclcpp::spin(std::make_shared());}); + rclcpp::spin(std::make_shared()); + + rclcpp::shutdown(); + thread.join(); + + return 0; +} diff --git a/src/example/rclcpp/wait_set/src/listener.cpp b/src/example/rclcpp/wait_set/src/listener.cpp new file mode 100644 index 0000000..e56bda0 --- /dev/null +++ b/src/example/rclcpp/wait_set/src/listener.cpp @@ -0,0 +1,83 @@ +// Copyright 2021, Apex.AI 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. + +#include +#include + +#include "wait_set/listener.hpp" +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using namespace std::chrono_literals; + +Listener::Listener(rclcpp::NodeOptions options) +: Node("listener", options) +{ + auto subscription_callback = [this](std_msgs::msg::String::UniquePtr msg) { + RCLCPP_INFO(this->get_logger(), "I heard: '%s' (executor)", msg->data.c_str()); + }; + subscription1_ = this->create_subscription( + "topic", + 10, + subscription_callback + ); + + auto wait_set_subscription_callback = [this](std_msgs::msg::String::UniquePtr msg) { + RCLCPP_INFO(this->get_logger(), "I heard: '%s' (wait-set)", msg->data.c_str()); + }; + rclcpp::CallbackGroup::SharedPtr cb_group_waitset = this->create_callback_group( + rclcpp::CallbackGroupType::MutuallyExclusive, false); + auto subscription_options = rclcpp::SubscriptionOptions(); + subscription_options.callback_group = cb_group_waitset; + subscription2_ = this->create_subscription( + "topic", + 10, + wait_set_subscription_callback, + subscription_options); + wait_set_.add_subscription(subscription2_); + thread_ = std::thread([this]() -> void {spin_wait_set();}); +} + +Listener::~Listener() +{ + if (thread_.joinable()) { + thread_.join(); + } +} + +void Listener::spin_wait_set() +{ + while (rclcpp::ok()) { + // Waiting up to 1s for a message to arrive + const auto wait_result = wait_set_.wait(std::chrono::seconds(1)); + if (wait_result.kind() == rclcpp::WaitResultKind::Ready) { + if (wait_result.get_wait_set().get_rcl_wait_set().subscriptions[0U]) { + std_msgs::msg::String msg; + rclcpp::MessageInfo msg_info; + if (subscription2_->take(msg, msg_info)) { + std::shared_ptr type_erased_msg = std::make_shared(msg); + subscription2_->handle_message(type_erased_msg, msg_info); + } + } + } else if (wait_result.kind() == rclcpp::WaitResultKind::Timeout) { + if (rclcpp::ok()) { + RCLCPP_ERROR(this->get_logger(), "Wait-set failed with timeout"); + } + } + } +} + +#include "rclcpp_components/register_node_macro.hpp" + +RCLCPP_COMPONENTS_REGISTER_NODE(Listener) diff --git a/src/example/rclcpp/wait_set/src/static_wait_set.cpp b/src/example/rclcpp/wait_set/src/static_wait_set.cpp new file mode 100644 index 0000000..79ba4d1 --- /dev/null +++ b/src/example/rclcpp/wait_set/src/static_wait_set.cpp @@ -0,0 +1,95 @@ +// Copyright 2021 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. + +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + + auto node = std::make_shared("static_wait_set_example_node"); + + auto do_nothing = [](std_msgs::msg::String::UniquePtr) {assert(false);}; + auto sub1 = node->create_subscription("~/chatter", 10, do_nothing); + auto sub2 = node->create_subscription("~/chatter", 10, do_nothing); + std::vector sub_vector = {sub1, sub2}; + auto guard_condition1 = std::make_shared(); + auto guard_condition2 = std::make_shared(); + + rclcpp::StaticWaitSet<2, 2, 0, 0, 0, 0> static_wait_set( + std::array::SubscriptionEntry, 2>{{{sub1}, {sub2}}}, + std::array{{{guard_condition1}, {guard_condition2}}}, + std::array{}, + std::array{}, + std::array{}, + std::array::WaitableEntry, 0>{}); + + auto wait_and_print_results = [&]() { + RCLCPP_INFO(node->get_logger(), "Waiting..."); + auto wait_result = static_wait_set.wait(std::chrono::seconds(1)); + if (wait_result.kind() == rclcpp::WaitResultKind::Ready) { + size_t guard_conditions_num = static_wait_set.get_rcl_wait_set().size_of_guard_conditions; + size_t subscriptions_num = static_wait_set.get_rcl_wait_set().size_of_subscriptions; + + for (size_t i = 0; i < guard_conditions_num; i++) { + if (wait_result.get_wait_set().get_rcl_wait_set().guard_conditions[i]) { + RCLCPP_INFO(node->get_logger(), "guard_condition %zu triggered", i + 1); + } + } + for (size_t i = 0; i < subscriptions_num; i++) { + if (wait_result.get_wait_set().get_rcl_wait_set().subscriptions[i]) { + RCLCPP_INFO(node->get_logger(), "subscription %zu triggered", i + 1); + std_msgs::msg::String msg; + rclcpp::MessageInfo msg_info; + if (sub_vector.at(i)->take(msg, msg_info)) { + RCLCPP_INFO( + node->get_logger(), + "subscription %zu: I heard '%s'", i + 1, msg.data.c_str()); + } else { + RCLCPP_INFO(node->get_logger(), "subscription %zu: No message", i + 1); + } + } + } + } else if (wait_result.kind() == rclcpp::WaitResultKind::Timeout) { + RCLCPP_INFO(node->get_logger(), "wait-set waiting failed with timeout"); + } else if (wait_result.kind() == rclcpp::WaitResultKind::Empty) { + RCLCPP_INFO(node->get_logger(), "wait-set waiting failed because wait-set is empty"); + } + }; + + RCLCPP_INFO(node->get_logger(), "Action: Nothing triggered"); + wait_and_print_results(); + + RCLCPP_INFO(node->get_logger(), "Action: Trigger Guard condition 1"); + guard_condition1->trigger(); + wait_and_print_results(); + + RCLCPP_INFO(node->get_logger(), "Action: Trigger Guard condition 2"); + guard_condition2->trigger(); + wait_and_print_results(); + + RCLCPP_INFO(node->get_logger(), "Action: Message published"); + auto pub = node->create_publisher("~/chatter", 1); + pub->publish(std_msgs::msg::String().set__data("test")); + wait_and_print_results(); + + // Note the static wait-set does not allow adding or removing entities dynamically. + // It will result in a compilation error. + + return 0; +} diff --git a/src/example/rclcpp/wait_set/src/talker.cpp b/src/example/rclcpp/wait_set/src/talker.cpp new file mode 100644 index 0000000..86a7aa2 --- /dev/null +++ b/src/example/rclcpp/wait_set/src/talker.cpp @@ -0,0 +1,39 @@ +// Copyright 2021, Apex.AI 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. + +#include + +#include "wait_set/talker.hpp" +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using namespace std::chrono_literals; + +Talker::Talker(rclcpp::NodeOptions options) +: Node("talker", options), count_(0) +{ + publisher_ = create_publisher("topic", 10); + auto timer_callback = + [this]() -> void { + auto message = std_msgs::msg::String(); + message.data = "Hello, world! " + std::to_string(count_++); + RCLCPP_INFO(this->get_logger(), "Publisher: '%s'", message.data.c_str()); + publisher_->publish(message); + }; + timer_ = this->create_wall_timer(500ms, timer_callback); +} + +#include "rclcpp_components/register_node_macro.hpp" + +RCLCPP_COMPONENTS_REGISTER_NODE(Talker) diff --git a/src/example/rclcpp/wait_set/src/thread_safe_wait_set.cpp b/src/example/rclcpp/wait_set/src/thread_safe_wait_set.cpp new file mode 100644 index 0000000..b0543e1 --- /dev/null +++ b/src/example/rclcpp/wait_set/src/thread_safe_wait_set.cpp @@ -0,0 +1,112 @@ +// Copyright 2021 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. + +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + + auto node = std::make_shared("wait_set_example_node"); + + auto do_nothing = [](std_msgs::msg::String::UniquePtr) {assert(false);}; + auto sub1 = node->create_subscription("~/chatter", 10, do_nothing); + auto sub2 = node->create_subscription("~/chatter", 10, do_nothing); + std::vector sub_vector = {sub1, sub2}; + auto guard_condition1 = std::make_shared(); + auto guard_condition2 = std::make_shared(); + + // FIXME: removing sub if it was added in the ctor leads to a failure + // terminate called after throwing an instance of 'std::runtime_error' + // what(): waitable not in wait set + rclcpp::ThreadSafeWaitSet wait_set( + // std::vector{{sub}}, + {}, + std::vector{guard_condition1}); + + wait_set.add_subscription(sub1); // FIXME: add it in the ctor + wait_set.add_subscription(sub2); + wait_set.add_guard_condition(guard_condition2); + + auto wait_and_print_results = [&]() { + RCLCPP_INFO(node->get_logger(), "Waiting..."); + auto wait_result = wait_set.wait(std::chrono::seconds(1)); + if (wait_result.kind() == rclcpp::WaitResultKind::Ready) { + size_t guard_conditions_num = wait_set.get_rcl_wait_set().size_of_guard_conditions; + size_t subscriptions_num = wait_set.get_rcl_wait_set().size_of_subscriptions; + + for (size_t i = 0; i < guard_conditions_num; i++) { + if (wait_result.get_wait_set().get_rcl_wait_set().guard_conditions[i]) { + RCLCPP_INFO(node->get_logger(), "guard_condition %zu triggered", i + 1); + } + } + for (size_t i = 0; i < subscriptions_num; i++) { + if (wait_result.get_wait_set().get_rcl_wait_set().subscriptions[i]) { + RCLCPP_INFO(node->get_logger(), "subscription %zu triggered", i + 1); + std_msgs::msg::String msg; + rclcpp::MessageInfo msg_info; + if (sub_vector.at(i)->take(msg, msg_info)) { + RCLCPP_INFO( + node->get_logger(), + "subscription %zu: I heard '%s'", i + 1, msg.data.c_str()); + } else { + RCLCPP_INFO(node->get_logger(), "subscription %zu: No message", i + 1); + } + } + } + } else if (wait_result.kind() == rclcpp::WaitResultKind::Timeout) { + RCLCPP_INFO(node->get_logger(), "wait-set waiting failed with timeout"); + } else if (wait_result.kind() == rclcpp::WaitResultKind::Empty) { + RCLCPP_INFO(node->get_logger(), "wait-set waiting failed because wait-set is empty"); + } + }; + + RCLCPP_INFO(node->get_logger(), "Action: Nothing triggered"); + wait_and_print_results(); + + RCLCPP_INFO(node->get_logger(), "Action: Trigger Guard condition 1"); + guard_condition1->trigger(); + wait_and_print_results(); + + RCLCPP_INFO(node->get_logger(), "Action: Trigger Guard condition 2"); + guard_condition2->trigger(); + wait_and_print_results(); + + RCLCPP_INFO(node->get_logger(), "Action: Message published"); + auto pub = node->create_publisher("~/chatter", 1); + pub->publish(std_msgs::msg::String().set__data("test")); + wait_and_print_results(); + + RCLCPP_INFO(node->get_logger(), "Action: Guard condition 1 removed"); + RCLCPP_INFO(node->get_logger(), "Action: Guard condition 2 removed"); + wait_set.remove_guard_condition(guard_condition1); + wait_set.remove_guard_condition(guard_condition2); + wait_and_print_results(); + + RCLCPP_INFO(node->get_logger(), "Action: Subscription 2 removed"); + wait_set.remove_subscription(sub2); + wait_and_print_results(); + + RCLCPP_INFO(node->get_logger(), "Action: Subscription 1 removed"); + wait_set.remove_subscription(sub1); + wait_and_print_results(); + + + return 0; +} diff --git a/src/example/rclcpp/wait_set/src/wait_set.cpp b/src/example/rclcpp/wait_set/src/wait_set.cpp new file mode 100644 index 0000000..b642292 --- /dev/null +++ b/src/example/rclcpp/wait_set/src/wait_set.cpp @@ -0,0 +1,111 @@ +// Copyright 2021 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. + +#include +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + + auto node = std::make_shared("wait_set_example_node"); + + auto do_nothing = [](std_msgs::msg::String::UniquePtr) {assert(false);}; + auto sub1 = node->create_subscription("~/chatter", 10, do_nothing); + auto sub2 = node->create_subscription("~/chatter", 10, do_nothing); + std::vector sub_vector = {sub1, sub2}; + auto guard_condition1 = std::make_shared(); + auto guard_condition2 = std::make_shared(); + + // FIXME: removing sub if it was added in the ctor leads to a failure + // terminate called after throwing an instance of 'std::runtime_error' + // what(): waitable not in wait set + rclcpp::WaitSet wait_set( + // std::vector{{sub}}, + {}, + std::vector{guard_condition1}); + + wait_set.add_subscription(sub1); // FIXME: add it in the ctor + wait_set.add_subscription(sub2); + wait_set.add_guard_condition(guard_condition2); + + auto wait_and_print_results = [&]() { + RCLCPP_INFO(node->get_logger(), "Waiting..."); + auto wait_result = wait_set.wait(std::chrono::seconds(1)); + if (wait_result.kind() == rclcpp::WaitResultKind::Ready) { + size_t guard_conditions_num = wait_set.get_rcl_wait_set().size_of_guard_conditions; + size_t subscriptions_num = wait_set.get_rcl_wait_set().size_of_subscriptions; + + for (size_t i = 0; i < guard_conditions_num; i++) { + if (wait_result.get_wait_set().get_rcl_wait_set().guard_conditions[i]) { + RCLCPP_INFO(node->get_logger(), "guard_condition %zu triggered", i + 1); + } + } + for (size_t i = 0; i < subscriptions_num; i++) { + if (wait_result.get_wait_set().get_rcl_wait_set().subscriptions[i]) { + RCLCPP_INFO(node->get_logger(), "subscription %zu triggered", i + 1); + std_msgs::msg::String msg; + rclcpp::MessageInfo msg_info; + if (sub_vector.at(i)->take(msg, msg_info)) { + RCLCPP_INFO( + node->get_logger(), + "subscription %zu: I heard '%s'", i + 1, msg.data.c_str()); + } else { + RCLCPP_INFO(node->get_logger(), "subscription %zu: No message", i + 1); + } + } + } + } else if (wait_result.kind() == rclcpp::WaitResultKind::Timeout) { + RCLCPP_INFO(node->get_logger(), "wait-set waiting failed with timeout"); + } else if (wait_result.kind() == rclcpp::WaitResultKind::Empty) { + RCLCPP_INFO(node->get_logger(), "wait-set waiting failed because wait-set is empty"); + } + }; + + RCLCPP_INFO(node->get_logger(), "Action: Nothing triggered"); + wait_and_print_results(); + + RCLCPP_INFO(node->get_logger(), "Action: Trigger Guard condition 1"); + guard_condition1->trigger(); + wait_and_print_results(); + + RCLCPP_INFO(node->get_logger(), "Action: Trigger Guard condition 2"); + guard_condition2->trigger(); + wait_and_print_results(); + + RCLCPP_INFO(node->get_logger(), "Action: Message published"); + auto pub = node->create_publisher("~/chatter", 1); + pub->publish(std_msgs::msg::String().set__data("test")); + wait_and_print_results(); + + RCLCPP_INFO(node->get_logger(), "Action: Guard condition 1 removed"); + RCLCPP_INFO(node->get_logger(), "Action: Guard condition 2 removed"); + wait_set.remove_guard_condition(guard_condition1); + wait_set.remove_guard_condition(guard_condition2); + wait_and_print_results(); + + RCLCPP_INFO(node->get_logger(), "Action: Subscription 2 removed"); + wait_set.remove_subscription(sub2); + wait_and_print_results(); + + RCLCPP_INFO(node->get_logger(), "Action: Subscription 1 removed"); + wait_set.remove_subscription(sub1); + wait_and_print_results(); + + return 0; +} diff --git a/src/example/rclcpp/wait_set/src/wait_set_composed.cpp b/src/example/rclcpp/wait_set/src/wait_set_composed.cpp new file mode 100644 index 0000000..a248b96 --- /dev/null +++ b/src/example/rclcpp/wait_set/src/wait_set_composed.cpp @@ -0,0 +1,32 @@ +// Copyright 2021, Apex.AI 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. + +#include +#include "wait_set/talker.hpp" +#include "wait_set/listener.hpp" +#include "rclcpp/rclcpp.hpp" + +int main(int argc, char * argv[]) +{ + rclcpp::init(argc, argv); + rclcpp::executors::SingleThreadedExecutor exec; + rclcpp::NodeOptions options; + auto talker = std::make_shared(options); + auto listener = std::make_shared(options); + exec.add_node(talker); + exec.add_node(listener); + exec.spin(); + rclcpp::shutdown(); + return 0; +} diff --git a/src/example/rclcpp/wait_set/src/wait_set_random_order.cpp b/src/example/rclcpp/wait_set/src/wait_set_random_order.cpp new file mode 100644 index 0000000..a71d5a8 --- /dev/null +++ b/src/example/rclcpp/wait_set/src/wait_set_random_order.cpp @@ -0,0 +1,78 @@ +// Copyright 2021, Apex.AI 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. + +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +#include "wait_set/random_listener.hpp" +#include "wait_set/random_talker.hpp" + +using namespace std::chrono_literals; + +/* For this example, we will be creating a talker node with three publishers which will + * publish the topics A, B, C in random order each time. The order in which the messages are + * handled is defined deterministically by the user in the code using a wait-set based loop. + * That is, in this example we always take and process the data in the same order A, B, C + * regardless of the arrival order. */ + +int32_t main(const int32_t argc, char ** const argv) +{ + rclcpp::init(argc, argv); + + auto random_listener = std::make_shared(); + auto subscriptions = random_listener->get_subscriptions(); + + // Create a wait-set and add the subscriptions + rclcpp::WaitSet wait_set; + for (const auto & subscription : subscriptions) { + wait_set.add_subscription(subscription); + } + + // Create a random talker and start publishing in another thread + auto thread = std::thread([]() {rclcpp::spin(std::make_shared());}); + + while (rclcpp::ok()) { + const auto wait_result = wait_set.wait(2s); + if (wait_result.kind() == rclcpp::WaitResultKind::Ready) { + bool sub1_has_data = wait_result.get_wait_set().get_rcl_wait_set().subscriptions[0U]; + bool sub2_has_data = wait_result.get_wait_set().get_rcl_wait_set().subscriptions[1U]; + bool sub3_has_data = wait_result.get_wait_set().get_rcl_wait_set().subscriptions[2U]; + + // Handle all the messages when all subscriptions have data + if (sub1_has_data && sub2_has_data && sub3_has_data) { + std_msgs::msg::String msg; + rclcpp::MessageInfo msg_info; + const size_t subscriptions_num = wait_set.get_rcl_wait_set().size_of_subscriptions; + for (size_t i = 0; i < subscriptions_num; i++) { + if (wait_result.get_wait_set().get_rcl_wait_set().subscriptions[i]) { + if (subscriptions.at(i)->take(msg, msg_info)) { + std::shared_ptr type_erased_msg = std::make_shared(msg); + subscriptions.at(i)->handle_message(type_erased_msg, msg_info); + } + } + } + } + } else if (wait_result.kind() == rclcpp::WaitResultKind::Timeout) { + if (rclcpp::ok()) { + RCLCPP_ERROR(random_listener->get_logger(), "Wait-set failed with timeout"); + } + } + } + + rclcpp::shutdown(); + thread.join(); + return 0; +} diff --git a/src/example/rclcpp/wait_set/src/wait_set_topics_and_timer.cpp b/src/example/rclcpp/wait_set/src/wait_set_topics_and_timer.cpp new file mode 100644 index 0000000..fca6c37 --- /dev/null +++ b/src/example/rclcpp/wait_set/src/wait_set_topics_and_timer.cpp @@ -0,0 +1,110 @@ +// Copyright 2021, Apex.AI 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. + +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using namespace std::chrono_literals; + +/* This example creates a node with three publishers, three subscriptions and a one-off timer. The + * node will use a wait-set based loop to trigger the timer, publish the messages and handle the + * received data */ + +int32_t main(const int32_t argc, char ** const argv) +{ + rclcpp::init(argc, argv); + + auto node = std::make_shared("wait_set_listener"); + auto do_nothing = [](std_msgs::msg::String::UniquePtr) {assert(false);}; + + auto sub1 = node->create_subscription("topicA", 1, do_nothing); + auto sub2 = node->create_subscription("topicB", 1, do_nothing); + auto sub3 = node->create_subscription("topicC", 1, do_nothing); + + std_msgs::msg::String msg1, msg2, msg3; + msg1.data = "Hello, world!"; + msg2.data = "Hello, world!"; + msg3.data = "Hello, world!"; + + const auto pub1 = + node->create_publisher("topicA", 1); + const auto pub2 = + node->create_publisher("topicB", 1); + const auto pub3 = + node->create_publisher("topicC", 1); + + // Use a timer to schedule one-off message publishing. + // Note in this case the callback won't be triggered automatically. It is up to the user to + // trigger it manually inside the wait-set loop. + rclcpp::TimerBase::SharedPtr one_off_timer; + auto timer_callback = [&]() { + RCLCPP_INFO(node->get_logger(), "Publishing msg1: '%s'", msg1.data.c_str()); + RCLCPP_INFO(node->get_logger(), "Publishing msg2: '%s'", msg2.data.c_str()); + RCLCPP_INFO(node->get_logger(), "Publishing msg3: '%s'", msg3.data.c_str()); + pub1->publish(msg1); + pub2->publish(msg2); + pub3->publish(msg3); + // disable the timer after the first call + one_off_timer->cancel(); + }; + + one_off_timer = node->create_wall_timer(1s, timer_callback); + + rclcpp::WaitSet wait_set({{{sub1}, {sub2}, {sub3}}}, {}, {one_off_timer}); + + // Loop waiting on the wait-set until 3 messages are received + auto num_recv = std::size_t(); + while (num_recv < 3U) { + const auto wait_result = wait_set.wait(2s); + if (wait_result.kind() == rclcpp::WaitResultKind::Ready) { + if (wait_result.get_wait_set().get_rcl_wait_set().timers[0U]) { + if (auto data = one_off_timer->call()) { + // The timer callback is executed manually here + one_off_timer->execute_callback(data); + } + } else { + std_msgs::msg::String msg; + rclcpp::MessageInfo msg_info; + if (wait_result.get_wait_set().get_rcl_wait_set().subscriptions[0U]) { + if (sub1->take(msg, msg_info)) { + ++num_recv; + RCLCPP_INFO(node->get_logger(), "msg1 data: '%s'", msg.data.c_str()); + } + } + if (wait_result.get_wait_set().get_rcl_wait_set().subscriptions[1U]) { + if (sub2->take(msg, msg_info)) { + ++num_recv; + RCLCPP_INFO(node->get_logger(), "msg2 data: '%s'", msg.data.c_str()); + } + } + if (wait_result.get_wait_set().get_rcl_wait_set().subscriptions[2U]) { + if (sub3->take(msg, msg_info)) { + ++num_recv; + RCLCPP_INFO(node->get_logger(), "msg3 data: '%s'", msg.data.c_str()); + } + } + RCLCPP_INFO(node->get_logger(), "Number of messages already got: %zu of 3", num_recv); + } + } else if (wait_result.kind() == rclcpp::WaitResultKind::Timeout) { + if (rclcpp::ok()) { + RCLCPP_ERROR(node->get_logger(), "Wait-set failed with timeout"); + } + } + } + RCLCPP_INFO(node->get_logger(), "Got all messages!"); + + return 0; +} diff --git a/src/example/rclcpp/wait_set/src/wait_set_topics_with_different_rates.cpp b/src/example/rclcpp/wait_set/src/wait_set_topics_with_different_rates.cpp new file mode 100644 index 0000000..3a4f42a --- /dev/null +++ b/src/example/rclcpp/wait_set/src/wait_set_topics_with_different_rates.cpp @@ -0,0 +1,125 @@ +// Copyright 2021, Apex.AI 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. + +#include +#include + +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using namespace std::chrono_literals; + +/* For this example, we will be creating three talkers publishing the topics A, B, C at different + * rates. The messages are handled by a wait-set loop which handles topics A and B together on a + * using topic B as trigger condition. Topic C is handled independently using the topic C + * itself as a trigger condition. */ + +class Talker : public rclcpp::Node +{ +public: + Talker( + const std::string & node_name, + const std::string & topic_name, + const std::string & message_data, + std::chrono::nanoseconds period) + : Node(node_name) + { + publisher_ = this->create_publisher(topic_name, 10); + auto timer_callback = + [this, message_data]() -> void { + std_msgs::msg::String message; + message.data = message_data; + RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str()); + this->publisher_->publish(message); + }; + timer_ = this->create_wall_timer(period, timer_callback); + } + +private: + rclcpp::TimerBase::SharedPtr timer_; + rclcpp::Publisher::SharedPtr publisher_; +}; + +int32_t main(const int32_t argc, char ** const argv) +{ + rclcpp::init(argc, argv); + + auto node = std::make_shared("wait_set_listener"); + auto do_nothing = [](std_msgs::msg::String::UniquePtr) {assert(false);}; + + auto sub1 = node->create_subscription("topicA", 10, do_nothing); + auto sub2 = node->create_subscription("topicB", 10, do_nothing); + auto sub3 = node->create_subscription("topicC", 10, do_nothing); + + rclcpp::WaitSet wait_set({{sub1}, {sub2}, {sub3}}); + + // Create three talkers publishing in topics A, B, and C with different publishing rates + auto talkerA = std::make_shared("TalkerA", "topicA", "A", 1500ms); + auto talkerB = std::make_shared("TalkerB", "topicB", "B", 2000ms); + auto talkerC = std::make_shared("TalkerC", "topicC", "C", 3000ms); + + // Create an executor to spin the talkers in a separate thread + rclcpp::executors::SingleThreadedExecutor exec; + exec.add_node(talkerA); + exec.add_node(talkerB); + exec.add_node(talkerC); + auto publisher_thread = std::thread([&exec]() {exec.spin();}); + + while (rclcpp::ok()) { + const auto wait_result = wait_set.wait(3s); + if (wait_result.kind() == rclcpp::WaitResultKind::Ready) { + bool sub2_has_data = wait_result.get_wait_set().get_rcl_wait_set().subscriptions[1U]; + bool sub3_has_data = wait_result.get_wait_set().get_rcl_wait_set().subscriptions[2U]; + + // topic A and B handling + // Note only topic B is used as a trigger condition + if (sub2_has_data) { + std_msgs::msg::String msg1; + std_msgs::msg::String msg2; + rclcpp::MessageInfo msg_info; + std::string handled_data; + + if (sub2->take(msg2, msg_info)) { + // since topic A is published at a faster rate we expect to take multiple messages + while (sub1->take(msg1, msg_info)) { + handled_data.append(msg1.data); + } + handled_data.append(msg2.data); + RCLCPP_INFO(node->get_logger(), "I heard: '%s'", handled_data.c_str()); + } else { + RCLCPP_ERROR(node->get_logger(), "An invalid message from topic B was received."); + } + } + + // topic C handling + if (sub3_has_data) { + std_msgs::msg::String msg; + rclcpp::MessageInfo msg_info; + if (sub3->take(msg, msg_info)) { + RCLCPP_INFO(node->get_logger(), "I heard: '%s'", msg.data.c_str()); + } else { + RCLCPP_ERROR(node->get_logger(), "An invalid message from topic C was received."); + } + } + } else if (wait_result.kind() == rclcpp::WaitResultKind::Timeout) { + if (rclcpp::ok()) { + RCLCPP_ERROR(node->get_logger(), "Wait-set failed with timeout"); + } + } + } + + rclcpp::shutdown(); + publisher_thread.join(); + return 0; +} diff --git a/src/example/rclpy/actions/minimal_action_client/CHANGELOG.rst b/src/example/rclpy/actions/minimal_action_client/CHANGELOG.rst new file mode 100644 index 0000000..1072038 --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_client/CHANGELOG.rst @@ -0,0 +1,145 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclpy_minimal_action_client +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- +* Enable document generation using rosdoc2 for ament_python pkgs (`#357 `_) + * Add missing action_msgs dep + * Add exec_deps for launch_testing_examples +* Contributors: Yadu + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Update maintainers to Aditya Pande and Shane Loretz (`#332 `_) +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande, Audrow Nash + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- +* Use underscores instead of dashes in setup.cfg (`#310 `_) +* Contributors: Ivan Santiago Paunovic + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- +* Using asyncio with ros2 action client (`#301 `_) +* Contributors: alemme + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- +* Added missing linting tests (`#287 `_) +* Contributors: Allison Thackston + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ + +0.9.0 (2020-04-30) +------------------ + +0.8.2 (2019-11-19) +------------------ +* Fix client_cancel example. (`#258 `_) +* Contributors: Michel Hidalgo + +0.8.1 (2019-10-24) +------------------ + +0.7.3 (2019-05-29) +------------------ +* Fix InvalidHandle exception in action client examples (`#246 `_) +* Contributors: Siddharth Kucheria + +0.7.2 (2019-05-20) +------------------ + +0.7.1 (2019-05-08) +------------------ +* Fix rclpy action client examples +* Contributors: Jacob Perron + +0.7.0 (2019-04-14) +------------------ +* Added rclpy action examples. (`#222 `_) +* Contributors: Jacob Perron + +0.6.2 (2019-02-08) +------------------ + +0.6.1 (2018-12-07) +------------------ + +0.6.0 (2018-11-20) +------------------ + +0.5.1 (2018-06-27) +------------------ + +0.5.0 (2018-06-26) +------------------ + +0.4.0 (2017-12-08) +------------------ diff --git a/src/example/rclpy/actions/minimal_action_client/examples_rclpy_minimal_action_client/__init__.py b/src/example/rclpy/actions/minimal_action_client/examples_rclpy_minimal_action_client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/actions/minimal_action_client/examples_rclpy_minimal_action_client/client.py b/src/example/rclpy/actions/minimal_action_client/examples_rclpy_minimal_action_client/client.py new file mode 100644 index 0000000..2b8dc18 --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_client/examples_rclpy_minimal_action_client/client.py @@ -0,0 +1,81 @@ +# 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. + +from action_msgs.msg import GoalStatus +from example_interfaces.action import Fibonacci + +import rclpy +from rclpy.action import ActionClient +from rclpy.node import Node + + +class MinimalActionClient(Node): + + def __init__(self): + super().__init__('minimal_action_client') + self._action_client = ActionClient(self, Fibonacci, 'fibonacci') + + def goal_response_callback(self, future): + goal_handle = future.result() + if not goal_handle.accepted: + self.get_logger().info('Goal rejected :(') + return + + self.get_logger().info('Goal accepted :)') + + self._get_result_future = goal_handle.get_result_async() + self._get_result_future.add_done_callback(self.get_result_callback) + + def feedback_callback(self, feedback): + self.get_logger().info('Received feedback: {0}'.format(feedback.feedback.sequence)) + + def get_result_callback(self, future): + result = future.result().result + status = future.result().status + if status == GoalStatus.STATUS_SUCCEEDED: + self.get_logger().info('Goal succeeded! Result: {0}'.format(result.sequence)) + else: + self.get_logger().info('Goal failed with status: {0}'.format(status)) + + # Shutdown after receiving a result + rclpy.shutdown() + + def send_goal(self): + self.get_logger().info('Waiting for action server...') + self._action_client.wait_for_server() + + goal_msg = Fibonacci.Goal() + goal_msg.order = 10 + + self.get_logger().info('Sending goal request...') + + self._send_goal_future = self._action_client.send_goal_async( + goal_msg, + feedback_callback=self.feedback_callback) + + self._send_goal_future.add_done_callback(self.goal_response_callback) + + +def main(args=None): + rclpy.init(args=args) + + action_client = MinimalActionClient() + + action_client.send_goal() + + rclpy.spin(action_client) + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/actions/minimal_action_client/examples_rclpy_minimal_action_client/client_asyncio.py b/src/example/rclpy/actions/minimal_action_client/examples_rclpy_minimal_action_client/client_asyncio.py new file mode 100644 index 0000000..9af6bcb --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_client/examples_rclpy_minimal_action_client/client_asyncio.py @@ -0,0 +1,117 @@ +# Copyright 2021 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. + +import asyncio + +from action_msgs.msg import GoalStatus +from example_interfaces.action import Fibonacci + +import rclpy +from rclpy.action import ActionClient +from rclpy.node import Node + + +class MinimalActionClientAsyncIO(Node): + + def __init__(self): + super().__init__('minimal_action_client_asyncio') + self._action_client = ActionClient(self, Fibonacci, 'fibonacci') + + def feedback_callback(self, feedback): + self.get_logger().info('Received feedback: {0}'.format(feedback.feedback.sequence)) + + async def send_goal(self): + self.get_logger().info('Waiting for action server...') + self._action_client.wait_for_server() + + goal_msg = Fibonacci.Goal() + goal_msg.order = 10 + + self.get_logger().info('Sending goal request...') + + goal_handle = await self._action_client.send_goal_async( + goal_msg, + feedback_callback=self.feedback_callback + ) + + if not goal_handle.accepted: + self.get_logger().info('Goal rejected :(') + return + + self.get_logger().info('Goal accepted :)') + + res = await goal_handle.get_result_async() + result = res.result + status = res.status + if status == GoalStatus.STATUS_SUCCEEDED: + self.get_logger().info('Goal succeeded! Result: {0}'.format(result.sequence)) + else: + self.get_logger().info('Goal failed with status: {0}'.format(status)) + return result, status + + +async def spinning(node): + while rclpy.ok(): + rclpy.spin_once(node, timeout_sec=0.01) + await asyncio.sleep(0.001) + + +async def run(args, loop): + + logger = rclpy.logging.get_logger('minimal_action_client') + + # init ros2 + rclpy.init(args=args) + + # create node + action_client = MinimalActionClientAsyncIO() + + # start spinning + spin_task = loop.create_task(spinning(action_client)) + + # Parallel example + # execute goal request and schedule in loop + my_task1 = loop.create_task(action_client.send_goal()) + my_task2 = loop.create_task(action_client.send_goal()) + + # glue futures together and wait + wait_future = asyncio.wait([my_task1, my_task2]) + # run event loop + finished, unfinished = await wait_future + logger.info(f'unfinished: {len(unfinished)}') + for task in finished: + logger.info('result {} and status flag {}'.format(*task.result())) + + # Sequence + result, status = await loop.create_task(action_client.send_goal()) + logger.info(f'A) result {result} and status flag {status}') + result, status = await loop.create_task(action_client.send_goal()) + logger.info(f'B) result {result} and status flag {status}') + + # cancel spinning task + spin_task.cancel() + try: + await spin_task + except asyncio.exceptions.CancelledError: + pass + rclpy.shutdown() + + +def main(args=None): + loop = asyncio.get_event_loop() + loop.run_until_complete(run(args, loop=loop)) + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/actions/minimal_action_client/examples_rclpy_minimal_action_client/client_cancel.py b/src/example/rclpy/actions/minimal_action_client/examples_rclpy_minimal_action_client/client_cancel.py new file mode 100644 index 0000000..5258faf --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_client/examples_rclpy_minimal_action_client/client_cancel.py @@ -0,0 +1,89 @@ +# Copyright 2019 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. + +from example_interfaces.action import Fibonacci + +import rclpy +from rclpy.action import ActionClient +from rclpy.node import Node + + +class MinimalActionClient(Node): + + def __init__(self): + super().__init__('minimal_action_client') + self._action_client = ActionClient(self, Fibonacci, 'fibonacci') + + def cancel_done(self, future): + cancel_response = future.result() + if len(cancel_response.goals_canceling) > 0: + self.get_logger().info('Goal successfully canceled') + else: + self.get_logger().info('Goal failed to cancel') + + rclpy.shutdown() + + def goal_response_callback(self, future): + goal_handle = future.result() + if not goal_handle.accepted: + self.get_logger().info('Goal rejected :(') + return + + self._goal_handle = goal_handle + + self.get_logger().info('Goal accepted :)') + + # Start a 2 second timer + self._timer = self.create_timer(2.0, self.timer_callback) + + def feedback_callback(self, feedback): + self.get_logger().info('Received feedback: {0}'.format(feedback.feedback.sequence)) + + def timer_callback(self): + self.get_logger().info('Canceling goal') + # Cancel the goal + future = self._goal_handle.cancel_goal_async() + future.add_done_callback(self.cancel_done) + + # Cancel the timer + self._timer.cancel() + + def send_goal(self): + self.get_logger().info('Waiting for action server...') + self._action_client.wait_for_server() + + goal_msg = Fibonacci.Goal() + goal_msg.order = 10 + + self.get_logger().info('Sending goal request...') + + self._send_goal_future = self._action_client.send_goal_async( + goal_msg, + feedback_callback=self.feedback_callback) + + self._send_goal_future.add_done_callback(self.goal_response_callback) + + +def main(args=None): + rclpy.init(args=args) + + action_client = MinimalActionClient() + + action_client.send_goal() + + rclpy.spin(action_client) + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/actions/minimal_action_client/examples_rclpy_minimal_action_client/client_not_composable.py b/src/example/rclpy/actions/minimal_action_client/examples_rclpy_minimal_action_client/client_not_composable.py new file mode 100644 index 0000000..26af729 --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_client/examples_rclpy_minimal_action_client/client_not_composable.py @@ -0,0 +1,77 @@ +# Copyright 2019 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. + +from action_msgs.msg import GoalStatus + +from example_interfaces.action import Fibonacci + +import rclpy +from rclpy.action import ActionClient + + +def feedback_cb(logger, feedback): + logger.info('Received feedback: {0}'.format(feedback.feedback.sequence)) + + +def main(args=None): + rclpy.init(args=args) + + node = rclpy.create_node('minimal_action_client') + + action_client = ActionClient(node, Fibonacci, 'fibonacci') + + node.get_logger().info('Waiting for action server...') + + action_client.wait_for_server() + + goal_msg = Fibonacci.Goal() + goal_msg.order = 10 + + node.get_logger().info('Sending goal request...') + + send_goal_future = action_client.send_goal_async( + goal_msg, feedback_callback=lambda feedback: feedback_cb(node.get_logger(), feedback)) + + rclpy.spin_until_future_complete(node, send_goal_future) + + goal_handle = send_goal_future.result() + + if not goal_handle.accepted: + node.get_logger().info('Goal rejected :(') + action_client.destroy() + node.destroy_node() + rclpy.shutdown() + return + + node.get_logger().info('Goal accepted :)') + + get_result_future = goal_handle.get_result_async() + + rclpy.spin_until_future_complete(node, get_result_future) + + result = get_result_future.result().result + status = get_result_future.result().status + if status == GoalStatus.STATUS_SUCCEEDED: + node.get_logger().info( + 'Goal succeeded! Result: {0}'.format(result.sequence)) + else: + node.get_logger().info('Goal failed with status code: {0}'.format(status)) + + action_client.destroy() + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/actions/minimal_action_client/package.xml b/src/example/rclpy/actions/minimal_action_client/package.xml new file mode 100644 index 0000000..e052fc4 --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_client/package.xml @@ -0,0 +1,30 @@ + + + + examples_rclpy_minimal_action_client + 0.19.6 + Examples of minimal action clients using rclpy. + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Jacob Perron + Shane Loretz + + action_msgs + example_interfaces + rclpy + + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/example/rclpy/actions/minimal_action_client/resource/examples_rclpy_minimal_action_client b/src/example/rclpy/actions/minimal_action_client/resource/examples_rclpy_minimal_action_client new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/actions/minimal_action_client/setup.cfg b/src/example/rclpy/actions/minimal_action_client/setup.cfg new file mode 100644 index 0000000..d89c932 --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_client/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/examples_rclpy_minimal_action_client +[install] +install_scripts=$base/lib/examples_rclpy_minimal_action_client diff --git a/src/example/rclpy/actions/minimal_action_client/setup.py b/src/example/rclpy/actions/minimal_action_client/setup.py new file mode 100644 index 0000000..2b5c6e3 --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_client/setup.py @@ -0,0 +1,38 @@ +from setuptools import setup + +package_name = 'examples_rclpy_minimal_action_client' + +setup( + name=package_name, + version='0.19.6', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + author='Jacob Perron', + author_email='jacob@openrobotics.org', + maintainer='Aditya Pande, Alejandro Hernandez Cordero', + maintainer_email='aditya.pande@openrobotics.org, alejandro@openrobotics.org', + keywords=['ROS'], + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Topic :: Software Development', + ], + description='Examples of action clients using rclpy.', + license='Apache License, Version 2.0', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'client = ' + package_name + '.client:main', + 'client_cancel = ' + package_name + '.client_cancel:main', + 'client_not_composable = ' + package_name + '.client_not_composable:main', + 'client_asyncio = ' + package_name + '.client_asyncio:main', + ], + }, +) diff --git a/src/example/rclpy/actions/minimal_action_client/test/test_copyright.py b/src/example/rclpy/actions/minimal_action_client/test/test_copyright.py new file mode 100644 index 0000000..8959088 --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_client/test/test_copyright.py @@ -0,0 +1,24 @@ +# Copyright 2017 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. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + # Test is called from package root + rc = main(argv=['.']) + assert rc == 0, 'Found errors' diff --git a/src/example/rclpy/actions/minimal_action_client/test/test_flake8.py b/src/example/rclpy/actions/minimal_action_client/test/test_flake8.py new file mode 100644 index 0000000..76ccb0f --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_client/test/test_flake8.py @@ -0,0 +1,26 @@ +# Copyright 2017 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. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + # Test is called from package root + rc, errors = main_with_errors(argv=['.']) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/example/rclpy/actions/minimal_action_client/test/test_pep257.py b/src/example/rclpy/actions/minimal_action_client/test/test_pep257.py new file mode 100644 index 0000000..18a66da --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_client/test/test_pep257.py @@ -0,0 +1,24 @@ +# Copyright 2017 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + # Test is called from package root + rc = main(argv=['.']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/example/rclpy/actions/minimal_action_server/CHANGELOG.rst b/src/example/rclpy/actions/minimal_action_server/CHANGELOG.rst new file mode 100644 index 0000000..173c319 --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_server/CHANGELOG.rst @@ -0,0 +1,140 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclpy_minimal_action_server +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- +* Add guard on Python single goal action server example (`#380 `_) (`#381 `_) + Co-authored-by: Ruddick Lawrence <679360+mrjogo@users.noreply.github.com> +* Contributors: mergify[bot] + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Update maintainers to Aditya Pande and Shane Loretz (`#332 `_) +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande, Audrow Nash + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- +* Use underscores instead of dashes in setup.cfg (`#310 `_) +* Contributors: Ivan Santiago Paunovic + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- +* Added missing linting tests (`#287 `_) +* Contributors: Allison Thackston + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ +* Fix server_queue_goals.py example (`#272 `_) +* Contributors: Michel Hidalgo + +0.9.0 (2020-04-30) +------------------ + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ + +0.7.3 (2019-05-29) +------------------ + +0.7.2 (2019-05-20) +------------------ + +0.7.1 (2019-05-08) +------------------ +* Rename action state transitions (`#234 `_) +* Contributors: Jacob Perron + +0.7.0 (2019-04-14) +------------------ +* Added rclpy action examples (`#222 `_) +* Contributors: Jacob Perron + +0.6.2 (2019-02-08) +------------------ + +0.6.1 (2018-12-07) +------------------ + +0.6.0 (2018-11-20) +------------------ + +0.5.1 (2018-06-27) +------------------ + +0.5.0 (2018-06-26) +------------------ + +0.4.0 (2017-12-08) +------------------ diff --git a/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/__init__.py b/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/server.py b/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/server.py new file mode 100644 index 0000000..47fb42a --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/server.py @@ -0,0 +1,107 @@ +# Copyright 2019 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. + +import time + +from example_interfaces.action import Fibonacci + +import rclpy +from rclpy.action import ActionServer, CancelResponse, GoalResponse +from rclpy.callback_groups import ReentrantCallbackGroup +from rclpy.executors import MultiThreadedExecutor +from rclpy.node import Node + + +class MinimalActionServer(Node): + + def __init__(self): + super().__init__('minimal_action_server') + + self._action_server = ActionServer( + self, + Fibonacci, + 'fibonacci', + execute_callback=self.execute_callback, + callback_group=ReentrantCallbackGroup(), + goal_callback=self.goal_callback, + cancel_callback=self.cancel_callback) + + def destroy(self): + self._action_server.destroy() + super().destroy_node() + + def goal_callback(self, goal_request): + """Accept or reject a client request to begin an action.""" + # This server allows multiple goals in parallel + self.get_logger().info('Received goal request') + return GoalResponse.ACCEPT + + def cancel_callback(self, goal_handle): + """Accept or reject a client request to cancel an action.""" + self.get_logger().info('Received cancel request') + return CancelResponse.ACCEPT + + async def execute_callback(self, goal_handle): + """Execute a goal.""" + self.get_logger().info('Executing goal...') + + # Append the seeds for the Fibonacci sequence + feedback_msg = Fibonacci.Feedback() + feedback_msg.sequence = [0, 1] + + # Start executing the action + for i in range(1, goal_handle.request.order): + if goal_handle.is_cancel_requested: + goal_handle.canceled() + self.get_logger().info('Goal canceled') + return Fibonacci.Result() + + # Update Fibonacci sequence + feedback_msg.sequence.append(feedback_msg.sequence[i] + feedback_msg.sequence[i-1]) + + self.get_logger().info('Publishing feedback: {0}'.format(feedback_msg.sequence)) + + # Publish the feedback + goal_handle.publish_feedback(feedback_msg) + + # Sleep for demonstration purposes + time.sleep(1) + + goal_handle.succeed() + + # Populate result message + result = Fibonacci.Result() + result.sequence = feedback_msg.sequence + + self.get_logger().info('Returning result: {0}'.format(result.sequence)) + + return result + + +def main(args=None): + rclpy.init(args=args) + + minimal_action_server = MinimalActionServer() + + # Use a MultiThreadedExecutor to enable processing goals concurrently + executor = MultiThreadedExecutor() + + rclpy.spin(minimal_action_server, executor=executor) + + minimal_action_server.destroy() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/server_defer.py b/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/server_defer.py new file mode 100644 index 0000000..21c93d4 --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/server_defer.py @@ -0,0 +1,121 @@ +# 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. + +import time + +from example_interfaces.action import Fibonacci + +import rclpy +from rclpy.action import ActionServer, CancelResponse, GoalResponse +from rclpy.callback_groups import ReentrantCallbackGroup +from rclpy.executors import MultiThreadedExecutor +from rclpy.node import Node + + +class MinimalActionServer(Node): + + def __init__(self): + super().__init__('minimal_action_server') + + self._goal_handle = None + + self._action_server = ActionServer( + self, + Fibonacci, + 'fibonacci', + execute_callback=self.execute_callback, + callback_group=ReentrantCallbackGroup(), + goal_callback=self.goal_callback, + handle_accepted_callback=self.handle_accepted_callback, + cancel_callback=self.cancel_callback) + + def destroy(self): + self._action_server.destroy() + super().destroy_node() + + def goal_callback(self, goal_request): + """Accept or reject a client request to begin an action.""" + self.get_logger().info('Received goal request') + return GoalResponse.ACCEPT + + def handle_accepted_callback(self, goal_handle): + """Provide a handle to an accepted goal.""" + self.get_logger().info('Deferring execution...') + self._goal_handle = goal_handle + self._timer = self.create_timer(3.0, self.timer_callback) + + def cancel_callback(self, goal_handle): + """Accept or reject a client request to cancel an action.""" + self.get_logger().info('Received cancel request') + return CancelResponse.ACCEPT + + def timer_callback(self): + # Execute the defered goal + if self._goal_handle is not None: + self._goal_handle.execute() + self._timer.cancel() + + async def execute_callback(self, goal_handle): + """Execute a goal.""" + self.get_logger().info('Executing goal...') + + # Append the seeds for the Fibonacci sequence + feedback_msg = Fibonacci.Feedback() + feedback_msg.sequence = [0, 1] + + # Start executing the action + for i in range(1, goal_handle.request.order): + if goal_handle.is_cancel_requested: + goal_handle.canceled() + self.get_logger().info('Goal canceled') + return Fibonacci.Result() + + # Update Fibonacci sequence + feedback_msg.sequence.append(feedback_msg.sequence[i] + feedback_msg.sequence[i-1]) + + self.get_logger().info('Publishing feedback: {0}'.format(feedback_msg.sequence)) + + # Publish the feedback + goal_handle.publish_feedback(feedback_msg) + + # Sleep for demonstration purposes + time.sleep(1) + + goal_handle.succeed() + + # Populate result message + result = Fibonacci.Result() + result.sequence = feedback_msg.sequence + + self.get_logger().info('Returning result: {0}'.format(result.sequence)) + + return result + + +def main(args=None): + rclpy.init(args=args) + + minimal_action_server = MinimalActionServer() + + # Use a MultiThreadedExecutor to enable processing goals concurrently + executor = MultiThreadedExecutor() + + rclpy.spin(minimal_action_server, executor=executor) + + minimal_action_server.destroy() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/server_not_composable.py b/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/server_not_composable.py new file mode 100644 index 0000000..61ef22f --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/server_not_composable.py @@ -0,0 +1,99 @@ +# Copyright 2019 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. + +import time + +from example_interfaces.action import Fibonacci + +import rclpy +from rclpy.action import ActionServer, CancelResponse +from rclpy.callback_groups import ReentrantCallbackGroup +from rclpy.executors import MultiThreadedExecutor + + +logger = None + + +def cancel_callback(goal_handle): + logger.info('Received cancel request') + return CancelResponse.ACCEPT + + +async def execute_callback(goal_handle): + """Execute the goal.""" + logger.info('Executing goal...') + + # Append the seeds for the fibonacci sequence + feedback_msg = Fibonacci.Feedback() + feedback_msg.sequence = [0, 1] + + # Start executing the action + for i in range(1, goal_handle.request.order): + if goal_handle.is_cancel_requested: + goal_handle.canceled() + logger.info('Goal canceled') + return Fibonacci.Result() + + # Update Fibonacci sequence + feedback_msg.sequence.append(feedback_msg.sequence[i] + feedback_msg.sequence[i-1]) + + logger.info('Publishing feedback: {0}'.format(feedback_msg.sequence)) + + # Publish feedback + goal_handle.publish_feedback(feedback_msg) + + # Sleep for demonstration purposes + time.sleep(1) + + goal_handle.succeed() + + # Populate result message + result = Fibonacci.Result() + result.sequence = feedback_msg.sequence + + logger.info('Returning result: {0}'.format(result.sequence)) + + return result + + +def main(args=None): + global logger + rclpy.init(args=args) + + node = rclpy.create_node('minimal_action_server') + logger = node.get_logger() + + # Use a ReentrantCallbackGroup to enable processing multiple goals concurrently + # Default goal callback accepts all goals + # Default cancel callback rejects cancel requests + action_server = ActionServer( + node, + Fibonacci, + 'fibonacci', + execute_callback=execute_callback, + cancel_callback=cancel_callback, + callback_group=ReentrantCallbackGroup()) + + # Use a MultiThreadedExecutor to enable processing goals concurrently + executor = MultiThreadedExecutor() + + rclpy.spin(node, executor=executor) + + action_server.destroy() + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/server_queue_goals.py b/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/server_queue_goals.py new file mode 100644 index 0000000..abcde95 --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/server_queue_goals.py @@ -0,0 +1,137 @@ +# Copyright 2018-2020 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. + +import collections +import threading +import time + +from example_interfaces.action import Fibonacci + +import rclpy +from rclpy.action import ActionServer, CancelResponse, GoalResponse +from rclpy.callback_groups import ReentrantCallbackGroup +from rclpy.executors import MultiThreadedExecutor +from rclpy.node import Node + + +class MinimalActionServer(Node): + + def __init__(self): + super().__init__('minimal_action_server') + self._goal_queue = collections.deque() + self._goal_queue_lock = threading.Lock() + self._current_goal = None + + self._action_server = ActionServer( + self, + Fibonacci, + 'fibonacci', + handle_accepted_callback=self.handle_accepted_callback, + execute_callback=self.execute_callback, + goal_callback=self.goal_callback, + cancel_callback=self.cancel_callback, + callback_group=ReentrantCallbackGroup()) + + def destroy(self): + self._action_server.destroy() + super().destroy_node() + + def handle_accepted_callback(self, goal_handle): + """Start or defer execution of an already accepted goal.""" + with self._goal_queue_lock: + if self._current_goal is not None: + # Put incoming goal in the queue + self._goal_queue.append(goal_handle) + self.get_logger().info('Goal put in the queue') + else: + # Start goal execution right away + self._current_goal = goal_handle + self._current_goal.execute() + + def goal_callback(self, goal_request): + """Accept or reject a client request to begin an action.""" + self.get_logger().info('Received goal request') + return GoalResponse.ACCEPT + + def cancel_callback(self, goal_handle): + """Accept or reject a client request to cancel an action.""" + self.get_logger().info('Received cancel request') + return CancelResponse.ACCEPT + + def execute_callback(self, goal_handle): + """Execute a goal.""" + try: + self.get_logger().info('Executing goal...') + + # Append the seeds for the Fibonacci sequence + feedback_msg = Fibonacci.Feedback() + feedback_msg.sequence = [0, 1] + + # Start executing the action + for i in range(1, goal_handle.request.order): + if goal_handle.is_cancel_requested: + goal_handle.canceled() + self.get_logger().info('Goal canceled') + return Fibonacci.Result() + + # Update Fibonacci sequence + feedback_msg.sequence.append( + feedback_msg.sequence[i] + feedback_msg.sequence[i-1]) + + self.get_logger().info( + 'Publishing feedback: {0}'.format(feedback_msg.sequence)) + + # Publish the feedback + goal_handle.publish_feedback(feedback_msg) + + # Sleep for demonstration purposes + time.sleep(1) + + goal_handle.succeed() + + # Populate result message + result = Fibonacci.Result() + result.sequence = feedback_msg.sequence + + self.get_logger().info( + 'Returning result: {0}'.format(result.sequence)) + + return result + finally: + with self._goal_queue_lock: + try: + # Start execution of the next goal in the queue. + self._current_goal = self._goal_queue.popleft() + self.get_logger().info('Next goal pulled from the queue') + self._current_goal.execute() + except IndexError: + # No goal in the queue. + self._current_goal = None + + +def main(args=None): + rclpy.init(args=args) + + minimal_action_server = MinimalActionServer() + + executor = MultiThreadedExecutor() + + rclpy.spin(minimal_action_server, executor=executor) + + minimal_action_server.destroy() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/server_single_goal.py b/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/server_single_goal.py new file mode 100644 index 0000000..38ea732 --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_server/examples_rclpy_minimal_action_server/server_single_goal.py @@ -0,0 +1,131 @@ +# Copyright 2019 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. + +import threading +import time + +from example_interfaces.action import Fibonacci + +import rclpy +from rclpy.action import ActionServer, CancelResponse, GoalResponse +from rclpy.callback_groups import ReentrantCallbackGroup +from rclpy.executors import MultiThreadedExecutor +from rclpy.node import Node + + +class MinimalActionServer(Node): + """Minimal action server that processes one goal at a time.""" + + def __init__(self): + super().__init__('minimal_action_server') + self._goal_handle = None + self._goal_lock = threading.Lock() + self._action_server = ActionServer( + self, + Fibonacci, + 'fibonacci', + execute_callback=self.execute_callback, + goal_callback=self.goal_callback, + handle_accepted_callback=self.handle_accepted_callback, + cancel_callback=self.cancel_callback, + callback_group=ReentrantCallbackGroup()) + + def destroy(self): + self._action_server.destroy() + super().destroy_node() + + def goal_callback(self, goal_request): + """Accept or reject a client request to begin an action.""" + self.get_logger().info('Received goal request') + return GoalResponse.ACCEPT + + def handle_accepted_callback(self, goal_handle): + with self._goal_lock: + # This server only allows one goal at a time + if self._goal_handle is not None and self._goal_handle.is_active: + self.get_logger().info('Aborting previous goal') + # Abort the existing goal + self._goal_handle.abort() + self._goal_handle = goal_handle + + goal_handle.execute() + + def cancel_callback(self, goal): + """Accept or reject a client request to cancel an action.""" + self.get_logger().info('Received cancel request') + return CancelResponse.ACCEPT + + def execute_callback(self, goal_handle): + """Execute the goal.""" + self.get_logger().info('Executing goal...') + + # Append the seeds for the Fibonacci sequence + feedback_msg = Fibonacci.Feedback() + feedback_msg.sequence = [0, 1] + + # Start executing the action + for i in range(1, goal_handle.request.order): + # If goal is flagged as no longer active (ie. another goal was accepted), + # then stop executing + if not goal_handle.is_active: + self.get_logger().info('Goal aborted') + return Fibonacci.Result() + + if goal_handle.is_cancel_requested: + goal_handle.canceled() + self.get_logger().info('Goal canceled') + return Fibonacci.Result() + + # Update Fibonacci sequence + feedback_msg.sequence.append(feedback_msg.sequence[i] + feedback_msg.sequence[i-1]) + + self.get_logger().info('Publishing feedback: {0}'.format(feedback_msg.sequence)) + + # Publish the feedback + goal_handle.publish_feedback(feedback_msg) + + # Sleep for demonstration purposes + time.sleep(1) + + with self._goal_lock: + if not goal_handle.is_active: + self.get_logger().info('Goal aborted') + return Fibonacci.Result() + + goal_handle.succeed() + + # Populate result message + result = Fibonacci.Result() + result.sequence = feedback_msg.sequence + + self.get_logger().info('Returning result: {0}'.format(result.sequence)) + + return result + + +def main(args=None): + rclpy.init(args=args) + + action_server = MinimalActionServer() + + # We use a MultiThreadedExecutor to handle incoming goal requests concurrently + executor = MultiThreadedExecutor() + rclpy.spin(action_server, executor=executor) + + action_server.destroy() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/actions/minimal_action_server/package.xml b/src/example/rclpy/actions/minimal_action_server/package.xml new file mode 100644 index 0000000..c255937 --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_server/package.xml @@ -0,0 +1,29 @@ + + + + examples_rclpy_minimal_action_server + 0.19.6 + Examples of minimal action servers using rclpy. + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Jacob Perron + Shane Loretz + + example_interfaces + rclpy + + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/example/rclpy/actions/minimal_action_server/resource/examples_rclpy_minimal_action_server b/src/example/rclpy/actions/minimal_action_server/resource/examples_rclpy_minimal_action_server new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/actions/minimal_action_server/setup.cfg b/src/example/rclpy/actions/minimal_action_server/setup.cfg new file mode 100644 index 0000000..0f87d4a --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_server/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/examples_rclpy_minimal_action_server +[install] +install_scripts=$base/lib/examples_rclpy_minimal_action_server diff --git a/src/example/rclpy/actions/minimal_action_server/setup.py b/src/example/rclpy/actions/minimal_action_server/setup.py new file mode 100644 index 0000000..1569fdd --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_server/setup.py @@ -0,0 +1,39 @@ +from setuptools import setup + +package_name = 'examples_rclpy_minimal_action_server' + +setup( + name=package_name, + version='0.19.6', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + author='Jacob Perron', + author_email='jacob@openrobotics.org', + maintainer='Aditya Pande, Alejandro Hernandez Cordero', + maintainer_email='aditya.pande@openrobotics.org, alejandro@openrobotics.org', + keywords=['ROS'], + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Topic :: Software Development', + ], + description='Examples of action servers using rclpy.', + license='Apache License, Version 2.0', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'server = ' + package_name + '.server:main', + 'server_defer = ' + package_name + '.server_defer:main', + 'server_not_composable = ' + package_name + '.server_not_composable:main', + 'server_queue_goals = ' + package_name + '.server_queue_goals:main', + 'server_single_goal = ' + package_name + '.server_single_goal:main', + ], + }, +) diff --git a/src/example/rclpy/actions/minimal_action_server/test/test_copyright.py b/src/example/rclpy/actions/minimal_action_server/test/test_copyright.py new file mode 100644 index 0000000..8959088 --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_server/test/test_copyright.py @@ -0,0 +1,24 @@ +# Copyright 2017 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. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + # Test is called from package root + rc = main(argv=['.']) + assert rc == 0, 'Found errors' diff --git a/src/example/rclpy/actions/minimal_action_server/test/test_flake8.py b/src/example/rclpy/actions/minimal_action_server/test/test_flake8.py new file mode 100644 index 0000000..76ccb0f --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_server/test/test_flake8.py @@ -0,0 +1,26 @@ +# Copyright 2017 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. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + # Test is called from package root + rc, errors = main_with_errors(argv=['.']) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/example/rclpy/actions/minimal_action_server/test/test_pep257.py b/src/example/rclpy/actions/minimal_action_server/test/test_pep257.py new file mode 100644 index 0000000..18a66da --- /dev/null +++ b/src/example/rclpy/actions/minimal_action_server/test/test_pep257.py @@ -0,0 +1,24 @@ +# Copyright 2017 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + # Test is called from package root + rc = main(argv=['.']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/example/rclpy/executors/CHANGELOG.rst b/src/example/rclpy/executors/CHANGELOG.rst new file mode 100644 index 0000000..505f1a5 --- /dev/null +++ b/src/example/rclpy/executors/CHANGELOG.rst @@ -0,0 +1,146 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclpy_executors +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Update maintainers to Aditya Pande and Shane Loretz (`#332 `_) +* Updated maintainers (`#329 `_) +* Update python nodes sigint/sigterm handling (`#330 `_) +* Contributors: Aditya Pande, Audrow Nash, Ivan Santiago Paunovic + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- +* Use underscores instead of dashes in setup.cfg (`#310 `_) +* Contributors: Ivan Santiago Paunovic + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ + +0.9.0 (2020-04-30) +------------------ +* more verbose test_flake8 error messages (same as `ros2/launch_ros#135 `_) +* Contributors: Dirk Thomas + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ + +0.7.3 (2019-05-29) +------------------ + +0.7.2 (2019-05-20) +------------------ +* Fix deprecation warnings (`#241 `_) +* Contributors: Jacob Perron + +0.7.1 (2019-05-08) +------------------ + +0.7.0 (2019-04-14) +------------------ + +0.6.2 (2019-02-08) +------------------ + +0.6.0 (2018-11-20) +------------------ +* No changes. + +0.5.1 (2018-06-27) +------------------ + +0.5.0 (2018-06-26) +------------------ +* add pytest markers to linter tests +* set zip_safe to avoid warning during installation (`#205 `_) +* Contributors: Dirk Thomas, Mikael Arguedas + +0.4.0 (2017-12-08) +------------------ +* Destroy nodes when the example is done (`#196 `_) +* wait_for_ready_callbacks returns a tuple now (`#194 `_) + `ros2/rclpy#159 `_ changed wait_for_ready_callbacks to manage the generator internally and return just a tuple +* Use logging (`#190 `_) +* Fix import statement and usage for rclpy.node.Node (`#189 `_) +* remove test_suite, add pytest as test_requires +* Follow up to executor example comments (`#184 `_) +* 0.0.3 +* remove Listener from the "ThrottledTalkerListener" name given that this is only a throttled talker (`#183 `_) +* Examples for Executors and callback groups (`#182 `_) +* Contributors: Dirk Thomas, Mikael Arguedas, Shane Loretz diff --git a/src/example/rclpy/executors/examples_rclpy_executors/__init__.py b/src/example/rclpy/executors/examples_rclpy_executors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/executors/examples_rclpy_executors/callback_group.py b/src/example/rclpy/executors/examples_rclpy_executors/callback_group.py new file mode 100644 index 0000000..a13c654 --- /dev/null +++ b/src/example/rclpy/executors/examples_rclpy_executors/callback_group.py @@ -0,0 +1,72 @@ +# Copyright 2017 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. + +from examples_rclpy_executors.listener import Listener +import rclpy +from rclpy.callback_groups import MutuallyExclusiveCallbackGroup +from rclpy.executors import MultiThreadedExecutor +from rclpy.node import Node +from std_msgs.msg import String + + +class DoubleTalker(Node): + """Publish messages to a topic using two publishers at different rates.""" + + def __init__(self): + super().__init__('double_talker') + + self.i = 0 + self.pub = self.create_publisher(String, 'chatter', 10) + + # This type of callback group only allows one callback to be executed at a time + self.group = MutuallyExclusiveCallbackGroup() + # Pass the group as a parameter to give it control over the execution of the timer callback + self.timer = self.create_timer(1.0, self.timer_callback, callback_group=self.group) + self.timer2 = self.create_timer(0.5, self.timer_callback, callback_group=self.group) + + def timer_callback(self): + msg = String() + msg.data = 'Hello World: {0}'.format(self.i) + self.i += 1 + self.get_logger().info('Publishing: "{0}"'.format(msg.data)) + self.pub.publish(msg) + + +def main(args=None): + rclpy.init(args=args) + try: + talker = DoubleTalker() + listener = Listener() + # MultiThreadedExecutor executes callbacks with a thread pool. If num_threads is not + # specified then num_threads will be multiprocessing.cpu_count() if it is implemented. + # Otherwise it will use a single thread. This executor will allow callbacks to happen in + # parallel, however the MutuallyExclusiveCallbackGroup in DoubleTalker will only allow its + # callbacks to be executed one at a time. The callbacks in Listener are free to execute in + # parallel to the ones in DoubleTalker however. + executor = MultiThreadedExecutor(num_threads=4) + executor.add_node(talker) + executor.add_node(listener) + + try: + executor.spin() + finally: + executor.shutdown() + listener.destroy_node() + talker.destroy_node() + finally: + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/executors/examples_rclpy_executors/composed.py b/src/example/rclpy/executors/examples_rclpy_executors/composed.py new file mode 100644 index 0000000..d203efd --- /dev/null +++ b/src/example/rclpy/executors/examples_rclpy_executors/composed.py @@ -0,0 +1,52 @@ +# Copyright 2017 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. + +import sys + +from examples_rclpy_executors.listener import Listener +from examples_rclpy_executors.talker import Talker +import rclpy +from rclpy.executors import ExternalShutdownException +from rclpy.executors import SingleThreadedExecutor + + +def main(args=None): + rclpy.init(args=args) + try: + talker = Talker() + listener = Listener() + + # Runs all callbacks in the main thread + executor = SingleThreadedExecutor() + # Add imported nodes to this executor + executor.add_node(talker) + executor.add_node(listener) + + try: + # Execute callbacks for both nodes as they become ready + executor.spin() + finally: + executor.shutdown() + listener.destroy_node() + talker.destroy_node() + except KeyboardInterrupt: + pass + except ExternalShutdownException: + sys.exit(1) + finally: + rclpy.try_shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/executors/examples_rclpy_executors/custom_callback_group.py b/src/example/rclpy/executors/examples_rclpy_executors/custom_callback_group.py new file mode 100644 index 0000000..2971b85 --- /dev/null +++ b/src/example/rclpy/executors/examples_rclpy_executors/custom_callback_group.py @@ -0,0 +1,116 @@ +# Copyright 2017 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. + +import sys +import threading + +import rclpy +from rclpy.callback_groups import CallbackGroup +from rclpy.executors import ExternalShutdownException +from rclpy.node import Node +from std_msgs.msg import String + + +class ThrottledCallbackGroup(CallbackGroup): + """ + Throttle callbacks using a token bucket. + + Callback groups are responsible for controlling when callbacks are allowed to be executed. + rclpy provides two groups: one which always allows a callback to be executed, and another which + allows only one callback to be executed at a time. If neither of these are sufficient then a + custom callback group should be used instead. + """ + + def __init__(self, node): + super().__init__() + self.timer = node.create_timer(0.5, self.timer_callback) + self.bucket = 10 + self.bucket_max = 10 + self.lock = threading.Lock() + + def can_execute(self, entity): + """ + Ask group if this entity could be executed. + + :param entity: A timer, subscriber, client, or service instance + :rtype bool: true if a callback can be executed + """ + return self.bucket > 0 + + def beginning_execution(self, entity): + """ + Get permission from the group to execute a callback for an entity. + + :param entity: A timer, subscriber, client, or service instance + :rtype bool: true if the executor has permission to execute it + """ + with self.lock: + if self.bucket > 0: + # Take a token + self.bucket -= 1 + return True + # The bucket has no tokens + return False + + def ending_execution(self, entity): + """ + Notify group that a callback finished executing. + + :param entity: A timer, subscriber, client, or service instance + """ + pass + + def timer_callback(self): + """Replenish the tokens in the bucket at a steady rate.""" + with self.lock: + # If there is room in the bucket, add a token to it. + if self.bucket < self.bucket_max: + self.bucket += 1 + + +class ThrottledTalker(Node): + """A Node which uses a custom callback group.""" + + def __init__(self): + super().__init__('intermittent_talker') + self.i = 0 + self.pub = self.create_publisher(String, 'chatter', 10) + self.group = ThrottledCallbackGroup(self) + # Timer triggers very quickly, but is part of a throttled group + self.timer = self.create_timer(0.1, self.timer_callback, callback_group=self.group) + + def timer_callback(self): + msg = String() + msg.data = 'Hello World: {0}'.format(self.i) + self.i += 1 + self.get_logger().info('Publishing: "{0}"'.format(msg.data)) + self.pub.publish(msg) + + +def main(args=None): + rclpy.init(args=args) + try: + talker = ThrottledTalker() + rclpy.spin(talker) + except KeyboardInterrupt: + pass + except ExternalShutdownException: + sys.exit(1) + finally: + rclpy.try_shutdown() + talker.destroy_node() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/executors/examples_rclpy_executors/custom_executor.py b/src/example/rclpy/executors/examples_rclpy_executors/custom_executor.py new file mode 100644 index 0000000..5a3d8ad --- /dev/null +++ b/src/example/rclpy/executors/examples_rclpy_executors/custom_executor.py @@ -0,0 +1,103 @@ +# Copyright 2017 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. + +from concurrent.futures import ThreadPoolExecutor +import os + +from examples_rclpy_executors.listener import Listener +from examples_rclpy_executors.talker import Talker +import rclpy +from rclpy.executors import Executor +from rclpy.node import Node +from std_msgs.msg import String + + +class Estopper(Node): + + def __init__(self): + super().__init__('estopper') + self.sub = self.create_subscription(String, 'estop', self.estop_callback, 10) + + def estop_callback(self, msg): + self.get_logger().info('I heard: "%s"' % msg.data) + + +class PriorityExecutor(Executor): + """ + Execute high priority callbacks in multiple threads, all others in a single thread. + + This is an example of a custom exectuor in python. Executors are responsible for managing + how callbacks get mapped to threads. Rclpy provides two executors: one which runs all callbacks + in the main thread, and another which runs callbacks in a pool of threads. A custom executor + should be written if neither are appropriate for your application. + """ + + def __init__(self): + super().__init__() + self.high_priority_nodes = set() + self.hp_executor = ThreadPoolExecutor(max_workers=os.cpu_count() or 4) + self.lp_executor = ThreadPoolExecutor(max_workers=1) + + def add_high_priority_node(self, node): + self.high_priority_nodes.add(node) + # add_node inherited + self.add_node(node) + + def spin_once(self, timeout_sec=None): + """ + Execute a single callback, then return. + + This is the only function which must be overridden by a custom executor. Its job is to + start executing one callback, then return. It uses the method `wait_for_ready_callbacks` + to get work to execute. + + :param timeout_sec: Seconds to wait. Block forever if None. Don't wait if <= 0 + :type timeout_sec: float or None + """ + # wait_for_ready_callbacks yields callbacks that are ready to be executed + try: + handler, group, node = self.wait_for_ready_callbacks(timeout_sec=timeout_sec) + except StopIteration: + pass + else: + if node in self.high_priority_nodes: + self.hp_executor.submit(handler) + else: + self.lp_executor.submit(handler) + + +def main(args=None): + rclpy.init(args=args) + try: + listener = Listener() + talker = Talker() + estopper = Estopper() + + executor = PriorityExecutor() + executor.add_high_priority_node(estopper) + executor.add_node(listener) + executor.add_node(talker) + try: + executor.spin() + finally: + executor.shutdown() + estopper.destroy_node() + talker.destroy_node() + listener.destroy_node() + finally: + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/executors/examples_rclpy_executors/listener.py b/src/example/rclpy/executors/examples_rclpy_executors/listener.py new file mode 100644 index 0000000..9cb5dc2 --- /dev/null +++ b/src/example/rclpy/executors/examples_rclpy_executors/listener.py @@ -0,0 +1,66 @@ +# Copyright 2017 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. + +import sys + +import rclpy +from rclpy.executors import ExternalShutdownException +from rclpy.node import Node +from std_msgs.msg import String + + +class Listener(Node): + """ + A node with a single subscriber. + + This class creates a node which prints messages it receives on a topic. Creating a node by + inheriting from Node is recommended because it allows it to be imported and used by + other scripts. + """ + + def __init__(self): + # Calls Node.__init__('listener') + super().__init__('listener') + self.sub = self.create_subscription(String, 'chatter', self.chatter_callback, 10) + + def chatter_callback(self, msg): + self.get_logger().info('I heard: "%s"' % msg.data) + + +def main(args=None): + """ + Run a Listener node standalone. + + This function is called directly when using an entrypoint. Entrypoints are configured in + setup.py. This along with the script installation in setup.cfg allows a listener node to be run + with the command `ros2 run examples_rclpy_executors listener`. + + :param args: Arguments passed in from the command line. + """ + rclpy.init(args=args) + try: + listener = Listener() + rclpy.spin(listener) + except KeyboardInterrupt: + pass + except ExternalShutdownException: + sys.exit(1) + finally: + rclpy.try_shutdown() + listener.destroy_node() + + +if __name__ == '__main__': + # Runs a listener node when this script is run directly (not through an entrypoint) + main() diff --git a/src/example/rclpy/executors/examples_rclpy_executors/talker.py b/src/example/rclpy/executors/examples_rclpy_executors/talker.py new file mode 100644 index 0000000..73ce23c --- /dev/null +++ b/src/example/rclpy/executors/examples_rclpy_executors/talker.py @@ -0,0 +1,76 @@ +# Copyright 2017 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. + +import sys + +import rclpy +from rclpy.executors import ExternalShutdownException +from rclpy.node import Node +from std_msgs.msg import String + + +class Talker(Node): + """ + A node with a single publisher. + + This class creates a node which regularly publishes messages on a topic. Creating a node by + inheriting from Node is recommended because it allows it to be imported and used by + other scripts. + """ + + def __init__(self): + # Calls Node.__init__('talker') + super().__init__('talker') + self.i = 0 + self.pub = self.create_publisher(String, 'chatter', 10) + # Create a timer that calls a callback every second. A timer is recommended for executing + # periodic tasks because it does not block the main thread while it's waiting. This allows + # an executor to do other work when mutliple nodes are run in the same process. + self.timer = self.create_timer(1.0, self.timer_callback) + + def timer_callback(self): + msg = String() + msg.data = 'Hello World: {0}'.format(self.i) + self.i += 1 + self.get_logger().info('Publishing: "{0}"'.format(msg.data)) + self.pub.publish(msg) + + +def main(args=None): + """ + Run a Talker node standalone. + + This function is called directly when using an entrypoint. Entrypoints are configured in + setup.py. This along with the script installation in setup.cfg allows a talker node to be run + with the command `ros2 run examples_rclpy_executors talker`. + + :param args: Arguments passed in from the command line. + """ + # Run standalone + rclpy.init(args=args) + try: + talker = Talker() + rclpy.spin(talker) + except KeyboardInterrupt: + pass + except ExternalShutdownException: + sys.exit(1) + finally: + rclpy.try_shutdown() + talker.destroy_node() + + +if __name__ == '__main__': + # Runs a talker node when this script is run directly (not through an entrypoint) + main() diff --git a/src/example/rclpy/executors/package.xml b/src/example/rclpy/executors/package.xml new file mode 100644 index 0000000..f215bf1 --- /dev/null +++ b/src/example/rclpy/executors/package.xml @@ -0,0 +1,27 @@ + + + + examples_rclpy_executors + 0.19.6 + Examples of creating and using exectors to run multiple nodes in the same process + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Aditya Pande + Shane Loretz + + rclpy + std_msgs + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/example/rclpy/executors/resource/examples_rclpy_executors b/src/example/rclpy/executors/resource/examples_rclpy_executors new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/executors/setup.cfg b/src/example/rclpy/executors/setup.cfg new file mode 100644 index 0000000..f400b81 --- /dev/null +++ b/src/example/rclpy/executors/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/examples_rclpy_executors +[install] +install_scripts=$base/lib/examples_rclpy_executors diff --git a/src/example/rclpy/executors/setup.py b/src/example/rclpy/executors/setup.py new file mode 100644 index 0000000..6975dd3 --- /dev/null +++ b/src/example/rclpy/executors/setup.py @@ -0,0 +1,39 @@ +from setuptools import setup + +package_name = 'examples_rclpy_executors' + +setup( + name=package_name, + version='0.19.6', + packages=['examples_rclpy_executors'], + data_files=[ + ('share/ament_index/resource_index/packages', ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + author='Shane Loretz', + author_email='sloretz@openrobotics.org', + maintainer='Aditya Pande, Alejandro Hernandez Cordero', + maintainer_email='aditya.pande@openrobotics.org, alejandro@openrobotics.org', + keywords=['ROS'], + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Topic :: Software Development', + ], + description='Examples of creating and using exectors to run multiple nodes in rclpy.', + license='Apache License, Version 2.0', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'listener = examples_rclpy_executors.listener:main', + 'talker = examples_rclpy_executors.talker:main', + 'callback_group = examples_rclpy_executors.callback_group:main', + 'composed = examples_rclpy_executors.composed:main', + 'custom_executor = examples_rclpy_executors.custom_executor:main', + 'custom_callback_group = examples_rclpy_executors.custom_callback_group:main', + ], + }, +) diff --git a/src/example/rclpy/executors/test/test_copyright.py b/src/example/rclpy/executors/test/test_copyright.py new file mode 100644 index 0000000..8959088 --- /dev/null +++ b/src/example/rclpy/executors/test/test_copyright.py @@ -0,0 +1,24 @@ +# Copyright 2017 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. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + # Test is called from package root + rc = main(argv=['.']) + assert rc == 0, 'Found errors' diff --git a/src/example/rclpy/executors/test/test_flake8.py b/src/example/rclpy/executors/test/test_flake8.py new file mode 100644 index 0000000..76ccb0f --- /dev/null +++ b/src/example/rclpy/executors/test/test_flake8.py @@ -0,0 +1,26 @@ +# Copyright 2017 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. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + # Test is called from package root + rc, errors = main_with_errors(argv=['.']) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/example/rclpy/executors/test/test_pep257.py b/src/example/rclpy/executors/test/test_pep257.py new file mode 100644 index 0000000..18a66da --- /dev/null +++ b/src/example/rclpy/executors/test/test_pep257.py @@ -0,0 +1,24 @@ +# Copyright 2017 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + # Test is called from package root + rc = main(argv=['.']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/example/rclpy/guard_conditions/CHANGELOG.rst b/src/example/rclpy/guard_conditions/CHANGELOG.rst new file mode 100644 index 0000000..0a5484a --- /dev/null +++ b/src/example/rclpy/guard_conditions/CHANGELOG.rst @@ -0,0 +1,86 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclpy_guard_conditions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Update maintainers to Aditya Pande and Shane Loretz (`#332 `_) +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande, Audrow Nash + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- +* Use underscores instead of dashes in setup.cfg (`#310 `_) +* Contributors: Ivan Santiago Paunovic + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- +* [rclpy] Create a package with an example showing how guard conditions work (`#283 `_) +* Contributors: Audrow Nash diff --git a/src/example/rclpy/guard_conditions/examples_rclpy_guard_conditions/__init__.py b/src/example/rclpy/guard_conditions/examples_rclpy_guard_conditions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/guard_conditions/examples_rclpy_guard_conditions/trigger_guard_condition.py b/src/example/rclpy/guard_conditions/examples_rclpy_guard_conditions/trigger_guard_condition.py new file mode 100644 index 0000000..9d7e7c9 --- /dev/null +++ b/src/example/rclpy/guard_conditions/examples_rclpy_guard_conditions/trigger_guard_condition.py @@ -0,0 +1,48 @@ +# Copyright 2020 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. + +import rclpy + + +def main(args=None): + + rclpy.init(args=args) + node = rclpy.create_node('demo_guard_condition') + executor = rclpy.executors.SingleThreadedExecutor() + executor.add_node(node) + + def guard_condition_callback(): + rclpy.shutdown() + node.get_logger().info('guard callback called shutdown') + + def timer_callback(): + guard_condition.trigger() + node.get_logger().info('timer callback triggered guard condition') + + node.create_timer(timer_period_sec=2, callback=timer_callback) + guard_condition = node.create_guard_condition(guard_condition_callback) + + while rclpy.ok(): + # First loop: `spin_once` waits for timer to be ready, then calls + # the timer's callback, which triggers the guard condition. + # Second loop: The guard condition is ready so it's callback is + # called. The callback calls shutdown, so the loop doesn't run + # again and the program exits. + node.get_logger().info("waiting for 'spin_once' to finish...") + executor.spin_once() + node.get_logger().info("...'spin_once' finished!\n") + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/guard_conditions/package.xml b/src/example/rclpy/guard_conditions/package.xml new file mode 100644 index 0000000..8b59db2 --- /dev/null +++ b/src/example/rclpy/guard_conditions/package.xml @@ -0,0 +1,26 @@ + + + + examples_rclpy_guard_conditions + 0.19.6 + Examples of using guard conditions. + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Audrow Nash + Shane Loretz + + rclpy + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/example/rclpy/guard_conditions/resource/examples_rclpy_guard_conditions b/src/example/rclpy/guard_conditions/resource/examples_rclpy_guard_conditions new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/guard_conditions/setup.cfg b/src/example/rclpy/guard_conditions/setup.cfg new file mode 100644 index 0000000..1c3ca23 --- /dev/null +++ b/src/example/rclpy/guard_conditions/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/examples_rclpy_guard_conditions +[install] +install_scripts=$base/lib/examples_rclpy_guard_conditions diff --git a/src/example/rclpy/guard_conditions/setup.py b/src/example/rclpy/guard_conditions/setup.py new file mode 100644 index 0000000..f9897b3 --- /dev/null +++ b/src/example/rclpy/guard_conditions/setup.py @@ -0,0 +1,33 @@ +from setuptools import setup + +package_name = 'examples_rclpy_guard_conditions' + +setup( + name=package_name, + version='0.19.6', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='Aditya Pande, Alejandro Hernandez Cordero', + maintainer_email='aditya.pande@openrobotics.org, alejandro@openrobotics.org', + keywords=['ROS'], + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Topic :: Software Development', + ], + description='Examples of using guard conditions.', + license='Apache License, Version 2.0', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'trigger_guard_condition = ' + 'examples_rclpy_guard_conditions.trigger_guard_condition:main' + ], + }, +) diff --git a/src/example/rclpy/guard_conditions/test/test_copyright.py b/src/example/rclpy/guard_conditions/test/test_copyright.py new file mode 100644 index 0000000..cc8ff03 --- /dev/null +++ b/src/example/rclpy/guard_conditions/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/example/rclpy/guard_conditions/test/test_flake8.py b/src/example/rclpy/guard_conditions/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/src/example/rclpy/guard_conditions/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 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. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/example/rclpy/guard_conditions/test/test_pep257.py b/src/example/rclpy/guard_conditions/test/test_pep257.py new file mode 100644 index 0000000..b234a38 --- /dev/null +++ b/src/example/rclpy/guard_conditions/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/example/rclpy/pytest.ini b/src/example/rclpy/pytest.ini new file mode 100644 index 0000000..fe55d2e --- /dev/null +++ b/src/example/rclpy/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +junit_family=xunit2 diff --git a/src/example/rclpy/services/minimal_client/CHANGELOG.rst b/src/example/rclpy/services/minimal_client/CHANGELOG.rst new file mode 100644 index 0000000..75263eb --- /dev/null +++ b/src/example/rclpy/services/minimal_client/CHANGELOG.rst @@ -0,0 +1,160 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclpy_minimal_client +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Update maintainers to Aditya Pande and Shane Loretz (`#332 `_) +* Updated maintainers (`#329 `_) + * Updated maintainers + * Removed author +* Contributors: Aditya Pande, Audrow Nash + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- +* Use underscores instead of dashes in setup.cfg (`#310 `_) +* Contributors: Ivan Santiago Paunovic + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- +* Remove bare exception catching (`#299 `_) +* Contributors: Shane Loretz + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ + +0.9.0 (2020-04-30) +------------------ +* more verbose test_flake8 error messages (same as `ros2/launch_ros#135 `_) +* Contributors: Dirk Thomas + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ +* future.result() raises if it fails (`#253 `_) +* Contributors: Shane Loretz + +0.7.3 (2019-05-29) +------------------ + +0.7.2 (2019-05-20) +------------------ + +0.7.1 (2019-05-08) +------------------ + +0.7.0 (2019-04-14) +------------------ + +0.6.2 (2019-02-08) +------------------ +* Modified examples to install entry point scripts into a package. (`#226 `_) +* Contributors: Shane Loretz + +0.6.0 (2018-11-20) +------------------ +* Updated maintainer info. (`#218 `_) +* Contributors: Shane Loretz + +0.5.1 (2018-06-27) +------------------ + +0.5.0 (2018-06-26) +------------------ +* add pytest markers to linter tests +* Update examples for new client api (`#203 `_) +* set zip_safe to avoid warning during installation (`#205 `_) +* Contributors: Dirk Thomas, Mikael Arguedas, Shane Loretz + +0.4.0 (2017-12-08) +------------------ +* Use logging (`#190 `_) +* Use wait_for_service (`#185 `_) +* Fix import statement and usage for rclpy.node.Node (`#189 `_) +* remove test_suite, add pytest as test_requires +* 0.0.3 +* Examples for Executors and callback groups (`#182 `_) +* update style to satisfy new flake8 plugins `#181 `_ +* remove dependency on ament_python and perform customizations in setup.py `#178 `_ +* 0.0.2 +* rename executables with shorter names (`#177 `_) +* install data_files `#176 `_ +* fix error when exiting scripts `#175 `_ +* install executables in package specific path `#173 `_ +* use explicit kwargs `#169 `_ +* fix function name (`#168 `_) +* comply with flake8 import order (`#167 `_) +* Rclpy minimal services (`#140 `_) +* Contributors: Dirk Thomas, Mikael Arguedas, Shane Loretz diff --git a/src/example/rclpy/services/minimal_client/README.md b/src/example/rclpy/services/minimal_client/README.md new file mode 100644 index 0000000..3f30124 --- /dev/null +++ b/src/example/rclpy/services/minimal_client/README.md @@ -0,0 +1,6 @@ +# Minimal service client cookbook recipes + +This package contains a few strategies to create service clients. +The `client` recipe shows how to request data from a service with a blocking call. +The `client_async` recipe shows how to request data from a service with a non blocking call. The user has to check if a response has been received in the main loop +The `client_async_member_function` recipe is analog to `client_async` but sends the request inside a MinimalClient class diff --git a/src/example/rclpy/services/minimal_client/examples_rclpy_minimal_client/__init__.py b/src/example/rclpy/services/minimal_client/examples_rclpy_minimal_client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/services/minimal_client/examples_rclpy_minimal_client/client.py b/src/example/rclpy/services/minimal_client/examples_rclpy_minimal_client/client.py new file mode 100644 index 0000000..16ed590 --- /dev/null +++ b/src/example/rclpy/services/minimal_client/examples_rclpy_minimal_client/client.py @@ -0,0 +1,44 @@ +# Copyright 2016 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. + +from example_interfaces.srv import AddTwoInts + +import rclpy + + +def main(args=None): + rclpy.init(args=args) + node = rclpy.create_node('minimal_client') + cli = node.create_client(AddTwoInts, 'add_two_ints') + + req = AddTwoInts.Request() + req.a = 41 + req.b = 1 + while not cli.wait_for_service(timeout_sec=1.0): + node.get_logger().info('service not available, waiting again...') + + future = cli.call_async(req) + rclpy.spin_until_future_complete(node, future) + + result = future.result() + node.get_logger().info( + 'Result of add_two_ints: for %d + %d = %d' % + (req.a, req.b, result.sum)) + + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/services/minimal_client/examples_rclpy_minimal_client/client_async.py b/src/example/rclpy/services/minimal_client/examples_rclpy_minimal_client/client_async.py new file mode 100644 index 0000000..e154d4f --- /dev/null +++ b/src/example/rclpy/services/minimal_client/examples_rclpy_minimal_client/client_async.py @@ -0,0 +1,48 @@ +# Copyright 2016 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. + +from example_interfaces.srv import AddTwoInts + +import rclpy + + +def main(args=None): + rclpy.init(args=args) + + node = rclpy.create_node('minimal_client_async') + + cli = node.create_client(AddTwoInts, 'add_two_ints') + + req = AddTwoInts.Request() + req.a = 41 + req.b = 1 + while not cli.wait_for_service(timeout_sec=1.0): + node.get_logger().info('service not available, waiting again...') + + future = cli.call_async(req) + while rclpy.ok(): + rclpy.spin_once(node) + if future.done(): + result = future.result() + node.get_logger().info( + 'Result of add_two_ints: for %d + %d = %d' % + (req.a, req.b, result.sum)) + break + + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/services/minimal_client/examples_rclpy_minimal_client/client_async_callback.py b/src/example/rclpy/services/minimal_client/examples_rclpy_minimal_client/client_async_callback.py new file mode 100644 index 0000000..7e8cd88 --- /dev/null +++ b/src/example/rclpy/services/minimal_client/examples_rclpy_minimal_client/client_async_callback.py @@ -0,0 +1,67 @@ +# 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. + +from example_interfaces.srv import AddTwoInts + +import rclpy +from rclpy.callback_groups import ReentrantCallbackGroup + + +def main(args=None): + rclpy.init(args=args) + node = rclpy.create_node('minimal_client') + # Node's default callback group is mutually exclusive. This would prevent the client response + # from being processed until the timer callback finished, but the timer callback in this + # example is waiting for the client response + cb_group = ReentrantCallbackGroup() + cli = node.create_client(AddTwoInts, 'add_two_ints', callback_group=cb_group) + did_run = False + did_get_result = False + + async def call_service(): + nonlocal cli, node, did_run, did_get_result + did_run = True + try: + req = AddTwoInts.Request() + req.a = 41 + req.b = 1 + future = cli.call_async(req) + result = await future + node.get_logger().info( + 'Result of add_two_ints: for %d + %d = %d' % + (req.a, req.b, result.sum)) + finally: + did_get_result = True + + while not cli.wait_for_service(timeout_sec=1.0): + node.get_logger().info('service not available, waiting again...') + + timer = node.create_timer(0.5, call_service, callback_group=cb_group) + + while rclpy.ok() and not did_run: + rclpy.spin_once(node) + + if did_run: + # call timer callback only once + timer.cancel() + + while rclpy.ok() and not did_get_result: + rclpy.spin_once(node) + + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/services/minimal_client/examples_rclpy_minimal_client/client_async_member_function.py b/src/example/rclpy/services/minimal_client/examples_rclpy_minimal_client/client_async_member_function.py new file mode 100644 index 0000000..ae7fc9d --- /dev/null +++ b/src/example/rclpy/services/minimal_client/examples_rclpy_minimal_client/client_async_member_function.py @@ -0,0 +1,56 @@ +# Copyright 2016 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. + +from example_interfaces.srv import AddTwoInts + +import rclpy +from rclpy.node import Node + + +class MinimalClientAsync(Node): + + def __init__(self): + super().__init__('minimal_client_async') + self.cli = self.create_client(AddTwoInts, 'add_two_ints') + while not self.cli.wait_for_service(timeout_sec=1.0): + self.get_logger().info('service not available, waiting again...') + self.req = AddTwoInts.Request() + + def send_request(self): + self.req.a = 41 + self.req.b = 1 + self.future = self.cli.call_async(self.req) + + +def main(args=None): + rclpy.init(args=args) + + minimal_client = MinimalClientAsync() + minimal_client.send_request() + + while rclpy.ok(): + rclpy.spin_once(minimal_client) + if minimal_client.future.done(): + response = minimal_client.future.result() + minimal_client.get_logger().info( + 'Result of add_two_ints: for %d + %d = %d' % + (minimal_client.req.a, minimal_client.req.b, response.sum)) + break + + minimal_client.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/services/minimal_client/package.xml b/src/example/rclpy/services/minimal_client/package.xml new file mode 100644 index 0000000..dcc378f --- /dev/null +++ b/src/example/rclpy/services/minimal_client/package.xml @@ -0,0 +1,30 @@ + + + + examples_rclpy_minimal_client + 0.19.6 + Examples of minimal service clients using rclpy. + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Aditya Pande + Shane Loretz + + example_interfaces + rclpy + std_msgs + + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/example/rclpy/services/minimal_client/resource/examples_rclpy_minimal_client b/src/example/rclpy/services/minimal_client/resource/examples_rclpy_minimal_client new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/services/minimal_client/setup.cfg b/src/example/rclpy/services/minimal_client/setup.cfg new file mode 100644 index 0000000..1aebd08 --- /dev/null +++ b/src/example/rclpy/services/minimal_client/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/examples_rclpy_minimal_client +[install] +install_scripts=$base/lib/examples_rclpy_minimal_client diff --git a/src/example/rclpy/services/minimal_client/setup.py b/src/example/rclpy/services/minimal_client/setup.py new file mode 100644 index 0000000..d435712 --- /dev/null +++ b/src/example/rclpy/services/minimal_client/setup.py @@ -0,0 +1,39 @@ +from setuptools import setup + +package_name = 'examples_rclpy_minimal_client' + +setup( + name=package_name, + version='0.19.6', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + author='Mikael Arguedas', + author_email='mikael@osrfoundation.org', + maintainer='Aditya Pande, Alejandro Hernandez Cordero', + maintainer_email='aditya.pande@openrobotics.org, alejandro@openrobotics.org', + keywords=['ROS'], + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Topic :: Software Development', + ], + description='Examples of minimal service clients using rclpy.', + license='Apache License, Version 2.0', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'client = examples_rclpy_minimal_client.client:main', + 'client_async = examples_rclpy_minimal_client.client_async:main', + 'client_async_member_function =' + ' examples_rclpy_minimal_client.client_async_member_function:main', + 'client_async_callback = examples_rclpy_minimal_client.client_async_callback:main', + ], + }, +) diff --git a/src/example/rclpy/services/minimal_client/test/test_copyright.py b/src/example/rclpy/services/minimal_client/test/test_copyright.py new file mode 100644 index 0000000..aa74f3e --- /dev/null +++ b/src/example/rclpy/services/minimal_client/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.']) + assert rc == 0, 'Found errors' diff --git a/src/example/rclpy/services/minimal_client/test/test_flake8.py b/src/example/rclpy/services/minimal_client/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/src/example/rclpy/services/minimal_client/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 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. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/example/rclpy/services/minimal_client/test/test_pep257.py b/src/example/rclpy/services/minimal_client/test/test_pep257.py new file mode 100644 index 0000000..399afc9 --- /dev/null +++ b/src/example/rclpy/services/minimal_client/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/example/rclpy/services/minimal_service/CHANGELOG.rst b/src/example/rclpy/services/minimal_service/CHANGELOG.rst new file mode 100644 index 0000000..c9f1773 --- /dev/null +++ b/src/example/rclpy/services/minimal_service/CHANGELOG.rst @@ -0,0 +1,152 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclpy_minimal_service +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Update maintainers to Aditya Pande and Shane Loretz (`#332 `_) +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande, Audrow Nash + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- +* Use underscores instead of dashes in setup.cfg (`#310 `_) +* Contributors: Ivan Santiago Paunovic + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ + +0.9.0 (2020-04-30) +------------------ +* more verbose test_flake8 error messages (same as `ros2/launch_ros#135 `_) +* Contributors: Dirk Thomas + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ + +0.7.3 (2019-05-29) +------------------ + +0.7.2 (2019-05-20) +------------------ + +0.7.1 (2019-05-08) +------------------ + +0.7.0 (2019-04-14) +------------------ + +0.6.2 (2019-02-08) +------------------ +* Modified examples to install entry point scripts into a package. (`#226 `_) +* Contributors: Shane Loretz + +0.6.0 (2018-11-20) +------------------ +* Updated maintainer info. (`#218 `_) +* Contributors: Shane Loretz + +0.5.1 (2018-06-27) +------------------ + +0.5.0 (2018-06-26) +------------------ +* add pytest markers to linter tests +* set zip_safe to avoid warning during installation (`#205 `_) +* Contributors: Dirk Thomas, Mikael Arguedas + +0.4.0 (2017-12-08) +------------------ +* Use logging (`#190 `_) +* Fix import statement and usage for rclpy.node.Node (`#189 `_) +* remove test_suite, add pytest as test_requires +* 0.0.3 +* Examples for Executors and callback groups (`#182 `_) +* update style to satisfy new flake8 plugins (`#181 `_) +* remove dependency on ament_python and perform customizations in setup.py (`#178 `_) +* 0.0.2 +* rename executables with shorter names (`#177 `_) +* install data_files `#176 `_ from ros2/data_files +* install executables in package specific path `#173 `_ +* use same node_names and service names in cpp and python (`#172 `_) +* use explicit kwargs `#169 `_ from ros2/use_explicit_kwargs +* fix function name (`#168 `_) +* comply with flake8 import order (`#167 `_) +* Rclpy minimal services (`#140 `_) +* Contributors: Dirk Thomas, Mikael Arguedas, Shane Loretz diff --git a/src/example/rclpy/services/minimal_service/README.md b/src/example/rclpy/services/minimal_service/README.md new file mode 100644 index 0000000..5cb9b86 --- /dev/null +++ b/src/example/rclpy/services/minimal_service/README.md @@ -0,0 +1,5 @@ +# Minimal service server cookbook recipes + +This package contains a few strategies to create service servers. +The `service` recipe shows how to define a service server in an analog way to ROS 1 and rospy +The `service_member_function` recipe creates a MinimalService class that processes the incoming requests diff --git a/src/example/rclpy/services/minimal_service/examples_rclpy_minimal_service/__init__.py b/src/example/rclpy/services/minimal_service/examples_rclpy_minimal_service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/services/minimal_service/examples_rclpy_minimal_service/service.py b/src/example/rclpy/services/minimal_service/examples_rclpy_minimal_service/service.py new file mode 100644 index 0000000..f80df11 --- /dev/null +++ b/src/example/rclpy/services/minimal_service/examples_rclpy_minimal_service/service.py @@ -0,0 +1,49 @@ +# Copyright 2016 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. + +from example_interfaces.srv import AddTwoInts + +import rclpy + +g_node = None + + +def add_two_ints_callback(request, response): + global g_node + response.sum = request.a + request.b + g_node.get_logger().info( + 'Incoming request\na: %d b: %d' % (request.a, request.b)) + + return response + + +def main(args=None): + global g_node + rclpy.init(args=args) + + g_node = rclpy.create_node('minimal_service') + + srv = g_node.create_service(AddTwoInts, 'add_two_ints', add_two_ints_callback) + while rclpy.ok(): + rclpy.spin_once(g_node) + + # Destroy the service attached to the node explicitly + # (optional - otherwise it will be done automatically + # when the garbage collector destroys the node object) + g_node.destroy_service(srv) + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/services/minimal_service/examples_rclpy_minimal_service/service_member_function.py b/src/example/rclpy/services/minimal_service/examples_rclpy_minimal_service/service_member_function.py new file mode 100644 index 0000000..3f1dc80 --- /dev/null +++ b/src/example/rclpy/services/minimal_service/examples_rclpy_minimal_service/service_member_function.py @@ -0,0 +1,45 @@ +# Copyright 2016 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. + +from example_interfaces.srv import AddTwoInts + +import rclpy +from rclpy.node import Node + + +class MinimalService(Node): + + def __init__(self): + super().__init__('minimal_service') + self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback) + + def add_two_ints_callback(self, request, response): + response.sum = request.a + request.b + self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b)) + + return response + + +def main(args=None): + rclpy.init(args=args) + + minimal_service = MinimalService() + + rclpy.spin(minimal_service) + + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/services/minimal_service/package.xml b/src/example/rclpy/services/minimal_service/package.xml new file mode 100644 index 0000000..4697151 --- /dev/null +++ b/src/example/rclpy/services/minimal_service/package.xml @@ -0,0 +1,30 @@ + + + + examples_rclpy_minimal_service + 0.19.6 + Examples of minimal service servers using rclpy. + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Aditya Pande + Shane Loretz + + example_interfaces + rclpy + std_msgs + + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/example/rclpy/services/minimal_service/resource/examples_rclpy_minimal_service b/src/example/rclpy/services/minimal_service/resource/examples_rclpy_minimal_service new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/services/minimal_service/setup.cfg b/src/example/rclpy/services/minimal_service/setup.cfg new file mode 100644 index 0000000..5809d94 --- /dev/null +++ b/src/example/rclpy/services/minimal_service/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/examples_rclpy_minimal_service +[install] +install_scripts=$base/lib/examples_rclpy_minimal_service diff --git a/src/example/rclpy/services/minimal_service/setup.py b/src/example/rclpy/services/minimal_service/setup.py new file mode 100644 index 0000000..294761a --- /dev/null +++ b/src/example/rclpy/services/minimal_service/setup.py @@ -0,0 +1,37 @@ +from setuptools import setup + +package_name = 'examples_rclpy_minimal_service' + +setup( + name=package_name, + version='0.19.6', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + author='Mikael Arguedas', + author_email='mikael@osrfoundation.org', + maintainer='Aditya Pande, Alejandro Hernandez Cordero', + maintainer_email='aditya.pande@openrobotics.org, alejandro@openrobotics.org', + keywords=['ROS'], + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Topic :: Software Development', + ], + description='Examples of minimal service servers using rclpy.', + license='Apache License, Version 2.0', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'service = examples_rclpy_minimal_service.service:main', + 'service_member_function = ' + ' examples_rclpy_minimal_service.service_member_function:main', + ], + }, +) diff --git a/src/example/rclpy/services/minimal_service/test/test_copyright.py b/src/example/rclpy/services/minimal_service/test/test_copyright.py new file mode 100644 index 0000000..aa74f3e --- /dev/null +++ b/src/example/rclpy/services/minimal_service/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.']) + assert rc == 0, 'Found errors' diff --git a/src/example/rclpy/services/minimal_service/test/test_flake8.py b/src/example/rclpy/services/minimal_service/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/src/example/rclpy/services/minimal_service/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 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. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/example/rclpy/services/minimal_service/test/test_pep257.py b/src/example/rclpy/services/minimal_service/test/test_pep257.py new file mode 100644 index 0000000..399afc9 --- /dev/null +++ b/src/example/rclpy/services/minimal_service/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/example/rclpy/topics/minimal_publisher/CHANGELOG.rst b/src/example/rclpy/topics/minimal_publisher/CHANGELOG.rst new file mode 100644 index 0000000..e8c27da --- /dev/null +++ b/src/example/rclpy/topics/minimal_publisher/CHANGELOG.rst @@ -0,0 +1,156 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclpy_minimal_publisher +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- +* add publisher_member_function_with_wait_for_all_acked.py. (backport `#407 `_) (`#409 `_) + * add publisher_member_function_with_wait_for_all_acked.py. (`#407 `_) + (cherry picked from commit bea0186806239ed2e28958b62b6eacab3390e8d0) +* Contributors: mergify[bot] + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Update maintainers to Aditya Pande and Shane Loretz (`#332 `_) +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande, Audrow Nash + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- +* Use underscores instead of dashes in setup.cfg (`#310 `_) +* Contributors: Ivan Santiago Paunovic + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ + +0.9.0 (2020-04-30) +------------------ +* more verbose test_flake8 error messages (same as `ros2/launch_ros#135 `_) +* Contributors: Dirk Thomas + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ + +0.7.3 (2019-05-29) +------------------ + +0.7.2 (2019-05-20) +------------------ +* Fix deprecation warnings (`#241 `_) +* Contributors: Jacob Perron + +0.7.1 (2019-05-08) +------------------ + +0.7.0 (2019-04-14) +------------------ + +0.6.2 (2019-02-08) +------------------ +* Modified examples to install entry point scripts into a package. (`#226 `_) +* Contributors: Shane Loretz + +0.6.0 (2018-11-20) +------------------ +* Updated maintainer info. (`#218 `_) +* Contributors: Shane Loretz + +0.5.1 (2018-06-27) +------------------ + +0.5.0 (2018-06-26) +------------------ +* add pytest markers to linter tests +* set zip_safe to avoid warning during installation (`#205 `_) +* Contributors: Dirk Thomas, Mikael Arguedas + +0.4.0 (2017-12-08) +------------------ +* Use logging (`#190 `_) +* Fix import statement and usage for rclpy.node.Node (`#189 `_) +* remove test_suite, add pytest as test_requires +* 0.0.3 +* Examples for Executors and callback groups (`#182 `_) +* remove dependency on ament_python and perform customizations in setup.py `#178 `_ +* 0.0.2 +* rename executables with shorter names (`#177 `_) +* install data_files `#176 `_ +* install executables in package specific path `#173 `_ +* use explicit kwargs `#169 `_ +* fix function name (`#168 `_) +* comply with flake8 import order (`#167 `_) +* Rclpy minimal pub sub (`#139 `_) +* Contributors: Dirk Thomas, Mikael Arguedas, Shane Loretz diff --git a/src/example/rclpy/topics/minimal_publisher/README.md b/src/example/rclpy/topics/minimal_publisher/README.md new file mode 100644 index 0000000..8909109 --- /dev/null +++ b/src/example/rclpy/topics/minimal_publisher/README.md @@ -0,0 +1,7 @@ +# Minimal "publisher" cookbook recipes + +This package contains a few different strategies for creating short nodes that blast out messages. +The `publisher_old_school` recipe creates a talker node very similar to how it would be done in ROS 1 using rospy. +The `publisher_local_function` recipe shows how to leverage the timers provided by ROS 2 to trigger message publication. +The `publisher_member_function` recipe creates a class MinimalPublisher that sends messages periodically. +The `publisher_member_function_with_wait_for_all_acked` recipe creates a class MinimalPublisher that sends messages and wait for all messages are acknowledged. diff --git a/src/example/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/__init__.py b/src/example/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_local_function.py b/src/example/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_local_function.py new file mode 100644 index 0000000..002ce3b --- /dev/null +++ b/src/example/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_local_function.py @@ -0,0 +1,50 @@ +# Copyright 2016 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. + +import rclpy + +from std_msgs.msg import String + + +def main(args=None): + rclpy.init(args=args) + + node = rclpy.create_node('minimal_publisher') + publisher = node.create_publisher(String, 'topic', 10) + + msg = String() + i = 0 + + def timer_callback(): + nonlocal i + msg.data = 'Hello World: %d' % i + i += 1 + node.get_logger().info('Publishing: "%s"' % msg.data) + publisher.publish(msg) + + timer_period = 0.5 # seconds + timer = node.create_timer(timer_period, timer_callback) + + rclpy.spin(node) + + # Destroy the timer attached to the node explicitly + # (optional - otherwise it will be done automatically + # when the garbage collector destroys the node object) + node.destroy_timer(timer) + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_member_function.py b/src/example/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_member_function.py new file mode 100644 index 0000000..dfdb032 --- /dev/null +++ b/src/example/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_member_function.py @@ -0,0 +1,53 @@ +# Copyright 2016 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. + +import rclpy +from rclpy.node import Node + +from std_msgs.msg import String + + +class MinimalPublisher(Node): + + def __init__(self): + super().__init__('minimal_publisher') + self.publisher_ = self.create_publisher(String, 'topic', 10) + timer_period = 0.5 # seconds + self.timer = self.create_timer(timer_period, self.timer_callback) + self.i = 0 + + def timer_callback(self): + msg = String() + msg.data = 'Hello World: %d' % self.i + self.publisher_.publish(msg) + self.get_logger().info('Publishing: "%s"' % msg.data) + self.i += 1 + + +def main(args=None): + rclpy.init(args=args) + + minimal_publisher = MinimalPublisher() + + rclpy.spin(minimal_publisher) + + # Destroy the node explicitly + # (optional - otherwise it will be done automatically + # when the garbage collector destroys the node object) + minimal_publisher.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_member_function_with_wait_for_all_acked.py b/src/example/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_member_function_with_wait_for_all_acked.py new file mode 100644 index 0000000..5a77a93 --- /dev/null +++ b/src/example/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_member_function_with_wait_for_all_acked.py @@ -0,0 +1,71 @@ +# Copyright 2025 Sony Group Corporation. +# +# 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. + +import rclpy +from rclpy.duration import Duration +from rclpy.executors import ExternalShutdownException +from rclpy.node import Node + +from std_msgs.msg import String + + +class MinimalPublisher(Node): + + def __init__(self): + super().__init__('minimal_publisher_with_wait_for_all_acked') + reliable_qos = rclpy.qos.QoSProfile( + depth=10, + reliability=rclpy.qos.QoSReliabilityPolicy.RELIABLE) + self.publisher_ = self.create_publisher(String, 'topic', reliable_qos) + + self.timer_period = 0.5 # seconds + self.timer = self.create_timer(self.timer_period, self.timer_callback) + self.i = 0 + self.max_count = 10 + + def wait_for_all_acked(self): + self.get_logger().info('Waiting for all messages to be acknowledged...') + acknowledged = self.publisher_.wait_for_all_acked(Duration(seconds=3)) + if acknowledged: + self.get_logger().info('All messages acknowledged.') + else: + self.get_logger().warn('Not all messages were acknowledged.') + + def timer_callback(self): + if self.i < self.max_count: + msg = String() + msg.data = 'Hello World: %d' % self.i + self.publisher_.publish(msg) + self.get_logger().info('Publishing: "%s"' % msg.data) + self.i += 1 + else: + self.timer.cancel() + self.wait_for_all_acked() + self.i = 0 + self.timer.reset() + + +def main(args=None): + rclpy.init(args=args) + minimal_publisher = MinimalPublisher() + try: + rclpy.spin(minimal_publisher) + except (KeyboardInterrupt, ExternalShutdownException): + pass + finally: + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_old_school.py b/src/example/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_old_school.py new file mode 100644 index 0000000..23ab3cc --- /dev/null +++ b/src/example/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_old_school.py @@ -0,0 +1,52 @@ +# Copyright 2016 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. + +from time import sleep + +import rclpy + +from std_msgs.msg import String + +# We do not recommend this style as ROS 2 provides timers for this purpose, +# and it is recommended that all nodes call a variation of spin. +# This example is only included for completeness because it is similar to examples in ROS 1. +# For periodic publication please see the other examples using timers. + + +def main(args=None): + rclpy.init(args=args) + + node = rclpy.create_node('minimal_publisher') + + publisher = node.create_publisher(String, 'topic', 10) + + msg = String() + + i = 0 + while rclpy.ok(): + msg.data = 'Hello World: %d' % i + i += 1 + node.get_logger().info('Publishing: "%s"' % msg.data) + publisher.publish(msg) + sleep(0.5) # seconds + + # Destroy the node explicitly + # (optional - otherwise it will be done automatically + # when the garbage collector destroys the node object) + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/topics/minimal_publisher/package.xml b/src/example/rclpy/topics/minimal_publisher/package.xml new file mode 100644 index 0000000..e731683 --- /dev/null +++ b/src/example/rclpy/topics/minimal_publisher/package.xml @@ -0,0 +1,29 @@ + + + + examples_rclpy_minimal_publisher + 0.19.6 + Examples of minimal publishers using rclpy. + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Aditya Pande + Shane Loretz + + rclpy + std_msgs + + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/example/rclpy/topics/minimal_publisher/resource/examples_rclpy_minimal_publisher b/src/example/rclpy/topics/minimal_publisher/resource/examples_rclpy_minimal_publisher new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/topics/minimal_publisher/setup.cfg b/src/example/rclpy/topics/minimal_publisher/setup.cfg new file mode 100644 index 0000000..774d7e2 --- /dev/null +++ b/src/example/rclpy/topics/minimal_publisher/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/examples_rclpy_minimal_publisher +[install] +install_scripts=$base/lib/examples_rclpy_minimal_publisher diff --git a/src/example/rclpy/topics/minimal_publisher/setup.py b/src/example/rclpy/topics/minimal_publisher/setup.py new file mode 100644 index 0000000..591b47e --- /dev/null +++ b/src/example/rclpy/topics/minimal_publisher/setup.py @@ -0,0 +1,42 @@ +from setuptools import setup + +package_name = 'examples_rclpy_minimal_publisher' + +setup( + name=package_name, + version='0.19.6', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + author='Mikael Arguedas', + author_email='mikael@osrfoundation.org', + maintainer='Aditya Pande, Alejandro Hernandez Cordero', + maintainer_email='aditya.pande@openrobotics.org, alejandro@openrobotics.org', + keywords=['ROS'], + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Topic :: Software Development', + ], + description='Examples of minimal publishers using rclpy.', + license='Apache License, Version 2.0', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'publisher_old_school = examples_rclpy_minimal_publisher.publisher_old_school:main', + 'publisher_local_function =' + ' examples_rclpy_minimal_publisher.publisher_local_function:main', + 'publisher_member_function =' + ' examples_rclpy_minimal_publisher.publisher_member_function:main', + 'publisher_member_function_with_wait_for_all_acked =' + ' examples_rclpy_minimal_publisher.' + 'publisher_member_function_with_wait_for_all_acked:main', + ], + }, +) diff --git a/src/example/rclpy/topics/minimal_publisher/test/test_copyright.py b/src/example/rclpy/topics/minimal_publisher/test/test_copyright.py new file mode 100644 index 0000000..aa74f3e --- /dev/null +++ b/src/example/rclpy/topics/minimal_publisher/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.']) + assert rc == 0, 'Found errors' diff --git a/src/example/rclpy/topics/minimal_publisher/test/test_flake8.py b/src/example/rclpy/topics/minimal_publisher/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/src/example/rclpy/topics/minimal_publisher/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 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. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/example/rclpy/topics/minimal_publisher/test/test_pep257.py b/src/example/rclpy/topics/minimal_publisher/test/test_pep257.py new file mode 100644 index 0000000..399afc9 --- /dev/null +++ b/src/example/rclpy/topics/minimal_publisher/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/example/rclpy/topics/minimal_subscriber/CHANGELOG.rst b/src/example/rclpy/topics/minimal_subscriber/CHANGELOG.rst new file mode 100644 index 0000000..234b741 --- /dev/null +++ b/src/example/rclpy/topics/minimal_subscriber/CHANGELOG.rst @@ -0,0 +1,152 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclpy_minimal_subscriber +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Update maintainers to Aditya Pande and Shane Loretz (`#332 `_) +* Updated maintainers (`#329 `_) +* Contributors: Aditya Pande, Audrow Nash + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- +* Use underscores instead of dashes in setup.cfg (`#310 `_) +* Contributors: Ivan Santiago Paunovic + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- + +0.10.2 (2021-01-25) +------------------- + +0.10.1 (2020-12-10) +------------------- +* Update maintainers (`#292 `_) +* Contributors: Shane Loretz + +0.10.0 (2020-09-21) +------------------- + +0.9.2 (2020-06-01) +------------------ + +0.9.1 (2020-05-26) +------------------ + +0.9.0 (2020-04-30) +------------------ +* more verbose test_flake8 error messages (same as `ros2/launch_ros#135 `_) +* Contributors: Dirk Thomas + +0.8.2 (2019-11-19) +------------------ + +0.8.1 (2019-10-24) +------------------ + +0.7.3 (2019-05-29) +------------------ + +0.7.2 (2019-05-20) +------------------ +* Fix deprecation warnings (`#241 `_) +* Contributors: Jacob Perron + +0.7.1 (2019-05-08) +------------------ + +0.7.0 (2019-04-14) +------------------ + +0.6.2 (2019-02-08) +------------------ +* Modified examples to install entry point scripts into a package. (`#226 `_) +* Contributors: Shane Loretz + +0.6.0 (2018-11-20) +------------------ +* Updated maintainer info. (`#218 `_) +* Contributors: Shane Loretz + +0.5.1 (2018-06-27) +------------------ + +0.5.0 (2018-06-26) +------------------ +* add pytest markers to linter tests +* set zip_safe to avoid warning during installation (`#205 `_) +* Contributors: Dirk Thomas, Mikael Arguedas + +0.4.0 (2017-12-08) +------------------ +* Use logging (`#190 `_) +* Fix import statement and usage for rclpy.node.Node (`#189 `_) +* remove test_suite, add pytest as test_requires +* 0.0.3 +* Examples for Executors and callback groups (`#182 `_) +* remove dependency on ament_python and perform customizations in setup.py `#178 `_ +* 0.0.2 +* rename executables with shorter names (`#177 `_) +* install data_files `#176 `_ +* install executables in package specific path `#173 `_ +* use explicit kwargs `#169 `_ +* fix function name (`#168 `_) +* comply with flake8 import order (`#167 `_) +* Rclpy minimal pub sub (`#139 `_) +* Contributors: Dirk Thomas, Mikael Arguedas, Shane Loretz diff --git a/src/example/rclpy/topics/minimal_subscriber/README.md b/src/example/rclpy/topics/minimal_subscriber/README.md new file mode 100644 index 0000000..7b28fcb --- /dev/null +++ b/src/example/rclpy/topics/minimal_subscriber/README.md @@ -0,0 +1,6 @@ +# Minimal "subscriber" cookbook recipes + +This package contains a few different strategies for creating short nodes that display received messages. +The `subscriber_old_school` recipe creates a listener node very similar to how it would be done in ROS 1 using rospy. +The `subscriber_lambda` recipe shows how to embed the callback functions inside your main. +The `subscriber_member_function` recipe creates a class MinimalSubscriber that contains the callback, keeping the main simple. diff --git a/src/example/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/__init__.py b/src/example/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_lambda.py b/src/example/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_lambda.py new file mode 100644 index 0000000..6d8e84d --- /dev/null +++ b/src/example/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_lambda.py @@ -0,0 +1,39 @@ +# Copyright 2016 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. + +import rclpy + +from std_msgs.msg import String + + +def main(args=None): + rclpy.init(args=args) + + node = rclpy.create_node('minimal_subscriber') + + subscription = node.create_subscription( + String, 'topic', lambda msg: node.get_logger().info('I heard: "%s"' % msg.data), 10) + subscription # prevent unused variable warning + + rclpy.spin(node) + + # Destroy the node explicitly + # (optional - otherwise it will be done automatically + # when the garbage collector destroys the node object) + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_member_function.py b/src/example/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_member_function.py new file mode 100644 index 0000000..78c7266 --- /dev/null +++ b/src/example/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_member_function.py @@ -0,0 +1,51 @@ +# Copyright 2016 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. + +import rclpy +from rclpy.node import Node + +from std_msgs.msg import String + + +class MinimalSubscriber(Node): + + def __init__(self): + super().__init__('minimal_subscriber') + self.subscription = self.create_subscription( + String, + 'topic', + self.listener_callback, + 10) + self.subscription # prevent unused variable warning + + def listener_callback(self, msg): + self.get_logger().info('I heard: "%s"' % msg.data) + + +def main(args=None): + rclpy.init(args=args) + + minimal_subscriber = MinimalSubscriber() + + rclpy.spin(minimal_subscriber) + + # Destroy the node explicitly + # (optional - otherwise it will be done automatically + # when the garbage collector destroys the node object) + minimal_subscriber.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_old_school.py b/src/example/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_old_school.py new file mode 100644 index 0000000..a15e789 --- /dev/null +++ b/src/example/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_old_school.py @@ -0,0 +1,48 @@ +# Copyright 2016 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. + +import rclpy + +from std_msgs.msg import String + +g_node = None + + +def chatter_callback(msg): + global g_node + g_node.get_logger().info( + 'I heard: "%s"' % msg.data) + + +def main(args=None): + global g_node + rclpy.init(args=args) + + g_node = rclpy.create_node('minimal_subscriber') + + subscription = g_node.create_subscription(String, 'topic', chatter_callback, 10) + subscription # prevent unused variable warning + + while rclpy.ok(): + rclpy.spin_once(g_node) + + # Destroy the node explicitly + # (optional - otherwise it will be done automatically + # when the garbage collector destroys the node object) + g_node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/topics/minimal_subscriber/package.xml b/src/example/rclpy/topics/minimal_subscriber/package.xml new file mode 100644 index 0000000..d561afb --- /dev/null +++ b/src/example/rclpy/topics/minimal_subscriber/package.xml @@ -0,0 +1,29 @@ + + + + examples_rclpy_minimal_subscriber + 0.19.6 + Examples of minimal subscribers using rclpy. + + Aditya Pande + Alejandro Hernandez Cordero + + Apache License 2.0 + + Aditya Pande + Shane Loretz + + rclpy + std_msgs + + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/example/rclpy/topics/minimal_subscriber/resource/examples_rclpy_minimal_subscriber b/src/example/rclpy/topics/minimal_subscriber/resource/examples_rclpy_minimal_subscriber new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/topics/minimal_subscriber/setup.cfg b/src/example/rclpy/topics/minimal_subscriber/setup.cfg new file mode 100644 index 0000000..b98864a --- /dev/null +++ b/src/example/rclpy/topics/minimal_subscriber/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/examples_rclpy_minimal_subscriber +[install] +install_scripts=$base/lib/examples_rclpy_minimal_subscriber diff --git a/src/example/rclpy/topics/minimal_subscriber/setup.py b/src/example/rclpy/topics/minimal_subscriber/setup.py new file mode 100644 index 0000000..d4e7d8b --- /dev/null +++ b/src/example/rclpy/topics/minimal_subscriber/setup.py @@ -0,0 +1,39 @@ +from setuptools import setup + +package_name = 'examples_rclpy_minimal_subscriber' + +setup( + name=package_name, + version='0.19.6', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + author='Mikael Arguedas', + author_email='mikael@osrfoundation.org', + maintainer='Aditya Pande, Alejandro Hernandez Cordero', + maintainer_email='aditya.pande@openrobotics.org, alejandro@openrobotics.org', + keywords=['ROS'], + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Topic :: Software Development', + ], + description='Examples of minimal subscribers using rclpy.', + license='Apache License, Version 2.0', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'subscriber_old_school =' + ' examples_rclpy_minimal_subscriber.subscriber_old_school:main', + 'subscriber_lambda = examples_rclpy_minimal_subscriber.subscriber_lambda:main', + 'subscriber_member_function =' + ' examples_rclpy_minimal_subscriber.subscriber_member_function:main', + ], + }, +) diff --git a/src/example/rclpy/topics/minimal_subscriber/test/test_copyright.py b/src/example/rclpy/topics/minimal_subscriber/test/test_copyright.py new file mode 100644 index 0000000..aa74f3e --- /dev/null +++ b/src/example/rclpy/topics/minimal_subscriber/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.']) + assert rc == 0, 'Found errors' diff --git a/src/example/rclpy/topics/minimal_subscriber/test/test_flake8.py b/src/example/rclpy/topics/minimal_subscriber/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/src/example/rclpy/topics/minimal_subscriber/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 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. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/example/rclpy/topics/minimal_subscriber/test/test_pep257.py b/src/example/rclpy/topics/minimal_subscriber/test/test_pep257.py new file mode 100644 index 0000000..399afc9 --- /dev/null +++ b/src/example/rclpy/topics/minimal_subscriber/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/example/rclpy/topics/pointcloud_publisher/CHANGELOG.rst b/src/example/rclpy/topics/pointcloud_publisher/CHANGELOG.rst new file mode 100644 index 0000000..52cda50 --- /dev/null +++ b/src/example/rclpy/topics/pointcloud_publisher/CHANGELOG.rst @@ -0,0 +1,74 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package examples_rclpy_pointcloud_publisher +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.19.6 (2025-08-06) +------------------- + +0.19.5 (2025-04-02) +------------------- + +0.19.4 (2024-06-27) +------------------- + +0.19.3 (2024-04-16) +------------------- + +0.19.2 (2024-03-28) +------------------- + +0.19.1 (2023-07-11) +------------------- + +0.19.0 (2023-04-27) +------------------- + +0.18.0 (2023-04-11) +------------------- + +0.17.1 (2023-03-01) +------------------- + +0.17.0 (2023-02-14) +------------------- +* [rolling] Update maintainers - 2022-11-07 (`#352 `_) +* Contributors: Audrow Nash + +0.16.2 (2022-11-02) +------------------- + +0.16.1 (2022-09-13) +------------------- + +0.16.0 (2022-04-29) +------------------- + +0.15.0 (2022-03-01) +------------------- + +0.14.0 (2022-01-14) +------------------- +* Update maintainers to Aditya Pande and Shane Loretz (`#332 `_) +* Contributors: Audrow Nash + +0.13.0 (2021-10-18) +------------------- + +0.12.0 (2021-08-05) +------------------- + +0.11.2 (2021-04-26) +------------------- +* Use underscores instead of dashes in setup.cfg (`#310 `_) +* Contributors: Ivan Santiago Paunovic + +0.11.1 (2021-04-12) +------------------- + +0.11.0 (2021-04-06) +------------------- + +0.10.3 (2021-03-18) +------------------- +* add pointcloud publisher example (`#276 `_) +* Contributors: Evan Flynn diff --git a/src/example/rclpy/topics/pointcloud_publisher/examples_rclpy_pointcloud_publisher/__init__.py b/src/example/rclpy/topics/pointcloud_publisher/examples_rclpy_pointcloud_publisher/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/topics/pointcloud_publisher/examples_rclpy_pointcloud_publisher/pointcloud_publisher.py b/src/example/rclpy/topics/pointcloud_publisher/examples_rclpy_pointcloud_publisher/pointcloud_publisher.py new file mode 100644 index 0000000..6152f67 --- /dev/null +++ b/src/example/rclpy/topics/pointcloud_publisher/examples_rclpy_pointcloud_publisher/pointcloud_publisher.py @@ -0,0 +1,70 @@ +# Copyright 2020 Evan Flynn +# +# 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. + +import numpy as np + +import rclpy +from rclpy.node import Node +from sensor_msgs.msg import PointCloud2 +from sensor_msgs.msg import PointField +from sensor_msgs_py import point_cloud2 +from std_msgs.msg import Header + + +class PointCloudPublisher(Node): + + rate = 30 + moving = True + width = 100 + height = 100 + + header = Header() + header.frame_id = 'map' + + dtype = PointField.FLOAT32 + point_step = 16 + fields = [PointField(name='x', offset=0, datatype=dtype, count=1), + PointField(name='y', offset=4, datatype=dtype, count=1), + PointField(name='z', offset=8, datatype=dtype, count=1), + PointField(name='intensity', offset=12, datatype=dtype, count=1)] + + def __init__(self): + super().__init__('pc_publisher') + self.publisher_ = self.create_publisher(PointCloud2, 'test_cloud', 10) + timer_period = 1 / self.rate + self.timer = self.create_timer(timer_period, self.timer_callback) + self.counter = 0 + + def timer_callback(self): + self.header.stamp = self.get_clock().now().to_msg() + x, y = np.meshgrid(np.linspace(-2, 2, self.width), np.linspace(-2, 2, self.height)) + z = 0.5 * np.sin(2*x-self.counter/10.0) * np.sin(2*y) + points = np.array([x, y, z, z]).reshape(4, -1).T + pc2_msg = point_cloud2.create_cloud(self.header, self.fields, points) + self.publisher_.publish(pc2_msg) + + if self.moving: + self.counter += 1 + + +def main(args=None): + rclpy.init(args=args) + pc_publisher = PointCloudPublisher() + rclpy.spin(pc_publisher) + pc_publisher.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() diff --git a/src/example/rclpy/topics/pointcloud_publisher/package.xml b/src/example/rclpy/topics/pointcloud_publisher/package.xml new file mode 100644 index 0000000..3ce1e29 --- /dev/null +++ b/src/example/rclpy/topics/pointcloud_publisher/package.xml @@ -0,0 +1,29 @@ + + + + examples_rclpy_pointcloud_publisher + 0.19.6 + Example on how to publish a Pointcloud2 message + + Aditya Pande + Alejandro Hernandez Cordero + + Apache 2.0 + + Evan Flynn + + rclpy + sensor_msgs_py + python3-numpy + sensor_msgs + std_msgs + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/example/rclpy/topics/pointcloud_publisher/resource/examples_rclpy_pointcloud_publisher b/src/example/rclpy/topics/pointcloud_publisher/resource/examples_rclpy_pointcloud_publisher new file mode 100644 index 0000000..e69de29 diff --git a/src/example/rclpy/topics/pointcloud_publisher/setup.cfg b/src/example/rclpy/topics/pointcloud_publisher/setup.cfg new file mode 100644 index 0000000..7ffcb38 --- /dev/null +++ b/src/example/rclpy/topics/pointcloud_publisher/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/examples_rclpy_pointcloud_publisher +[install] +install_scripts=$base/lib/examples_rclpy_pointcloud_publisher diff --git a/src/example/rclpy/topics/pointcloud_publisher/setup.py b/src/example/rclpy/topics/pointcloud_publisher/setup.py new file mode 100644 index 0000000..f205d8d --- /dev/null +++ b/src/example/rclpy/topics/pointcloud_publisher/setup.py @@ -0,0 +1,26 @@ +from setuptools import setup + +package_name = 'examples_rclpy_pointcloud_publisher' + +setup( + name=package_name, + version='0.19.6', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='Aditya Pande, Alejandro Hernandez Cordero', + maintainer_email='aditya.pande@openrobotics.org, alejandro@openrobotics.org', + description='Example on how to publish a Pointcloud2 message', + license='Apache 2.0', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'pointcloud_publisher = examples_rclpy_pointcloud_publisher.pointcloud_publisher:main' + ], + }, +) diff --git a/src/example/rclpy/topics/pointcloud_publisher/test/test_copyright.py b/src/example/rclpy/topics/pointcloud_publisher/test/test_copyright.py new file mode 100644 index 0000000..cc8ff03 --- /dev/null +++ b/src/example/rclpy/topics/pointcloud_publisher/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/example/rclpy/topics/pointcloud_publisher/test/test_flake8.py b/src/example/rclpy/topics/pointcloud_publisher/test/test_flake8.py new file mode 100644 index 0000000..11be193 --- /dev/null +++ b/src/example/rclpy/topics/pointcloud_publisher/test/test_flake8.py @@ -0,0 +1,33 @@ +# Copyright 2017 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. +import pytest + +# backwards compatability for older ROS2 distros +try: + from ament_flake8.main import main +except ImportError: + from ament_flake8.main import main_with_errors + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + try: + rc = main(argv=[]) + assert rc == 0, 'Found errors' + except NameError: + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/example/rclpy/topics/pointcloud_publisher/test/test_pep257.py b/src/example/rclpy/topics/pointcloud_publisher/test/test_pep257.py new file mode 100644 index 0000000..b234a38 --- /dev/null +++ b/src/example/rclpy/topics/pointcloud_publisher/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/examples b/src/examples deleted file mode 160000 index fc82c84..0000000 --- a/src/examples +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fc82c84cde204523910254c6c5985b8e08d04f0d