From 62d07ba8f3144e200145f414fc38c4a41192033e Mon Sep 17 00:00:00 2001 From: "Krauskopf Sebastian (DC-AE/EAR2)" Date: Sun, 20 Oct 2024 16:54:40 +0200 Subject: [PATCH] new: add data layer samples --- .gitignore | 2 +- LICENSE | 2 +- README.md | 19 +- simple-listener-dl-cpp/.gitignore | 8 + simple-listener-dl-cpp/.vscode/settings.json | 7 + simple-listener-dl-cpp/.vscode/tasks.json | 28 +++ simple-listener-dl-cpp/README.md | 59 +++++++ simple-listener-dl-cpp/build-snap-amd64.sh | 12 ++ simple-listener-dl-cpp/colcon-build.sh | 17 ++ simple-listener-dl-cpp/snap/snapcraft.yaml | 48 ++++++ simple-listener-dl-cpp/src/app/CMakeLists.txt | 78 +++++++++ simple-listener-dl-cpp/src/app/package.xml | 22 +++ .../src/app/src/ctrlx_datalayer_helper.h | 144 ++++++++++++++++ .../app/src/subscriber_member_function.cpp | 159 +++++++++++++++++ .../wrapper/run-listener.sh | 12 ++ simple-talker-dl-cpp/.gitignore | 8 + simple-talker-dl-cpp/.vscode/settings.json | 6 + simple-talker-dl-cpp/.vscode/tasks.json | 28 +++ simple-talker-dl-cpp/README.md | 61 +++++++ simple-talker-dl-cpp/build-snap-amd64.sh | 12 ++ simple-talker-dl-cpp/colcon-build.sh | 17 ++ simple-talker-dl-cpp/snap/snapcraft.yaml | 48 ++++++ simple-talker-dl-cpp/src/app/CMakeLists.txt | 78 +++++++++ simple-talker-dl-cpp/src/app/package.xml | 22 +++ .../app/src/subscriber_member_function.cpp | 163 ++++++++++++++++++ simple-talker-dl-cpp/wrapper/run-listener.sh | 12 ++ simple-talker-py/README.md | 2 +- 27 files changed, 1066 insertions(+), 8 deletions(-) create mode 100644 simple-listener-dl-cpp/.gitignore create mode 100644 simple-listener-dl-cpp/.vscode/settings.json create mode 100644 simple-listener-dl-cpp/.vscode/tasks.json create mode 100644 simple-listener-dl-cpp/README.md create mode 100644 simple-listener-dl-cpp/build-snap-amd64.sh create mode 100644 simple-listener-dl-cpp/colcon-build.sh create mode 100644 simple-listener-dl-cpp/snap/snapcraft.yaml create mode 100644 simple-listener-dl-cpp/src/app/CMakeLists.txt create mode 100644 simple-listener-dl-cpp/src/app/package.xml create mode 100644 simple-listener-dl-cpp/src/app/src/ctrlx_datalayer_helper.h create mode 100644 simple-listener-dl-cpp/src/app/src/subscriber_member_function.cpp create mode 100644 simple-listener-dl-cpp/wrapper/run-listener.sh create mode 100644 simple-talker-dl-cpp/.gitignore create mode 100644 simple-talker-dl-cpp/.vscode/settings.json create mode 100644 simple-talker-dl-cpp/.vscode/tasks.json create mode 100644 simple-talker-dl-cpp/README.md create mode 100644 simple-talker-dl-cpp/build-snap-amd64.sh create mode 100644 simple-talker-dl-cpp/colcon-build.sh create mode 100644 simple-talker-dl-cpp/snap/snapcraft.yaml create mode 100644 simple-talker-dl-cpp/src/app/CMakeLists.txt create mode 100644 simple-talker-dl-cpp/src/app/package.xml create mode 100644 simple-talker-dl-cpp/src/app/src/subscriber_member_function.cpp create mode 100644 simple-talker-dl-cpp/wrapper/run-listener.sh diff --git a/.gitignore b/.gitignore index 1447f3e..2ae5981 100644 --- a/.gitignore +++ b/.gitignore @@ -464,7 +464,7 @@ Temporary Items /parts/ /stage/ /prime/ -/*.snap +*.snap # Snapcraft global state tracking data(automatically generated) # https://forum.snapcraft.io/t/location-to-save-global-state/768 diff --git a/LICENSE b/LICENSE index 69f6136..8ad2f8b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Bosch Rexroth AG +Copyright (c) 2024 Bosch Rexroth AG Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 7d713e6..adc07e6 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@ # ctrlX AUTOMATION Software Development Kit for ROS 2 -This is the Software Development Kit (SDK) to build ROS 2 Applications, that can run on industrial devices that are based on the ctrlX OS platform. +This is the Software Development Kit (SDK) to build ROS 2 Applications, that can run on industrial devices that are based on the ctrlX OS platform. -The **Robot Operating System (ROS)** is an open source robotics software framework that help you build robot applications. +The **Robot Operating System (ROS)** is an open source robotics software framework that help you build robot applications. **ctrlX OS** is an industrial grade realtime platform based on Linux which is available for several industrial hardware devices and features an app-based modular architecture that allows you to install additional functionality. This includes apps for industrial fieldbus communication, machine automation, programmable logic control (PLC) and other automation software which can also be found ready-to-run in the [ctrlX Store](https://developer.community.boschrexroth.com/). +Please note, that this is the first version of the ROS 2 SDK. You may expect more samples and extensions over the course of the next months. + ## Getting Started As a prerequisite you should get yourself familiar with: * ROS 2 and its underlying architecture using the official ROS 2 documentation at: * The [ctrlX AUTOMATION Ecosystem](https://ctrlx-automation.com/) and the architecture of ctrlX OS as well as devices which are capable to run ctrlX OS. E.g. ctrlX CORE devices from [Bosch Rexroth](https://www.boschrexroth.com/). -* The [ctrlX AUTOMATION SDK](https://github.com/boschrexroth/ctrlx-automation-sdk) which is the underlying SDK that allows you to create any kind of App for ctrlX OS and is the foundation for the ctrlX AUTOMATION Software Development Kit for ROS 2. +* The [ctrlX AUTOMATION SDK](https://github.com/boschrexroth/ctrlx-automation-sdk) which is the underlying SDK that allows you to create any kind of App for ctrlX OS and is the foundation for the ctrlX AUTOMATION Software Development Kit for ROS 2. [Link to SDK Documentation](https://boschrexroth.github.io/ctrlx-automation-sdk/) ## Usage @@ -38,13 +40,20 @@ This SDK contains also multiple project examples to show the usage of ROS 2. All ### ROS 2 Humble Applications in C++ -#### Writing a Simple Publisher and Subscriber (C++) +#### Learn how to package your application as snap This example is based on the official ROS 2 Tutorial and adjusted to run on ctrlX OS together with the *base snap*. You can find the original source code at [ROS 2 Humble Tutorials - Writing a simple publisher and subscriber (C++)](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Cpp-Publisher-And-Subscriber.html#writing-a-simple-publisher-and-subscriber-c) * [`simple-talker-cpp/`](simple-talker-cpp/README.md) contains the snap version of the described publisher (talker) * [`simple-listener-cpp/`](simple-listener-cpp/README.md) contains the snap version of the described subscriber (listener) +#### Learn how to access the ctrlX Data Layer from your application + +the ctrlX Data Layer is the realtime message broker that runs on ctrlX OS. It is used by all ctrlX Apps to publish and exchange variables, types, methods and programs. In the ctrlX Data Layer you can find all device, process, fieldbus and periphery data. You can connect to the ctrlX Data Layer via TCP/IP or IPC. + +* [`simple-talker-dl-cpp/`](simple-talker-dl-cpp/README.md) read from ctrlX Data Layer and publish as ROS message (talker) +* [`simple-listener-dl-cpp/`](simple-listener-dl-cpp/README.md) subscribe to ROS message and publish in ctrlX Data Layer (listener) + ### ROS 2 Humble Applications in Python #### Writing a Simple Publisher and Subscriber (Python) @@ -87,7 +96,7 @@ Any use of the source code and related documents of this repository in applicati ## About -SPDX-FileCopyrightText: Copyright (c) 2023 Bosch Rexroth AG +SPDX-FileCopyrightText: Copyright (c) 2024 Bosch Rexroth AG diff --git a/simple-listener-dl-cpp/.gitignore b/simple-listener-dl-cpp/.gitignore new file mode 100644 index 0000000..7cfd40a --- /dev/null +++ b/simple-listener-dl-cpp/.gitignore @@ -0,0 +1,8 @@ +build/ +install/ +log/ +__pycache__/ +prime/ +parts/ +stage/ +squashfs-root/ \ No newline at end of file diff --git a/simple-listener-dl-cpp/.vscode/settings.json b/simple-listener-dl-cpp/.vscode/settings.json new file mode 100644 index 0000000..d22ac93 --- /dev/null +++ b/simple-listener-dl-cpp/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "ros.distro": "humble", + "files.associations": { + "functional": "cpp", + "new": "cpp" + } +} \ No newline at end of file diff --git a/simple-listener-dl-cpp/.vscode/tasks.json b/simple-listener-dl-cpp/.vscode/tasks.json new file mode 100644 index 0000000..08d9005 --- /dev/null +++ b/simple-listener-dl-cpp/.vscode/tasks.json @@ -0,0 +1,28 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: gcc build active file", + "command": "/usr/bin/gcc", + "args": [ + "-fdiagnostics-color=always", + "-g", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Task generated by Debugger." + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/simple-listener-dl-cpp/README.md b/simple-listener-dl-cpp/README.md new file mode 100644 index 0000000..96030dc --- /dev/null +++ b/simple-listener-dl-cpp/README.md @@ -0,0 +1,59 @@ +# Simple ROS 2 Subscriber in C++ + +A simple ROS 2 node which subscribes to ROS messages of the topic `ros2_simple_talker_cpp` and publishes the value to the ctrlX Data Layer at the address `ros/listenercpp/mymessage`. From there the value can be used in any ctrlX App. + +## Prerequisites + +* `ros-base` snap. The base snap which provides the ROS 2 runtime binaries. Has to be installed on ctrlX OS. See [ROS 2 Humble Base Snap](../ros2-base-humble-deb/README.md). +* An Ubuntu based build environment to build an app. See [ctrlX Automation SDK](https://github.com/boschrexroth/ctrlx-automation-sdk). + +## Basis for this Project + +This project is based on the official ROS 2 Tutorial: [Writing a simple publisher and subscriber (C++)](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Cpp-Publisher-And-Subscriber.html#writing-a-simple-publisher-and-subscriber-c). + +## Building a Snap + +Building a snap has two steps: + +1. Colcon build: `CMakeLists.txt` defines how compile and link the C++ sources. +2. snap build: `snap/snapcraft.yaml` defines how the compiled binaries are packed into the snap and how they are called on ctrlX OS. + +### Colcon Configuration + +The colcon build tool is configured by `CMakeLists.txt`. + +This section defines the ROS 2 packages needed: + + find_package(ament_cmake REQUIRED) + find_package(rclcpp REQUIRED) + find_package(std_msgs REQUIRED) + +And here the executables and their dependencies are defined: + + add_executable(listener src/subscriber_member_function.cpp) + ament_target_dependencies(listener rclcpp std_msgs) + +### Snapcraft Configuration + +`snap/snapcraft.yaml` defines how the snap will be build: + +* `install/` is dumped into the snap +* also `wrapper/` +* Two apps (talker and listener) are copied into the snap and started as services +* The snap - respectively the executables - uses the content interface of the `ros-base` snap (here the ROS 2 runtime is provided). + +### Build the Snap + +Start this script: + + ./build-snap-amd64.sh + +## About + +SPDX-FileCopyrightText: Copyright (c) 2023 Bosch Rexroth AG + + + +## Licenses + +SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/simple-listener-dl-cpp/build-snap-amd64.sh b/simple-listener-dl-cpp/build-snap-amd64.sh new file mode 100644 index 0000000..b726b25 --- /dev/null +++ b/simple-listener-dl-cpp/build-snap-amd64.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +source colcon-build.sh +if [ $? -eq 0 ] +then + echo " " +else + exit 1 +fi + +snapcraft clean --destructive-mode +snapcraft --destructive-mode \ No newline at end of file diff --git a/simple-listener-dl-cpp/colcon-build.sh b/simple-listener-dl-cpp/colcon-build.sh new file mode 100644 index 0000000..711617c --- /dev/null +++ b/simple-listener-dl-cpp/colcon-build.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +rosdep install -i --from-path src --rosdistro humble -y +if [ $? -eq 0 ] +then + echo " " +else + exit 1 +fi + +rm -rf install/ +mkdir -p install/app +rm -rf build/ +rm -rf log/ + +source /opt/ros/humble/setup.bash +colcon build \ No newline at end of file diff --git a/simple-listener-dl-cpp/snap/snapcraft.yaml b/simple-listener-dl-cpp/snap/snapcraft.yaml new file mode 100644 index 0000000..4e9f200 --- /dev/null +++ b/simple-listener-dl-cpp/snap/snapcraft.yaml @@ -0,0 +1,48 @@ +name: ros2-simple-listener-dl-cpp +version: '2.2.0' +summary: Snap with simple ROS 2 listener +description: | + A simple ROS 2 listener which receives ROS 2 messages on topic 'ros2_simple_cpp'. + +base: core22 +grade: stable +confinement: strict + +parts: + ros-app: + plugin: dump + source: install + stage-packages: + - libzmq5 + - ctrlx-datalayer + override-build: | + snapcraftctl build + + wrapper-scripts: + plugin: dump + source: wrapper/ + organize: + ./run-listener.sh : usr/bin/run-listener + +apps: + listener: + command: usr/bin/run-listener + plugs: + - ros-base + - network + - network-bind + daemon: simple + passthrough: + restart-condition: always + restart-delay: 10s + +plugs: + ros-base: + interface: content + content: executables + target: $SNAP/rosruntime + + datalayer: + interface: content + content: datalayer + target: $SNAP_DATA/.datalayer diff --git a/simple-listener-dl-cpp/src/app/CMakeLists.txt b/simple-listener-dl-cpp/src/app/CMakeLists.txt new file mode 100644 index 0000000..cfce18c --- /dev/null +++ b/simple-listener-dl-cpp/src/app/CMakeLists.txt @@ -0,0 +1,78 @@ +cmake_minimum_required(VERSION 3.8) +project(app) +set(TARGET_PROJECT_NAME listener) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) + + +# +# Set link directories +# +MESSAGE( STATUS "Libraries directory: ${LIBRARY_DIR}") +link_directories( + ${LIBRARY_DIR} + ${LIBRARY_DEP_DIR} + ) + +# User dependency directory +# +set (USER_DEPENDENCY_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../..) + +SET ( PRIVATE_INCLUDE_DIRS + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_LIST_DIR}/include + ${USER_DEPENDENCY_DIR}/include + ${USER_DEPENDENCY_DIR}/include/comm.datalayer + ) + +# Set target link libraries + + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +add_executable(listener src/subscriber_member_function.cpp) +ament_target_dependencies(listener rclcpp std_msgs) + + +# +# Set target include directories +# +target_include_directories ( ${TARGET_PROJECT_NAME} + PUBLIC ${PUBLIC_INCLUDE_DIRS} + PUBLIC ${LIBRARY_INCLUDES} + PRIVATE ${PRIVATE_INCLUDE_DIRS} +) +# +target_link_libraries(${TARGET_PROJECT_NAME} -Wl,--no-undefined) +target_link_libraries(${TARGET_PROJECT_NAME} + libcomm_datalayer.so + pthread + systemd + zmq + ssl + crypto +) + +install(TARGETS + listener + DESTINATION lib/${PROJECT_NAME}) + +ament_package() diff --git a/simple-listener-dl-cpp/src/app/package.xml b/simple-listener-dl-cpp/src/app/package.xml new file mode 100644 index 0000000..87f99cc --- /dev/null +++ b/simple-listener-dl-cpp/src/app/package.xml @@ -0,0 +1,22 @@ + + + + app + 2.2.0 + Simple ROS 2 subscriber (listener) in cpp + boschrexroth + MIT License + + ament_cmake + + rclcpp + std_msgs + sensor_msgs + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/simple-listener-dl-cpp/src/app/src/ctrlx_datalayer_helper.h b/simple-listener-dl-cpp/src/app/src/ctrlx_datalayer_helper.h new file mode 100644 index 0000000..b26745d --- /dev/null +++ b/simple-listener-dl-cpp/src/app/src/ctrlx_datalayer_helper.h @@ -0,0 +1,144 @@ +/* + * SPDX-FileCopyrightText: Bosch Rexroth AG + * + * SPDX-License-Identifier: MIT + */ + +#ifndef CTRLX_DATALAYER_HELPER_H +#define CTRLX_DATALAYER_HELPER_H + + /*! \file + * + * This header file provides auxiliary methods to create ctrlX Datalayer client and provider connections to ctrlX CORE devices. + * It can be used for both running in an app build environment (QEMU VM) and within the snap environment on the ctrlX CORE. + * + * Feel free to use it in your projects and to change it if necessary. + * + * For ease of use, the default values for IP address, user, password and SSL port are chosen to match the settings of a + * newly created ctrlX CORE device: + * + * ip="192.168.1.1" + * user="boschrexroth" + * password="boschrexroth" + * ssl_port=443 + * + * If these values do not suit your use case, explicitly pass the parameters that require different values. + * Here some examples: + * + * 1. ctrlX CORE or ctrlX COREvirtual with another IP address, user and password: + * + * client, client_connection = get_client(system, ip="192.168.1.100", user="admin", password="-$_U/{X$aG}Z3/e<") + * + * 2. ctrlX COREvirtual with port forwarding running on the same host as the app build environment (QEMU VM): + * + * client, client_connection = get_client(system, ip="10.0.2.2", ssl_port=8443) + * + * Remarks: + * 10.0.2.2 is the IP address of the host from the point of view of the app build environment (QEMU VM). + * 8443 is the host port which is forwarded to the SSL port (=433) of the ctrlX COREvirtual + * + * + * IMPORTANT: + * You need not change the parameter settings before building a snap and installing the snap on a ctrlX CORE. + * The method get_connection_string detects the snap environment and uses automatically inter process communication. + * Therefor the connection string to the ctrlX Datalayer is: + * + * "ipc://" + * + */ + +#include +#include + +#include "comm/datalayer/datalayer.h" +#include "comm/datalayer/datalayer_system.h" + + //! Retrieve environment variable SNAP +static const char* snapPath() +{ + return std::getenv("SNAP"); +} + +//! Test if code is runnning in snap environment +static bool isSnap() +{ + return snapPath() != nullptr; +} + +//! Get Datalayer connection string +//! @param[in] ip IP address of the ctrlX CORE: 10.0.2.2 is ctrlX COREvirtual with port forwarding +//! @param[in] user User name +//! @param[in] password The password +//! @param[in] sslPort The port number for SSL: 8443 if ctrlX COREvirtual with port forwarding 8443:443 +//! @result Connection string +static std::string getConnectionString( + const std::string& ip = "10.0.2.2", + const std::string& user = "boschrexroth", + const std::string& password = "boschrexroth", + int sslPort = 8443) +{ + if (isSnap()) + { + std::cout << "INFO Is snap" << std::endl; + return DL_IPC; + } + + std::string connectionString = DL_TCP + user + std::string(":") + password + std::string("@") + ip; + + if (443 == sslPort) + { + return connectionString; + } + + return connectionString + std::string("?sslport=") + std::to_string(sslPort); +} + +//! Get Datalayer Client instance +//! @param[in] datalayerSystem Datalayer.System instance +//! @param[in] ip IP address of the ctrlX CORE: 10.0.2.2 is ctrlX COREvirtual with port forwarding +//! @param[in] user User name +//! @param[in] password The password +//! @param[in] sslPort The port number for SSL: 8443 if ctrlX COREvirtual with port forwarding 8443:443 +//! @result IClient instance or nullptr on error +static comm::datalayer::IClient* getClient(comm::datalayer::DatalayerSystem& datalayerSystem, + const std::string& ip = "10.0.2.2", + const std::string& user = "boschrexroth", + const std::string& password = "boschrexroth", int sslPort = 8443) +{ + std::string connectionString = getConnectionString(ip, user, password, sslPort); + comm::datalayer::IClient* client = datalayerSystem.factory()->createClient(connectionString); + if (client->isConnected()) + { + return client; + } + + delete client; + + return nullptr; +} + +//! Get Datalayer Provider instance +//! @param[in] datalayerSystem Datalayer.System instance +//! @param[in] ip IP address of the ctrlX CORE: 10.0.2.2 is ctrlX COREvirtual with port forwarding +//! @param[in] user User name +//! @param[in] password The password +//! @param[in] sslPort The port number for SSL: 8443 if ctrlX COREvirtual with port forwarding 8443:443 +//! @result IProvider instance or nullptr on error +static comm::datalayer::IProvider* getProvider(comm::datalayer::DatalayerSystem& datalayerSystem, + const std::string& ip = "10.0.2.2", + const std::string& user = "boschrexroth", + const std::string& password = "boschrexroth", int sslPort = 8443) +{ + std::string connectionString = getConnectionString(ip, user, password, sslPort); + comm::datalayer::IProvider* provider = datalayerSystem.factory()->createProvider(connectionString); + if (provider->start() == DL_OK && provider->isConnected()) + { + return provider; + } + + delete provider; + + return nullptr; +} + +#endif diff --git a/simple-listener-dl-cpp/src/app/src/subscriber_member_function.cpp b/simple-listener-dl-cpp/src/app/src/subscriber_member_function.cpp new file mode 100644 index 0000000..533fb37 --- /dev/null +++ b/simple-listener-dl-cpp/src/app/src/subscriber_member_function.cpp @@ -0,0 +1,159 @@ +// 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 "ctrlx_datalayer_helper.h" +#include "rclcpp/rclcpp.hpp" +#include "std_msgs/msg/string.hpp" + +using std::placeholders::_1; +#include +#include + +#include "comm/datalayer/datalayer.h" +#include "comm/datalayer/datalayer_system.h" + +// Add some signal Handling so we are able to abort the program with sending sigint +static bool g_endProcess = false; + +static void signalHandler(int signal) +{ + std::cout << "signal: " << signal << std::endl; + g_endProcess = true; + // Clean up datalayer instances so that process ends properly + // Attention: Doesn't return if any provider or client instance is still runnning + +} + +using comm::datalayer::IProviderNode; + +// Basic class Provider node interface for providing data to the system +class MyProviderNode: public IProviderNode +{ +private: + comm::datalayer::Variant m_data; + +public: + MyProviderNode(comm::datalayer::Variant data) + : m_data(data) + {}; + void setString(const std::string& input) + { + m_data.setValue(input); + + } + virtual ~MyProviderNode() override {}; + + // Create function of an object. Function will be called whenever a object should be created. + virtual void onCreate(const std::string& address, const comm::datalayer::Variant* data, const comm::datalayer::IProviderNode::ResponseCallback& callback) override + { + callback(comm::datalayer::DlResult::DL_FAILED, nullptr); + } + + // Read function of a node. Function will be called whenever a node should be read. + virtual void onRead(const std::string& address, const comm::datalayer::Variant* data, const comm::datalayer::IProviderNode::ResponseCallback& callback) override + { + comm::datalayer::Variant dataRead; + dataRead = m_data; + callback(comm::datalayer::DlResult::DL_OK, &dataRead); + } + + // Write function of a node. Function will be called whenever a node should be written. + virtual void onWrite(const std::string& address, const comm::datalayer::Variant* data, const comm::datalayer::IProviderNode::ResponseCallback& callback) override + { + std::cout << "INFO onWrite " << address << std::endl; + + if (data->getType() != m_data.getType()) + { + callback(comm::datalayer::DlResult::DL_TYPE_MISMATCH, nullptr); + } + + m_data = *data; + callback(comm::datalayer::DlResult::DL_OK, data); + } + + // Remove function for an object. Function will be called whenever a object should be removed. + virtual void onRemove(const std::string& address, const comm::datalayer::IProviderNode::ResponseCallback& callback) override + { + callback(comm::datalayer::DlResult::DL_FAILED, nullptr); + } + + // Browse function of a node. Function will be called to determine children of a node. + virtual void onBrowse(const std::string& address, const comm::datalayer::IProviderNode::ResponseCallback& callback) override + { + callback(comm::datalayer::DlResult::DL_FAILED, nullptr); + } + + // Read function of metadata of an object. Function will be called whenever a node should be written. + virtual void onMetadata(const std::string& address, const comm::datalayer::IProviderNode::ResponseCallback& callback) override + { + // Keep this comment! Can be used as sample creating metadata programmatically. + // callback(comm::datalayer::DlResult::DL_OK, &_metaData); + + // Take metadata from metadata.mddb + callback(comm::datalayer::DlResult::DL_FAILED, nullptr); + } +}; + + +class MinimalSubscriber : public rclcpp::Node +{ + +public: + comm::datalayer::DatalayerSystem datalayerSystem; + comm::datalayer::IProvider* provider; + comm::datalayer::Variant myString; + MyProviderNode *pippo = new MyProviderNode(myString); + + MinimalSubscriber() + : Node("minimal_subscriber") + { + subscription_ = this->create_subscription( + "ros2_simple_talker_cpp", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1)); + + // Starts the ctrlX Data Layer system without a new broker because one broker is already running on ctrlX CORE + datalayerSystem.start(false); + provider = getProvider(datalayerSystem); // ctrlX CORE (virtual) + pippo->setString("No Message yet"); + std::cout << "INFO Register node 'ros/listenercpp/mymessage' " << std::endl; + comm::datalayer::DlResult result = provider->registerNode("ros/listenercpp/mymessage",pippo); + if (STATUS_FAILED(result)) + { + std::cout << "WARN Register node 'sdk-cpp-registernode/myString' failed with: " << result.toString() << std::endl; + } + } + +private: + void topic_callback(const std_msgs::msg::String & msg) const + { + RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg.data.c_str()); + pippo->setString( 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/simple-listener-dl-cpp/wrapper/run-listener.sh b/simple-listener-dl-cpp/wrapper/run-listener.sh new file mode 100644 index 0000000..12e6059 --- /dev/null +++ b/simple-listener-dl-cpp/wrapper/run-listener.sh @@ -0,0 +1,12 @@ +#!/bin/bash +export ROS_BASE=$SNAP/rosruntime +export PYTHONPATH=$PYTHONPATH:$ROS_BASE/lib/python3.10/site-packages +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ROS_BASE/lib +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ROS_BASE/lib/$TRIPLET +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ROS_BASE/usr/lib +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ROS_BASE/usr/include/comm/datalayer/ +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ROS_BASE/usr/lib/x86_64-linux-gnu/ +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ROS_BASE/usr/lib/$TRIPLET +source $ROS_BASE/opt/ros/humble/setup.bash #source if ros2 using debian package is build as a snap +#source $ROS_BASE/setup.bash #source if ros2 is built from source +source $SNAP/local_setup.bash && $SNAP/app/lib/app/listener \ No newline at end of file diff --git a/simple-talker-dl-cpp/.gitignore b/simple-talker-dl-cpp/.gitignore new file mode 100644 index 0000000..7cfd40a --- /dev/null +++ b/simple-talker-dl-cpp/.gitignore @@ -0,0 +1,8 @@ +build/ +install/ +log/ +__pycache__/ +prime/ +parts/ +stage/ +squashfs-root/ \ No newline at end of file diff --git a/simple-talker-dl-cpp/.vscode/settings.json b/simple-talker-dl-cpp/.vscode/settings.json new file mode 100644 index 0000000..a9edd26 --- /dev/null +++ b/simple-talker-dl-cpp/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "ros.distro": "humble", + "files.associations": { + "iostream": "cpp" + } +} \ No newline at end of file diff --git a/simple-talker-dl-cpp/.vscode/tasks.json b/simple-talker-dl-cpp/.vscode/tasks.json new file mode 100644 index 0000000..08d9005 --- /dev/null +++ b/simple-talker-dl-cpp/.vscode/tasks.json @@ -0,0 +1,28 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: gcc build active file", + "command": "/usr/bin/gcc", + "args": [ + "-fdiagnostics-color=always", + "-g", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Task generated by Debugger." + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/simple-talker-dl-cpp/README.md b/simple-talker-dl-cpp/README.md new file mode 100644 index 0000000..9a7eb10 --- /dev/null +++ b/simple-talker-dl-cpp/README.md @@ -0,0 +1,61 @@ +# Simple ROS 2 Subscriber in C++ + +A simple ROS 2 node which cyclically reads the CPU usage from the ctrlX Data Layer by reading the address `framework/metrics/system/cpu-utilisation-percent` and pushes on the ROS2 topic `ros2_simple_talker_cpp`. +In the ctrlX Data Layer you can find any kind of information about the device, process, fieldbus and periphery. +Please note, that this is a very basic example which uses the simplest method of reading in the ctrlX Data Layer. For improved performance and scalability you should consider creating a subscription instead of performing synchronuous (blocking) reads. + +## Prerequisites + +* `ros-base` snap. The base snap which provides the ROS 2 runtime binaries. Has to be installed on ctrlX OS. See [ROS 2 Humble Base Snap](../ros2-base-humble-deb/README.md). +* An Ubuntu based build environment to build an app. See [ctrlX Automation SDK](https://github.com/boschrexroth/ctrlx-automation-sdk). + +## Basis for this Project + +This project is based on the official ROS 2 Tutorial: [Writing a simple publisher and subscriber (C++)](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Cpp-Publisher-And-Subscriber.html#writing-a-simple-publisher-and-subscriber-c). + +## Building a Snap + +Building a snap has two steps: + +1. Colcon build: `CMakeLists.txt` defines how compile and link the C++ sources. +2. snap build: `snap/snapcraft.yaml` defines how the compiled binaries are packed into the snap and how they are called on ctrlX OS. + +### Colcon Configuration + +The colcon build tool is configured by `CMakeLists.txt`. + +This section defines the ROS 2 packages needed: + + find_package(ament_cmake REQUIRED) + find_package(rclcpp REQUIRED) + find_package(std_msgs REQUIRED) + +And here the executables and their dependencies are defined: + + add_executable(listener src/subscriber_member_function.cpp) + ament_target_dependencies(listener rclcpp std_msgs) + +### Snapcraft Configuration + +`snap/snapcraft.yaml` defines how the snap will be build: + +* `install/` is dumped into the snap +* also `wrapper/` +* Two apps (talker and listener) are copied into the snap and started as services +* The snap - respectively the executables - uses the content interface of the `ros-base` snap (here the ROS 2 runtime is provided). + +### Build the Snap + +Start this script: + + ./build-snap-amd64.sh + +## About + +SPDX-FileCopyrightText: Copyright (c) 2023 Bosch Rexroth AG + + + +## Licenses + +SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/simple-talker-dl-cpp/build-snap-amd64.sh b/simple-talker-dl-cpp/build-snap-amd64.sh new file mode 100644 index 0000000..b726b25 --- /dev/null +++ b/simple-talker-dl-cpp/build-snap-amd64.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +source colcon-build.sh +if [ $? -eq 0 ] +then + echo " " +else + exit 1 +fi + +snapcraft clean --destructive-mode +snapcraft --destructive-mode \ No newline at end of file diff --git a/simple-talker-dl-cpp/colcon-build.sh b/simple-talker-dl-cpp/colcon-build.sh new file mode 100644 index 0000000..711617c --- /dev/null +++ b/simple-talker-dl-cpp/colcon-build.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +rosdep install -i --from-path src --rosdistro humble -y +if [ $? -eq 0 ] +then + echo " " +else + exit 1 +fi + +rm -rf install/ +mkdir -p install/app +rm -rf build/ +rm -rf log/ + +source /opt/ros/humble/setup.bash +colcon build \ No newline at end of file diff --git a/simple-talker-dl-cpp/snap/snapcraft.yaml b/simple-talker-dl-cpp/snap/snapcraft.yaml new file mode 100644 index 0000000..26250bc --- /dev/null +++ b/simple-talker-dl-cpp/snap/snapcraft.yaml @@ -0,0 +1,48 @@ +name: ros2-simple-talker-dl-cpp +version: '2.2.0' +summary: Snap with simple ROS 2 listener +description: | + A simple ROS 2 listener which receives ROS 2 messages on topic 'ros2_simple_cpp'. + +base: core22 +grade: stable +confinement: strict + +parts: + ros-app: + plugin: dump + source: install + stage-packages: + - libzmq5 + - ctrlx-datalayer + override-build: | + snapcraftctl build + + wrapper-scripts: + plugin: dump + source: wrapper/ + organize: + ./run-listener.sh : usr/bin/run-listener + +apps: + listener: + command: usr/bin/run-listener + plugs: + - ros-base + - network + - network-bind + daemon: simple + passthrough: + restart-condition: always + restart-delay: 10s + +plugs: + ros-base: + interface: content + content: executables + target: $SNAP/rosruntime + + datalayer: + interface: content + content: datalayer + target: $SNAP_DATA/.datalayer diff --git a/simple-talker-dl-cpp/src/app/CMakeLists.txt b/simple-talker-dl-cpp/src/app/CMakeLists.txt new file mode 100644 index 0000000..cfce18c --- /dev/null +++ b/simple-talker-dl-cpp/src/app/CMakeLists.txt @@ -0,0 +1,78 @@ +cmake_minimum_required(VERSION 3.8) +project(app) +set(TARGET_PROJECT_NAME listener) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(std_msgs REQUIRED) + + +# +# Set link directories +# +MESSAGE( STATUS "Libraries directory: ${LIBRARY_DIR}") +link_directories( + ${LIBRARY_DIR} + ${LIBRARY_DEP_DIR} + ) + +# User dependency directory +# +set (USER_DEPENDENCY_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../..) + +SET ( PRIVATE_INCLUDE_DIRS + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_LIST_DIR}/include + ${USER_DEPENDENCY_DIR}/include + ${USER_DEPENDENCY_DIR}/include/comm.datalayer + ) + +# Set target link libraries + + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +add_executable(listener src/subscriber_member_function.cpp) +ament_target_dependencies(listener rclcpp std_msgs) + + +# +# Set target include directories +# +target_include_directories ( ${TARGET_PROJECT_NAME} + PUBLIC ${PUBLIC_INCLUDE_DIRS} + PUBLIC ${LIBRARY_INCLUDES} + PRIVATE ${PRIVATE_INCLUDE_DIRS} +) +# +target_link_libraries(${TARGET_PROJECT_NAME} -Wl,--no-undefined) +target_link_libraries(${TARGET_PROJECT_NAME} + libcomm_datalayer.so + pthread + systemd + zmq + ssl + crypto +) + +install(TARGETS + listener + DESTINATION lib/${PROJECT_NAME}) + +ament_package() diff --git a/simple-talker-dl-cpp/src/app/package.xml b/simple-talker-dl-cpp/src/app/package.xml new file mode 100644 index 0000000..87f99cc --- /dev/null +++ b/simple-talker-dl-cpp/src/app/package.xml @@ -0,0 +1,22 @@ + + + + app + 2.2.0 + Simple ROS 2 subscriber (listener) in cpp + boschrexroth + MIT License + + ament_cmake + + rclcpp + std_msgs + sensor_msgs + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/simple-talker-dl-cpp/src/app/src/subscriber_member_function.cpp b/simple-talker-dl-cpp/src/app/src/subscriber_member_function.cpp new file mode 100644 index 0000000..14bfbeb --- /dev/null +++ b/simple-talker-dl-cpp/src/app/src/subscriber_member_function.cpp @@ -0,0 +1,163 @@ +// 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; + +#include +#include +#include + +#include +#include + +#include "comm/datalayer/datalayer.h" +#include "comm/datalayer/datalayer_system.h" + + +// Add some signal Handling so we are able to abort the program with sending sigint +static bool g_endProcess = false; + +static void signalHandler(int signal) +{ + std::cout << "signal: " << signal << std::endl; + g_endProcess = true; + // Clean up datalayer instances so that process ends properly + // Attention: Doesn't return if any provider or client instance is still runnning + +} +//! Retrieve environment variable SNAP +//! @result The content of SNAP ales nullptr if not available +static const char* snapPath() +{ + return std::getenv("SNAP"); +} + +//! Test if code is runnning in snap environment +//! @result True if running snap environment +static bool isSnap() +{ + return snapPath() != nullptr; +} + +//! Get Datalayer connection string +//! @param[in] ip IP address of the ctrlX CORE: 10.0.2.2 is ctrlX COREvirtual with port forwarding +//! @param[in] user User name +//! @param[in] password The password +//! @param[in] sslPort The port number for SSL: 8443 if ctrlX COREvirtual with port forwarding 8443:443 +//! @result Connection string +static std::string getConnectionString( + const std::string& ip = "10.0.2.2", + const std::string& user = "boschrexroth", + const std::string& password = "boschrexroth", + int sslPort = 8443) +{ + if (isSnap()) + { + return DL_IPC; + } + + std::string connectionString = DL_TCP + user + std::string(":") + password + std::string("@") + ip; + + if (443 == sslPort) + { + return connectionString; + } + + return connectionString + std::string("?sslport=") + std::to_string(sslPort); +} + +class MinimalPublisher : public rclcpp::Node +{ +public: + MinimalPublisher() + : Node("minimal_publisher") + { + publisher_ = this->create_publisher("ros2_simple_talker_cpp", 10); + } + + /// @brief + void myprint(const std::string& input) + + { + auto message = std_msgs::msg::String(); + message.data = "CPU Percentage: " + input; + 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[]) +{ + // Initialize ROS 2 + rclcpp::init(argc, argv); + MinimalPublisher pippo; + + std::cout << "INFO Starting ctrlX Data Layer system (without broker)" << std::endl; + comm::datalayer::DatalayerSystem datalayerSystem; + datalayerSystem.start(false); + + auto connectionString = getConnectionString(); // default: ctrlX CORE or ctrlX COREvirtual with Network Adpater + std::cout << "INFO Creating ctrlX Data Layer client connection to " << connectionString << " ..." << std::endl; + auto dataLayerClient = datalayerSystem.factory()->createClient(connectionString); + + int counter = 1; + while (dataLayerClient->isConnected()) + { + std::cout << "Loop #" << counter++ << std::endl; + + //Synchronous read of a ctrlX Data Layer node with a simple data type + auto cpuUtilisationPercentAddress = "framework/metrics/system/cpu-utilisation-percent"; + comm::datalayer::Variant cpuUtilisationPercentValue; + std::cout << "INFO Reading " << cpuUtilisationPercentAddress << " synchronously..." << std::endl; + auto result = dataLayerClient->readSync(cpuUtilisationPercentAddress, &cpuUtilisationPercentValue); + if (result != DL_OK){ + std::cout <<"WARN Reading " << cpuUtilisationPercentAddress << " failed with: " << result.toString() << std::endl; + } + else + { + if (cpuUtilisationPercentValue.getType() == comm::datalayer::VariantType::FLOAT64) + { + std::cout << "INFO Value of " << cpuUtilisationPercentAddress << ": " << double(cpuUtilisationPercentValue) << " %" << std::endl; + auto str = std::to_string(double(cpuUtilisationPercentValue)); + pippo.myprint(str); + } + else + { + std::cout << "WARN Value of " << cpuUtilisationPercentAddress << " has unexpected type: " << cpuUtilisationPercentValue.typeAsString() << std::endl; + } + } + + std::cout << "INFO Sleeping..." << std::endl; + sleep(2); + } + + std::cout << "ERROR ctrlX Data Layer connection is broken" << std::endl; + + delete dataLayerClient; + datalayerSystem.stop(); + + return 1; // We exit because an error happend +} \ No newline at end of file diff --git a/simple-talker-dl-cpp/wrapper/run-listener.sh b/simple-talker-dl-cpp/wrapper/run-listener.sh new file mode 100644 index 0000000..12e6059 --- /dev/null +++ b/simple-talker-dl-cpp/wrapper/run-listener.sh @@ -0,0 +1,12 @@ +#!/bin/bash +export ROS_BASE=$SNAP/rosruntime +export PYTHONPATH=$PYTHONPATH:$ROS_BASE/lib/python3.10/site-packages +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ROS_BASE/lib +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ROS_BASE/lib/$TRIPLET +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ROS_BASE/usr/lib +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ROS_BASE/usr/include/comm/datalayer/ +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ROS_BASE/usr/lib/x86_64-linux-gnu/ +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ROS_BASE/usr/lib/$TRIPLET +source $ROS_BASE/opt/ros/humble/setup.bash #source if ros2 using debian package is build as a snap +#source $ROS_BASE/setup.bash #source if ros2 is built from source +source $SNAP/local_setup.bash && $SNAP/app/lib/app/listener \ No newline at end of file diff --git a/simple-talker-py/README.md b/simple-talker-py/README.md index 09a08f0..c9f42f9 100644 --- a/simple-talker-py/README.md +++ b/simple-talker-py/README.md @@ -64,7 +64,7 @@ With following command you can combine the build, installation and test steps - ## About -SPDX-FileCopyrightText: Copyright (c) 2023 Bosch Rexroth AG +SPDX-FileCopyrightText: Copyright (c) 2024 Bosch Rexroth AG