├── requirements.txt ├── app ├── bin │ └── plcnext-ros-bridge ├── compose.yml ├── app_info.json └── initscript.sh ├── phoenix_bridge ├── phoenix_bridge │ ├── __init__.py │ ├── param_parser.py │ └── msg_parser.py ├── config │ ├── services_params.yaml │ ├── liveliness_config.yaml │ └── interface_description.yaml ├── include │ └── phoenix_bridge │ │ ├── ServiceStubs │ │ ├── Includes │ │ │ ├── ArpTypes.pb.h │ │ │ ├── DateTime.pb.h │ │ │ ├── DataType.pb.h │ │ │ ├── Severity.pb.h │ │ │ ├── VariableInfo.pb.h │ │ │ ├── SecurityToken.pb.h │ │ │ └── AuthenticationError.pb.h │ │ ├── ArpTypes.grpc.pb.cc │ │ ├── Plc │ │ │ ├── DataType.grpc.pb.cc │ │ │ ├── Gds │ │ │ │ ├── ReadItem.grpc.pb.cc │ │ │ │ ├── ForceItem.grpc.pb.cc │ │ │ │ ├── WriteItem.grpc.pb.cc │ │ │ │ ├── VariableInfo.grpc.pb.cc │ │ │ │ ├── DataAccessError.grpc.pb.cc │ │ │ │ ├── SubscriptionKind.grpc.pb.cc │ │ │ │ ├── ReadItem.grpc.pb.h │ │ │ │ ├── ForceItem.grpc.pb.h │ │ │ │ ├── WriteItem.grpc.pb.h │ │ │ │ ├── VariableInfo.grpc.pb.h │ │ │ │ ├── DataAccessError.grpc.pb.h │ │ │ │ ├── SubscriptionKind.grpc.pb.h │ │ │ │ ├── SubscriptionKind.pb.cc │ │ │ │ ├── DataAccessError.pb.cc │ │ │ │ ├── SubscriptionKind.pb.h │ │ │ │ ├── DataAccessError.pb.h │ │ │ │ └── VariableInfo.pb.h │ │ │ ├── DataType.grpc.pb.h │ │ │ ├── DataType.pb.cc │ │ │ └── DataType.pb.h │ │ └── ArpTypes.grpc.pb.h │ │ ├── include_types.h │ │ ├── phoenix_comm.hpp │ │ ├── phoenix_io_services.hpp │ │ ├── bridge_type.hpp │ │ └── read_conversions.hpp ├── test │ ├── scripts │ │ ├── pub_string.py │ │ ├── pub_twist.py │ │ ├── single_get_io.py │ │ ├── read_analog_io.py │ │ ├── single_set_io.py │ │ ├── write_analog_io.py │ │ ├── batch_get_io.py │ │ ├── batch_set_io.py │ │ └── pub_odom.py │ ├── launch_test_bridge.test.py │ └── test_write_conversions.cpp ├── src │ ├── phoenix_io_services_node.cpp │ ├── phoenix_bridge_node.cpp │ ├── liveliness_check.cpp │ └── phoenix_io_services.cpp ├── package.xml ├── launch │ └── launch_phoenix_bridge.py └── CMakeLists.txt ├── phoenix_interfaces ├── msg │ └── SetIO.msg ├── srv │ ├── BatchSetIO.srv │ ├── SingleGetIO.srv │ ├── SingleSetIO.srv │ ├── BatchGetIO.srv │ └── AnalogIO.srv ├── CMakeLists.txt └── package.xml ├── .dockerignore ├── dep.repo ├── dockerfile_manually ├── Dockerfile ├── dockerfile ├── .gitignore ├── ci └── gitlab_templates │ └── RULES.yml ├── .gitlab-ci.yml └── LICENSE /requirements.txt: -------------------------------------------------------------------------------- 1 | cogapp 2 | -------------------------------------------------------------------------------- /app/bin/plcnext-ros-bridge: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | -------------------------------------------------------------------------------- /phoenix_bridge/phoenix_bridge/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /phoenix_interfaces/msg/SetIO.msg: -------------------------------------------------------------------------------- 1 | string datapath 2 | bool value 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | .gitlab-ci.yml 4 | .dockerignore 5 | -------------------------------------------------------------------------------- /phoenix_interfaces/srv/BatchSetIO.srv: -------------------------------------------------------------------------------- 1 | SetIO[] payload 2 | --- 3 | bool status # If status=false, operation failed 4 | 5 | -------------------------------------------------------------------------------- /phoenix_interfaces/srv/SingleGetIO.srv: -------------------------------------------------------------------------------- 1 | string datapath 2 | --- 3 | bool value 4 | bool status # If status=false, operation failed 5 | -------------------------------------------------------------------------------- /phoenix_interfaces/srv/SingleSetIO.srv: -------------------------------------------------------------------------------- 1 | string datapath 2 | bool value 3 | --- 4 | bool status # If status=false, operation failed 5 | -------------------------------------------------------------------------------- /phoenix_interfaces/srv/BatchGetIO.srv: -------------------------------------------------------------------------------- 1 | string[] datapaths 2 | --- 3 | bool[] values 4 | bool status # If status=false, operation failed 5 | -------------------------------------------------------------------------------- /dep.repo: -------------------------------------------------------------------------------- 1 | repositories: 2 | ${DEP_REPO_NAME}: 3 | type: git 4 | url: ${DEP_REPO_URL} 5 | version: heads/${ROS_DISTRO}/devel 6 | -------------------------------------------------------------------------------- /phoenix_interfaces/srv/AnalogIO.srv: -------------------------------------------------------------------------------- 1 | string instance_path 2 | float64 value 3 | --- 4 | string instance_path 5 | float64 value 6 | bool status 7 | -------------------------------------------------------------------------------- /phoenix_bridge/config/services_params.yaml: -------------------------------------------------------------------------------- 1 | # Params for services 2 | 3 | phoenix_services: 4 | ros__parameters: 5 | grpc: 6 | address: "unix:/run/plcnext/grpc.sock" 7 | type: "tcp" 8 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Includes/ArpTypes.pb.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // Copyright PHOENIX CONTACT Electronics GmbH 4 | // 5 | /////////////////////////////////////////////////////////////////////////////// 6 | #pragma once 7 | #include "../ArpTypes.pb.h" 8 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Includes/DateTime.pb.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // Copyright PHOENIX CONTACT Electronics GmbH 4 | // 5 | /////////////////////////////////////////////////////////////////////////////// 6 | #pragma once 7 | #include "../DateTime.pb.h" 8 | 9 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Includes/DataType.pb.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // Copyright PHOENIX CONTACT Electronics GmbH 4 | // 5 | /////////////////////////////////////////////////////////////////////////////// 6 | #pragma once 7 | #include "../Plc/DataType.pb.h" 8 | 9 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Includes/Severity.pb.h: -------------------------------------------------------------------------------- 1 | 2 | /////////////////////////////////////////////////////////////////////////////// 3 | // 4 | // Copyright PHOENIX CONTACT Electronics GmbH 5 | // 6 | /////////////////////////////////////////////////////////////////////////////// 7 | #pragma once 8 | #include "../System/Nm/Severity.pb.h" -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Includes/VariableInfo.pb.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // Copyright PHOENIX CONTACT Electronics GmbH 4 | // 5 | /////////////////////////////////////////////////////////////////////////////// 6 | #pragma once 7 | #include "../Plc/Gds/VariableInfo.pb.h" -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Includes/SecurityToken.pb.h: -------------------------------------------------------------------------------- 1 | 2 | /////////////////////////////////////////////////////////////////////////////// 3 | // 4 | // Copyright PHOENIX CONTACT Electronics GmbH 5 | // 6 | /////////////////////////////////////////////////////////////////////////////// 7 | #pragma once 8 | #include "../System/Security/SecurityToken.pb.h" -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Includes/AuthenticationError.pb.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // Copyright PHOENIX CONTACT Electronics GmbH 4 | // 5 | /////////////////////////////////////////////////////////////////////////////// 6 | #pragma once 7 | #include "../System/Um/AuthenticationError.pb.h" 8 | 9 | -------------------------------------------------------------------------------- /phoenix_bridge/config/liveliness_config.yaml: -------------------------------------------------------------------------------- 1 | 2 | liveliness_check: 3 | ros__parameters: 4 | grpc: 5 | address: "unix:/run/plcnext/grpc.sock" 6 | type: "tcp" 7 | liveliness_bool: "Arp.Plc.Eclr/xLiveliness" 8 | liveliness_timeout_s: 1.0 # Timeout in seconds. If no response recevied in this interval, shout a warning 9 | poll_rate_hz: 1 # Poll rate in Hz to check the bool 10 | -------------------------------------------------------------------------------- /phoenix_interfaces/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | project(phoenix_interfaces) 3 | 4 | find_package(ament_cmake REQUIRED) 5 | find_package(rosidl_default_generators REQUIRED) 6 | 7 | rosidl_generate_interfaces(${PROJECT_NAME} 8 | "msg/SetIO.msg" 9 | "srv/BatchGetIO.srv" 10 | "srv/BatchSetIO.srv" 11 | "srv/SingleGetIO.srv" 12 | "srv/SingleSetIO.srv" 13 | "srv/AnalogIO.srv" 14 | ) 15 | 16 | install(DIRECTORY 17 | msg 18 | srv 19 | DESTINATION share/${PROJECT_NAME} 20 | ) 21 | 22 | ament_package() 23 | -------------------------------------------------------------------------------- /app/compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | rosbridge: 3 | # add _${APP_UNIQUE_NAME} to all of your container_name 4 | container_name: rosbridge_${APP_UNIQUE_NAME} 5 | # Use the Image ID or fully qualified path incl. sha256 to avoid conflicts 6 | image: §§IMAGE_ID§§ 7 | stdin_open: true # podman run -i 8 | tty: true # podman run -t 9 | volumes: 10 | - /run/plcnext/grpc.sock:/run/plcnext/grpc.sock 11 | restart: unless-stopped 12 | network_mode: host 13 | environment: 14 | - ROS_DOMAIN_ID=0 15 | command: bash -c "ros2 launch phoenix_bridge launch_phoenix_bridge.py" -------------------------------------------------------------------------------- /app/app_info.json: -------------------------------------------------------------------------------- 1 | { 2 | "plcnextapp": { 3 | "name": "plcnext-ros-bridge", 4 | "identifier": "60002172000613", 5 | "version": "§§ROS_BRIDGE_VERSION§§ §§ROS_DISTRO§§", 6 | "target": "§§TARGETS§§", 7 | "minfirmware_version": "22.6.0", 8 | "manufacturer": "Phoenix Contact Electronics" 9 | }, 10 | "datastorage": { 11 | "persistentdata": true, 12 | "temporarydata": true 13 | }, 14 | "linuxdaemons" : 15 | [ 16 | { 17 | "path": "/bin/plcnext-ros-bridge", 18 | "cmdargs" : "", 19 | "starttime": "99", 20 | "initScriptTemplate": "initscript.sh" 21 | } 22 | ], 23 | "plcnextservices" :[] 24 | } 25 | -------------------------------------------------------------------------------- /phoenix_interfaces/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | phoenix_interfaces 5 | 0.6.2 6 | ROS2 Phoenix interfaces 7 | 8 | todo 9 | Tejas 10 | 11 | Apache 2.0 12 | 13 | ament_cmake 14 | 15 | rosidl_default_generators 16 | 17 | rosidl_default_runtime 18 | 19 | rosidl_interface_packages 20 | 21 | 22 | ament_cmake 23 | 24 | 25 | -------------------------------------------------------------------------------- /phoenix_bridge/test/scripts/pub_string.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import rclpy 4 | 5 | from random import randint 6 | from rclpy.node import Node 7 | from std_msgs.msg import String 8 | 9 | 10 | class MinimalPublisher(Node): 11 | 12 | def __init__(self): 13 | super().__init__('twist_publisher') 14 | self.publisher_ = self.create_publisher(String, '/sub_string_1', 10) 15 | timer_period = 1 # seconds 16 | self.timer = self.create_timer(timer_period, self.timer_callback) 17 | 18 | def timer_callback(self): 19 | msg = String() 20 | msg.data = "hi" + str(randint(1,100)) 21 | 22 | self.publisher_.publish(msg) 23 | 24 | def main(args=None): 25 | rclpy.init(args=args) 26 | 27 | minimal_publisher = MinimalPublisher() 28 | 29 | rclpy.spin(minimal_publisher) 30 | 31 | minimal_publisher.destroy_node() 32 | rclpy.shutdown() 33 | 34 | if __name__ == '__main__': 35 | main() 36 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/ArpTypes.grpc.pb.cc: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: ArpTypes.proto 4 | 5 | #include "ArpTypes.pb.h" 6 | #include "ArpTypes.grpc.pb.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | namespace Arp { 23 | namespace Type { 24 | namespace Grpc { 25 | 26 | } // namespace Arp 27 | } // namespace Type 28 | } // namespace Grpc 29 | 30 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/DataType.grpc.pb.cc: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: DataType.proto 4 | 5 | #include "DataType.pb.h" 6 | #include "DataType.grpc.pb.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | namespace Arp { 23 | namespace Plc { 24 | namespace Grpc { 25 | 26 | } // namespace Arp 27 | } // namespace Plc 28 | } // namespace Grpc 29 | 30 | -------------------------------------------------------------------------------- /phoenix_bridge/src/phoenix_io_services_node.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 Fraunhofer IPA 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #include 18 | 19 | #include "phoenix_bridge/phoenix_io_services.hpp" 20 | 21 | int main(int argc, char ** argv) 22 | { 23 | rclcpp::init(argc, argv); 24 | 25 | rclcpp::executors::MultiThreadedExecutor mte; 26 | rclcpp::NodeOptions options; 27 | auto node = std::make_shared("phoenix_services", options); 28 | mte.add_node(node); 29 | mte.spin(); 30 | rclcpp::shutdown(); 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /phoenix_bridge/test/scripts/pub_twist.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import rclpy 4 | 5 | from random import randint 6 | from rclpy.node import Node 7 | from geometry_msgs.msg import Twist 8 | 9 | 10 | class MinimalPublisher(Node): 11 | 12 | def __init__(self): 13 | super().__init__('twist_publisher') 14 | self.publisher_ = self.create_publisher(Twist, '/sub_twist_1', 10) 15 | timer_period = 1 # seconds 16 | self.timer = self.create_timer(timer_period, self.timer_callback) 17 | 18 | def timer_callback(self): 19 | msg = Twist() 20 | msg.linear.x = randint(1, 500)/100 21 | msg.linear.y = randint(1, 500)/100 22 | msg.linear.z = randint(1, 500)/100 23 | msg.angular.x = randint(1, 314)/100 24 | msg.angular.y = randint(1, 314)/100 25 | msg.angular.z = randint(1, 314)/100 26 | 27 | self.publisher_.publish(msg) 28 | 29 | def main(args=None): 30 | rclpy.init(args=args) 31 | 32 | minimal_publisher = MinimalPublisher() 33 | 34 | rclpy.spin(minimal_publisher) 35 | 36 | minimal_publisher.destroy_node() 37 | rclpy.shutdown() 38 | 39 | if __name__ == '__main__': 40 | main() 41 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/ReadItem.grpc.pb.cc: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: ReadItem.proto 4 | 5 | #include "ReadItem.pb.h" 6 | #include "ReadItem.grpc.pb.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | namespace Arp { 23 | namespace Plc { 24 | namespace Gds { 25 | namespace Services { 26 | namespace Grpc { 27 | 28 | } // namespace Arp 29 | } // namespace Plc 30 | } // namespace Gds 31 | } // namespace Services 32 | } // namespace Grpc 33 | 34 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/ForceItem.grpc.pb.cc: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: ForceItem.proto 4 | 5 | #include "ForceItem.pb.h" 6 | #include "ForceItem.grpc.pb.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | namespace Arp { 23 | namespace Plc { 24 | namespace Gds { 25 | namespace Services { 26 | namespace Grpc { 27 | 28 | } // namespace Arp 29 | } // namespace Plc 30 | } // namespace Gds 31 | } // namespace Services 32 | } // namespace Grpc 33 | 34 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/WriteItem.grpc.pb.cc: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: WriteItem.proto 4 | 5 | #include "WriteItem.pb.h" 6 | #include "WriteItem.grpc.pb.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | namespace Arp { 23 | namespace Plc { 24 | namespace Gds { 25 | namespace Services { 26 | namespace Grpc { 27 | 28 | } // namespace Arp 29 | } // namespace Plc 30 | } // namespace Gds 31 | } // namespace Services 32 | } // namespace Grpc 33 | 34 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/VariableInfo.grpc.pb.cc: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: VariableInfo.proto 4 | 5 | #include "VariableInfo.pb.h" 6 | #include "VariableInfo.grpc.pb.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | namespace Arp { 23 | namespace Plc { 24 | namespace Gds { 25 | namespace Services { 26 | namespace Grpc { 27 | 28 | } // namespace Arp 29 | } // namespace Plc 30 | } // namespace Gds 31 | } // namespace Services 32 | } // namespace Grpc 33 | 34 | -------------------------------------------------------------------------------- /phoenix_bridge/test/scripts/single_get_io.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import time 5 | 6 | from phoenix_interfaces.srv import SingleGetIO 7 | import rclpy 8 | from rclpy.node import Node 9 | 10 | class MinimalClientAsync(Node): 11 | 12 | def __init__(self): 13 | super().__init__('single_get_IO') 14 | self.cli = self.create_client(SingleGetIO, '/single_get_IO') 15 | while not self.cli.wait_for_service(timeout_sec=1.0): 16 | self.get_logger().info('service not available, waiting again...') 17 | self.req = SingleGetIO.Request() 18 | 19 | while True: 20 | try: 21 | self.req.datapath = 'Arp.Plc.Eclr/MainInstance.gRPC_Obj.bool_data' 22 | self.future = self.cli.call_async(self.req) 23 | rclpy.spin_until_future_complete(self, self.future) 24 | response = self.future.result() 25 | print("Get from", self.req.datapath, ", status", response.status, "value", response.value) 26 | time.sleep(1) 27 | except KeyboardInterrupt: 28 | break 29 | 30 | def main(args=None): 31 | rclpy.init(args=args) 32 | minimal_client = MinimalClientAsync() 33 | minimal_client.destroy_node() 34 | rclpy.shutdown() 35 | 36 | if __name__ == '__main__': 37 | main() 38 | -------------------------------------------------------------------------------- /phoenix_bridge/test/scripts/read_analog_io.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import time 5 | 6 | from phoenix_interfaces.srv import AnalogIO 7 | import rclpy 8 | from rclpy.node import Node 9 | 10 | class MinimalClientAsync(Node): 11 | 12 | def __init__(self): 13 | super().__init__('read_analog_IO') 14 | self.cli = self.create_client(AnalogIO, '/read_analog_IO') 15 | while not self.cli.wait_for_service(timeout_sec=1.0): 16 | self.get_logger().info('service not available, waiting again...') 17 | self.req = AnalogIO.Request() 18 | 19 | while True: 20 | try: 21 | self.req.instance_path = 'Arp.Plc.Eclr/MainInstance.gRPC_Obj.double_data' 22 | self.future = self.cli.call_async(self.req) 23 | rclpy.spin_until_future_complete(self, self.future) 24 | response = self.future.result() 25 | print("Got from", self.req.instance_path, "result ", response.status, "value", response.value) 26 | time.sleep(1) 27 | except KeyboardInterrupt: 28 | break 29 | 30 | def main(args=None): 31 | rclpy.init(args=args) 32 | minimal_client = MinimalClientAsync() 33 | minimal_client.destroy_node() 34 | rclpy.shutdown() 35 | 36 | if __name__ == '__main__': 37 | main() 38 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/DataAccessError.grpc.pb.cc: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: DataAccessError.proto 4 | 5 | #include "DataAccessError.pb.h" 6 | #include "DataAccessError.grpc.pb.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | namespace Arp { 23 | namespace Plc { 24 | namespace Gds { 25 | namespace Services { 26 | namespace Grpc { 27 | 28 | } // namespace Arp 29 | } // namespace Plc 30 | } // namespace Gds 31 | } // namespace Services 32 | } // namespace Grpc 33 | 34 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/SubscriptionKind.grpc.pb.cc: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: SubscriptionKind.proto 4 | 5 | #include "SubscriptionKind.pb.h" 6 | #include "SubscriptionKind.grpc.pb.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | namespace Arp { 23 | namespace Plc { 24 | namespace Gds { 25 | namespace Services { 26 | namespace Grpc { 27 | 28 | } // namespace Arp 29 | } // namespace Plc 30 | } // namespace Gds 31 | } // namespace Services 32 | } // namespace Grpc 33 | 34 | -------------------------------------------------------------------------------- /phoenix_bridge/test/scripts/single_set_io.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import time 5 | 6 | from phoenix_interfaces.srv import SingleSetIO 7 | import rclpy 8 | from rclpy.node import Node 9 | 10 | class MinimalClientAsync(Node): 11 | 12 | def __init__(self): 13 | super().__init__('single_set_io') 14 | self.cli = self.create_client(SingleSetIO, '/single_set_IO') 15 | while not self.cli.wait_for_service(timeout_sec=1.0): 16 | self.get_logger().info('service not available, waiting again...') 17 | self.req = SingleSetIO.Request() 18 | self.bool_ = True 19 | 20 | while True: 21 | try: 22 | self.req.datapath = 'Arp.Plc.Eclr/MainInstance.gRPC_Obj.bool_data' 23 | self.req.value = self.bool_ 24 | self.bool_ = not self.bool_ 25 | self.future = self.cli.call_async(self.req) 26 | rclpy.spin_until_future_complete(self, self.future) 27 | response = self.future.result() 28 | print("Set ", self.req.value, " to ", self.req.datapath, " and got result ", response.status) 29 | time.sleep(1) 30 | except KeyboardInterrupt: 31 | break 32 | 33 | def main(args=None): 34 | rclpy.init(args=args) 35 | minimal_client = MinimalClientAsync() 36 | minimal_client.destroy_node() 37 | rclpy.shutdown() 38 | 39 | if __name__ == '__main__': 40 | main() 41 | -------------------------------------------------------------------------------- /phoenix_bridge/test/scripts/write_analog_io.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import time 5 | 6 | from phoenix_interfaces.srv import AnalogIO 7 | import rclpy 8 | from rclpy.node import Node 9 | 10 | class MinimalClientAsync(Node): 11 | 12 | def __init__(self): 13 | super().__init__('write_analog_io') 14 | self.cli = self.create_client(AnalogIO, '/write_analog_IO') 15 | while not self.cli.wait_for_service(timeout_sec=1.0): 16 | self.get_logger().info('service not available, waiting again...') 17 | self.req = AnalogIO.Request() 18 | self.double = 1.0 19 | 20 | while True: 21 | try: 22 | self.req.instance_path = 'Arp.Plc.Eclr/MainInstance.gRPC_Obj.double_data' 23 | self.req.value = self.double 24 | self.double += 1.0 25 | self.future = self.cli.call_async(self.req) 26 | rclpy.spin_until_future_complete(self, self.future) 27 | response = self.future.result() 28 | print("Set ", self.req.value, " to ", self.req.instance_path, " and got result ", response.status) 29 | time.sleep(1) 30 | except KeyboardInterrupt: 31 | break 32 | 33 | def main(args=None): 34 | rclpy.init(args=args) 35 | minimal_client = MinimalClientAsync() 36 | minimal_client.destroy_node() 37 | rclpy.shutdown() 38 | 39 | if __name__ == '__main__': 40 | main() 41 | -------------------------------------------------------------------------------- /phoenix_bridge/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | phoenix_bridge 5 | 0.6.2 6 | ROS2 Phoenix bridge 7 | 8 | todo 9 | Tejas 10 | 11 | Apache 2.0 12 | 13 | ament_cmake 14 | ament_cmake_python 15 | 16 | rclcpp 17 | rclpy 18 | std_msgs 19 | nav_msgs 20 | geometry_msgs 21 | launch 22 | phoenix_interfaces 23 | 24 | ament_cmake_clang_format 25 | ament_cmake_cpplint 26 | ament_cmake_cppcheck 27 | ament_cmake_lint_cmake 28 | ament_cmake_xmllint 29 | ament_cmake_gtest 30 | launch 31 | python3-pytest 32 | 33 | 34 | ament_cmake 35 | 36 | 37 | -------------------------------------------------------------------------------- /phoenix_bridge/test/scripts/batch_get_io.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import time 5 | 6 | from phoenix_interfaces.srv import BatchGetIO 7 | import rclpy 8 | from rclpy.node import Node 9 | 10 | class MinimalClientAsync(Node): 11 | 12 | def __init__(self): 13 | super().__init__('batch_get_io') 14 | self.cli = self.create_client(BatchGetIO, '/batch_get_IO') 15 | while not self.cli.wait_for_service(timeout_sec=1.0): 16 | self.get_logger().info('service not available, waiting again...') 17 | self.req = BatchGetIO.Request() 18 | 19 | while True: 20 | try: 21 | self.req.datapaths.clear() 22 | self.req.datapaths.append("Arp.Plc.Eclr/MainInstance.gRPC_Obj.bool_data") 23 | self.req.datapaths.append("Arp.Plc.Eclr/MainInstance.gRPC_Obj.bool_data2") 24 | self.req.datapaths.append("Arp.Plc.Eclr/MainInstance.gRPC_Obj.bool_data3") 25 | 26 | self.future = self.cli.call_async(self.req) 27 | rclpy.spin_until_future_complete(self, self.future) 28 | response = self.future.result() 29 | print("Payload sent: ") 30 | for element in self.req.datapaths: 31 | print(element) 32 | print("Response status: ", response.status) 33 | print("Values: ") 34 | for element in response.values: 35 | print(element) 36 | time.sleep(1) 37 | except KeyboardInterrupt: 38 | break 39 | 40 | def main(args=None): 41 | rclpy.init(args=args) 42 | minimal_client = MinimalClientAsync() 43 | minimal_client.destroy_node() 44 | rclpy.shutdown() 45 | 46 | if __name__ == '__main__': 47 | main() 48 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/DataType.grpc.pb.h: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: DataType.proto 4 | // Original file comments: 5 | // ///////////////////////////////////////////////////////////////////////////// 6 | // 7 | // Copyright PHOENIX CONTACT Electronics GmbH 8 | // 9 | // ///////////////////////////////////////////////////////////////////////////// 10 | #ifndef GRPC_DataType_2eproto__INCLUDED 11 | #define GRPC_DataType_2eproto__INCLUDED 12 | 13 | #include "DataType.pb.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | namespace Arp { 36 | namespace Plc { 37 | namespace Grpc { 38 | 39 | } // namespace Grpc 40 | } // namespace Plc 41 | } // namespace Arp 42 | 43 | 44 | #endif // GRPC_DataType_2eproto__INCLUDED 45 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/ArpTypes.grpc.pb.h: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: ArpTypes.proto 4 | // Original file comments: 5 | // ///////////////////////////////////////////////////////////////////////////// 6 | // 7 | // Copyright PHOENIX CONTACT Electronics GmbH 8 | // 9 | // ///////////////////////////////////////////////////////////////////////////// 10 | // 11 | #ifndef GRPC_ArpTypes_2eproto__INCLUDED 12 | #define GRPC_ArpTypes_2eproto__INCLUDED 13 | 14 | #include "ArpTypes.pb.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | namespace Arp { 37 | namespace Type { 38 | namespace Grpc { 39 | 40 | } // namespace Grpc 41 | } // namespace Type 42 | } // namespace Arp 43 | 44 | 45 | #endif // GRPC_ArpTypes_2eproto__INCLUDED 46 | -------------------------------------------------------------------------------- /dockerfile_manually: -------------------------------------------------------------------------------- 1 | ARG ROS_DISTRO 2 | 3 | FROM public.ecr.aws/docker/library/ros:${ROS_DISTRO}-ros-core as base 4 | FROM ghcr.io/ipa-rwu/builder:latest as builder 5 | 6 | 7 | FROM base as deps 8 | COPY . /root/ws/src 9 | RUN cd /root/ws/src && \ 10 | rm /root/ws/src/dep.repo 11 | 12 | COPY --from=builder workspace.bash /builder/workspace.bash 13 | RUN apt-get update -qq && \ 14 | apt-get install -y git && \ 15 | cd /root/ws/src && \ 16 | git clone https://github.com/PLCnext/gRPC-client-deps.git -b ${ROS_DISTRO}/devel 17 | RUN cd /root/ws/src/gRPC-client-deps/plcnext_deps/ && \ 18 | /root/ws/src/gRPC-client-deps/plcnext_deps/dep_copy.sh ${ROS_DISTRO} && \ 19 | rm -rf /var/lib/apt/lists/* 20 | 21 | FROM deps as pre_build 22 | RUN apt-get update -qq && \ 23 | /builder/workspace.bash update_list /root/ws && \ 24 | rm -rf /var/lib/apt/lists/* 25 | 26 | FROM pre_build as build 27 | ARG CMAKE_ARGS= 28 | ENV CMAKE_ARGS -DCMAKE_BUILD_TYPE=RELEASE 29 | RUN apt-get update -qq && \ 30 | /builder/workspace.bash build_workspace /root/ws && \ 31 | rm -rf /var/lib/apt/lists/* 32 | 33 | FROM build as test 34 | RUN apt-get update -qq && \ 35 | /builder/workspace.bash test_workspace /root/ws && \ 36 | rm -rf /var/lib/apt/lists/* 37 | 38 | FROM test as install 39 | RUN apt-get update -qq && \ 40 | /builder/workspace.bash install_workspace /root/ws && \ 41 | rm -rf /var/lib/apt/lists/* 42 | 43 | FROM base as deploy 44 | COPY --from=install /root/ws/DEPENDS /root/ws/DEPENDS 45 | COPY --from=builder workspace.bash /builder/workspace.bash 46 | RUN apt-get update -qq && \ 47 | /builder/workspace.bash install_depends /root/ws && \ 48 | rm -rf /var/lib/apt/lists/* 49 | COPY --from=install /opt/ros/${ROS_DISTRO} /opt/ros/${ROS_DISTRO} -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/ReadItem.grpc.pb.h: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: ReadItem.proto 4 | // Original file comments: 5 | // ///////////////////////////////////////////////////////////////////////////// 6 | // 7 | // Copyright PHOENIX CONTACT Electronics GmbH 8 | // 9 | // ///////////////////////////////////////////////////////////////////////////// 10 | // 11 | #ifndef GRPC_ReadItem_2eproto__INCLUDED 12 | #define GRPC_ReadItem_2eproto__INCLUDED 13 | 14 | #include "ReadItem.pb.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | namespace Arp { 37 | namespace Plc { 38 | namespace Gds { 39 | namespace Services { 40 | namespace Grpc { 41 | 42 | } // namespace Grpc 43 | } // namespace Services 44 | } // namespace Gds 45 | } // namespace Plc 46 | } // namespace Arp 47 | 48 | 49 | #endif // GRPC_ReadItem_2eproto__INCLUDED 50 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/ForceItem.grpc.pb.h: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: ForceItem.proto 4 | // Original file comments: 5 | // ///////////////////////////////////////////////////////////////////////////// 6 | // 7 | // Copyright PHOENIX CONTACT Electronics GmbH 8 | // 9 | // ///////////////////////////////////////////////////////////////////////////// 10 | // 11 | #ifndef GRPC_ForceItem_2eproto__INCLUDED 12 | #define GRPC_ForceItem_2eproto__INCLUDED 13 | 14 | #include "ForceItem.pb.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | namespace Arp { 37 | namespace Plc { 38 | namespace Gds { 39 | namespace Services { 40 | namespace Grpc { 41 | 42 | } // namespace Grpc 43 | } // namespace Services 44 | } // namespace Gds 45 | } // namespace Plc 46 | } // namespace Arp 47 | 48 | 49 | #endif // GRPC_ForceItem_2eproto__INCLUDED 50 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/WriteItem.grpc.pb.h: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: WriteItem.proto 4 | // Original file comments: 5 | // ///////////////////////////////////////////////////////////////////////////// 6 | // 7 | // Copyright PHOENIX CONTACT Electronics GmbH 8 | // 9 | // ///////////////////////////////////////////////////////////////////////////// 10 | // 11 | #ifndef GRPC_WriteItem_2eproto__INCLUDED 12 | #define GRPC_WriteItem_2eproto__INCLUDED 13 | 14 | #include "WriteItem.pb.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | namespace Arp { 37 | namespace Plc { 38 | namespace Gds { 39 | namespace Services { 40 | namespace Grpc { 41 | 42 | } // namespace Grpc 43 | } // namespace Services 44 | } // namespace Gds 45 | } // namespace Plc 46 | } // namespace Arp 47 | 48 | 49 | #endif // GRPC_WriteItem_2eproto__INCLUDED 50 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/VariableInfo.grpc.pb.h: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: VariableInfo.proto 4 | // Original file comments: 5 | // ///////////////////////////////////////////////////////////////////////////// 6 | // 7 | // Copyright PHOENIX CONTACT Electronics GmbH 8 | // 9 | // ///////////////////////////////////////////////////////////////////////////// 10 | // 11 | #ifndef GRPC_VariableInfo_2eproto__INCLUDED 12 | #define GRPC_VariableInfo_2eproto__INCLUDED 13 | 14 | #include "VariableInfo.pb.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | namespace Arp { 37 | namespace Plc { 38 | namespace Gds { 39 | namespace Services { 40 | namespace Grpc { 41 | 42 | } // namespace Grpc 43 | } // namespace Services 44 | } // namespace Gds 45 | } // namespace Plc 46 | } // namespace Arp 47 | 48 | 49 | #endif // GRPC_VariableInfo_2eproto__INCLUDED 50 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/DataAccessError.grpc.pb.h: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: DataAccessError.proto 4 | // Original file comments: 5 | // ///////////////////////////////////////////////////////////////////////////// 6 | // 7 | // Copyright PHOENIX CONTACT Electronics GmbH 8 | // 9 | // ///////////////////////////////////////////////////////////////////////////// 10 | #ifndef GRPC_DataAccessError_2eproto__INCLUDED 11 | #define GRPC_DataAccessError_2eproto__INCLUDED 12 | 13 | #include "DataAccessError.pb.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | namespace Arp { 36 | namespace Plc { 37 | namespace Gds { 38 | namespace Services { 39 | namespace Grpc { 40 | 41 | } // namespace Grpc 42 | } // namespace Services 43 | } // namespace Gds 44 | } // namespace Plc 45 | } // namespace Arp 46 | 47 | 48 | #endif // GRPC_DataAccessError_2eproto__INCLUDED 49 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/SubscriptionKind.grpc.pb.h: -------------------------------------------------------------------------------- 1 | // Generated by the gRPC C++ plugin. 2 | // If you make any local change, they will be lost. 3 | // source: SubscriptionKind.proto 4 | // Original file comments: 5 | // ///////////////////////////////////////////////////////////////////////////// 6 | // 7 | // Copyright PHOENIX CONTACT Electronics GmbH 8 | // 9 | // ///////////////////////////////////////////////////////////////////////////// 10 | #ifndef GRPC_SubscriptionKind_2eproto__INCLUDED 11 | #define GRPC_SubscriptionKind_2eproto__INCLUDED 12 | 13 | #include "SubscriptionKind.pb.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | namespace Arp { 36 | namespace Plc { 37 | namespace Gds { 38 | namespace Services { 39 | namespace Grpc { 40 | 41 | } // namespace Grpc 42 | } // namespace Services 43 | } // namespace Gds 44 | } // namespace Plc 45 | } // namespace Arp 46 | 47 | 48 | #endif // GRPC_SubscriptionKind_2eproto__INCLUDED 49 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/include_types.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 Fraunhofer IPA 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #ifndef INCLUDE_TYPES_H 18 | #define INCLUDE_TYPES_H 19 | 20 | #include 21 | 22 | /** 23 | * @brief This header contains the common types that are to be used throughout the bridge. 24 | * Specifically the ros message headers, which are generated based on the parameters file 25 | */ 26 | 27 | /// 28 | /// The following section is generated by cog at build time. Manual edits will be lost when rebuilt. 29 | /// 30 | 31 | /*[[[cog 32 | import cog 33 | import sys 34 | import os 35 | 36 | sys.path.append(os.getcwd()) # Necessary when colcon build invokes this script 37 | 38 | from phoenix_bridge.param_parser import ParamParser, getSnakeCase 39 | 40 | parser = ParamParser() 41 | for node in parser.nodes_: 42 | cog.outl("#include<{}.hpp>".format(getSnakeCase(node.msg_type))) 43 | ]]]*/ 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | //[[[end]]] 51 | 52 | /// 53 | /// The above section is generated by cog at build time. Manual edits will be lost when rebuilt. 54 | /// 55 | 56 | #endif // INCLUDE_TYPES_H 57 | -------------------------------------------------------------------------------- /phoenix_bridge/test/scripts/batch_set_io.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import time 5 | 6 | from phoenix_interfaces.srv import BatchSetIO 7 | from phoenix_interfaces.msg import SetIO 8 | import rclpy 9 | from rclpy.node import Node 10 | 11 | class MinimalClientAsync(Node): 12 | 13 | def __init__(self): 14 | super().__init__('batch_set_IO') 15 | self.cli = self.create_client(BatchSetIO, '/batch_set_IO') 16 | while not self.cli.wait_for_service(timeout_sec=1.0): 17 | self.get_logger().info('service not available, waiting again...') 18 | self.req = BatchSetIO.Request() 19 | self.bool_ = True 20 | 21 | while True: 22 | try: 23 | self.req.payload.clear() 24 | 25 | msg = SetIO() 26 | msg.datapath = "Arp.Plc.Eclr/MainInstance.gRPC_Obj.bool_data" 27 | msg.value = self.bool_ 28 | self.req.payload.append(msg) 29 | 30 | msg2 = SetIO() 31 | msg2.datapath = "Arp.Plc.Eclr/MainInstance.gRPC_Obj.bool_data2" 32 | msg2.value = not self.bool_ 33 | self.req.payload.append(msg2) 34 | 35 | msg3 = SetIO() 36 | msg3.datapath = "Arp.Plc.Eclr/MainInstance.gRPC_Obj.bool_data3" 37 | msg3.value = self.bool_ 38 | self.req.payload.append(msg3) 39 | 40 | self.bool_ = not self.bool_ 41 | self.future = self.cli.call_async(self.req) 42 | rclpy.spin_until_future_complete(self, self.future) 43 | response = self.future.result() 44 | print("Payload sent: ") 45 | for element in self.req.payload: 46 | print(element.datapath, element.value) 47 | print("Response status: ", response.status) 48 | time.sleep(1) 49 | except KeyboardInterrupt: 50 | break 51 | 52 | def main(args=None): 53 | rclpy.init(args=args) 54 | minimal_client = MinimalClientAsync() 55 | minimal_client.destroy_node() 56 | rclpy.shutdown() 57 | 58 | if __name__ == '__main__': 59 | main() 60 | -------------------------------------------------------------------------------- /phoenix_bridge/test/scripts/pub_odom.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import rclpy 4 | 5 | from random import randint 6 | from rclpy.node import Node 7 | from nav_msgs.msg import Odometry 8 | 9 | 10 | class MinimalPublisher(Node): 11 | 12 | def __init__(self): 13 | super().__init__('odom_publisher') 14 | self.publisher_ = self.create_publisher(Odometry, '/sub_odom_1', 10) 15 | timer_period = 1 # seconds 16 | self.timer = self.create_timer(timer_period, self.timer_callback) 17 | 18 | def timer_callback(self): 19 | msg = Odometry() 20 | msg.header.stamp = self.get_clock().now().to_msg() 21 | msg.header.frame_id = "test_publisher" 22 | msg.child_frame_id = "plc" 23 | msg.pose.pose.position.x = randint(1, 1000)/100 24 | msg.pose.pose.position.y = randint(1, 1000)/100 25 | msg.pose.pose.position.z = randint(1, 1000)/100 26 | msg.pose.pose.orientation.x = randint(1, 100)/100 27 | msg.pose.pose.orientation.y = randint(1, 100)/100 28 | msg.pose.pose.orientation.z = randint(1, 100)/100 29 | msg.pose.pose.orientation.w = randint(1, 100)/100 30 | msg.pose.covariance = [x/10 for x in range(0,36)] 31 | msg.twist.twist.linear.x = randint(1, 500)/100 32 | msg.twist.twist.linear.y = randint(1, 500)/100 33 | msg.twist.twist.linear.z = randint(1, 500)/100 34 | msg.twist.twist.angular.x = randint(1, 314)/100 35 | msg.twist.twist.angular.y = randint(1, 314)/100 36 | msg.twist.twist.angular.z = randint(1, 314)/100 37 | msg.twist.covariance = [x/10 for x in range(0,36)] 38 | 39 | self.publisher_.publish(msg) 40 | 41 | def main(args=None): 42 | rclpy.init(args=args) 43 | 44 | minimal_publisher = MinimalPublisher() 45 | 46 | rclpy.spin(minimal_publisher) 47 | 48 | minimal_publisher.destroy_node() 49 | rclpy.shutdown() 50 | 51 | if __name__ == '__main__': 52 | main() 53 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:experimental 2 | ARG BUILDER_SUFFIX= 3 | ARG BUILDER_PREFIX= 4 | ARG ROS_DISTRO= 5 | FROM public.ecr.aws/docker/library/ros:${ROS_DISTRO}-ros-core as base 6 | FROM ${BUILDER_PREFIX}builder${BUILDER_SUFFIX} as builder 7 | 8 | 9 | FROM base as deps 10 | COPY . /root/ws/src/ 11 | COPY --from=builder workspace.bash /builder/workspace.bash 12 | ARG ROSINSTALL_CI_JOB_TOKEN= 13 | ENV ROSINSTALL_CI_JOB_TOKEN $ROSINSTALL_CI_JOB_TOKEN 14 | ARG CI_JOB_TOKEN= 15 | ENV CI_JOB_TOKEN $CI_JOB_TOKEN 16 | ARG DEP_REPO_NAME= 17 | ENV DEP_REPO_NAME $DEP_REPO_NAME 18 | ARG DEP_REPO_URL= 19 | ENV DEP_REPO_URL $DEP_REPO_URL 20 | RUN apt-get update -qq && \ 21 | /builder/workspace.bash install_from_rosinstall /root/ws/src/dep.repo /root/ws/src/ 22 | RUN cd /root/ws/src/${DEP_REPO_NAME}/plcnext_deps/ && \ 23 | /root/ws/src/${DEP_REPO_NAME}/plcnext_deps/dep_copy.sh ${ROS_DISTRO} && \ 24 | rm -rf /var/lib/apt/lists/* 25 | 26 | FROM deps as pre_build 27 | COPY . /root/ws/src/ 28 | RUN apt-get update -qq && \ 29 | /builder/workspace.bash update_list /root/ws && \ 30 | rm -rf /var/lib/apt/lists/* 31 | 32 | FROM pre_build as build 33 | ARG CMAKE_ARGS= 34 | ENV CMAKE_ARGS $CMAKE_ARGS 35 | RUN apt-get update -qq && \ 36 | /builder/workspace.bash build_workspace /root/ws && \ 37 | rm -rf /var/lib/apt/lists/* 38 | 39 | FROM build as test 40 | RUN apt-get update -qq && \ 41 | /builder/workspace.bash test_workspace /root/ws && \ 42 | rm -rf /var/lib/apt/lists/* 43 | 44 | FROM test as install 45 | RUN apt-get update -qq && \ 46 | /builder/workspace.bash install_workspace /root/ws && \ 47 | rm -rf /var/lib/apt/lists/* 48 | 49 | FROM base as deploy 50 | COPY --from=install /root/ws/DEPENDS /root/ws/DEPENDS 51 | COPY --from=builder workspace.bash /builder/workspace.bash 52 | RUN apt-get update -qq && \ 53 | /builder/workspace.bash install_depends /root/ws && \ 54 | rm -rf /var/lib/apt/lists/* 55 | COPY --from=install /opt/ros/$ROS_DISTRO /opt/ros/$ROS_DISTRO -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:experimental 2 | ARG BUILDER_SUFFIX= 3 | ARG BUILDER_PREFIX= 4 | ARG ROS_DISTRO= 5 | FROM public.ecr.aws/docker/library/ros:${ROS_DISTRO}-ros-core as base 6 | FROM ${BUILDER_PREFIX}builder${BUILDER_SUFFIX} as builder 7 | 8 | 9 | FROM base as deps 10 | COPY . /root/ws/src/ 11 | COPY --from=builder workspace.bash /builder/workspace.bash 12 | ARG ROSINSTALL_CI_JOB_TOKEN= 13 | ENV ROSINSTALL_CI_JOB_TOKEN $ROSINSTALL_CI_JOB_TOKEN 14 | ARG CI_JOB_TOKEN= 15 | ENV CI_JOB_TOKEN $CI_JOB_TOKEN 16 | ARG DEP_REPO_NAME= 17 | ENV DEP_REPO_NAME $DEP_REPO_NAME 18 | ARG DEP_REPO_URL= 19 | ENV DEP_REPO_URL $DEP_REPO_URL 20 | RUN apt-get update -qq && \ 21 | /builder/workspace.bash install_from_rosinstall /root/ws/src/dep.repo /root/ws/src/ 22 | RUN cd /root/ws/src/${DEP_REPO_NAME}/plcnext_deps/ && \ 23 | /root/ws/src/${DEP_REPO_NAME}/plcnext_deps/dep_copy.sh ${ROS_DISTRO} && \ 24 | rm -rf /var/lib/apt/lists/* 25 | 26 | FROM deps as pre_build 27 | COPY . /root/ws/src/ 28 | RUN apt-get update -qq && \ 29 | /builder/workspace.bash update_list /root/ws && \ 30 | rm -rf /var/lib/apt/lists/* 31 | 32 | FROM pre_build as build 33 | ARG CMAKE_ARGS= 34 | ENV CMAKE_ARGS $CMAKE_ARGS 35 | RUN apt-get update -qq && \ 36 | /builder/workspace.bash build_workspace /root/ws && \ 37 | rm -rf /var/lib/apt/lists/* 38 | 39 | FROM build as test 40 | RUN apt-get update -qq && \ 41 | /builder/workspace.bash test_workspace /root/ws && \ 42 | rm -rf /var/lib/apt/lists/* 43 | 44 | FROM test as install 45 | RUN apt-get update -qq && \ 46 | /builder/workspace.bash install_workspace /root/ws && \ 47 | rm -rf /var/lib/apt/lists/* 48 | 49 | FROM base as deploy 50 | COPY --from=install /root/ws/DEPENDS /root/ws/DEPENDS 51 | COPY --from=builder workspace.bash /builder/workspace.bash 52 | RUN apt-get update -qq && \ 53 | /builder/workspace.bash install_depends /root/ws && \ 54 | rm -rf /var/lib/apt/lists/* 55 | COPY --from=install /opt/ros/$ROS_DISTRO /opt/ros/$ROS_DISTRO -------------------------------------------------------------------------------- /phoenix_bridge/launch/launch_phoenix_bridge.py: -------------------------------------------------------------------------------- 1 | import os 2 | from ament_index_python.packages import get_package_share_directory 3 | from launch import LaunchDescription 4 | from launch_ros.actions import Node 5 | 6 | def generate_launch_description(): 7 | ld = LaunchDescription() 8 | 9 | # Configuration file for the bridge 10 | # @todo: Replace this with an argument that the user can provide while launching 11 | config_bridge = os.path.join( 12 | get_package_share_directory('phoenix_bridge'), 13 | 'config', 14 | 'interface_description.yaml' 15 | ) 16 | 17 | # Configuration file for the io services 18 | config_services = os.path.join( 19 | get_package_share_directory('phoenix_bridge'), 20 | 'config', 21 | 'services_params.yaml' 22 | ) 23 | 24 | # Configuration file for the heartbeat detector 25 | liveliness_config = os.path.join( 26 | get_package_share_directory('phoenix_bridge'), 27 | 'config', 28 | 'liveliness_config.yaml' 29 | ) 30 | 31 | # Main bridge node 32 | node_bridge=Node( 33 | package = 'phoenix_bridge', 34 | # Do not specify node name, as we spawn multiple nodes from this executable 35 | executable = 'phoenix_bridge_node', 36 | output='screen', 37 | parameters = [config_bridge] 38 | ) 39 | 40 | # IO Services node 41 | node_services=Node( 42 | package = 'phoenix_bridge', 43 | # Do not specify node name, as we spawn multiple nodes from this executable 44 | executable = 'phoenix_io_services_node', 45 | output='screen', 46 | parameters = [config_services] 47 | ) 48 | 49 | # Heartbeat node 50 | node_heartbeat=Node( 51 | package = 'phoenix_bridge', 52 | name = 'liveliness_check', 53 | executable = 'liveliness_check', 54 | output='screen', 55 | parameters = [liveliness_config] 56 | ) 57 | 58 | ld.add_action(node_bridge) 59 | ld.add_action(node_services) 60 | ld.add_action(node_heartbeat) 61 | return ld 62 | -------------------------------------------------------------------------------- /phoenix_bridge/src/phoenix_bridge_node.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 Fraunhofer IPA 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #include 18 | #include 19 | 20 | #include "phoenix_bridge/bridge_type.hpp" 21 | 22 | int main(int argc, char ** argv) 23 | { 24 | rclcpp::init(argc, argv); 25 | rclcpp::executors::MultiThreadedExecutor mte; 26 | 27 | /*[[[cog 28 | import cog 29 | import sys 30 | import os 31 | 32 | sys.path.append(os.getcwd()) # Necessary when colcon build invokes this script 33 | 34 | from phoenix_bridge.param_parser import ParamParser 35 | 36 | parser = ParamParser() 37 | for node in parser.nodes_: 38 | cog.outl("auto {} = std::make_shared>(\"{}\");" 39 | .format(node.node_name, node.msg_type.replace("/","::"), node.node_name)) 40 | cog.outl("mte.add_node({});".format(node.node_name)) 41 | cog.outl() 42 | ]]]*/ 43 | auto odom_bridge = std::make_shared>("odom_bridge"); 44 | mte.add_node(odom_bridge); 45 | 46 | auto twist_bridge = std::make_shared>("twist_bridge"); 47 | mte.add_node(twist_bridge); 48 | 49 | auto string_bridge = std::make_shared>("string_bridge"); 50 | mte.add_node(string_bridge); 51 | 52 | auto double_bridge = std::make_shared>("double_bridge"); 53 | mte.add_node(double_bridge); 54 | 55 | auto int_bridge = std::make_shared>("int_bridge"); 56 | mte.add_node(int_bridge); 57 | 58 | auto header_bridge = std::make_shared>("header_bridge"); 59 | mte.add_node(header_bridge); 60 | 61 | // [[[end]]] 62 | 63 | std::cout << "Node starting" << std::endl; 64 | mte.spin(); 65 | rclcpp::shutdown(); 66 | return 0; 67 | } 68 | -------------------------------------------------------------------------------- /phoenix_bridge/test/launch_test_bridge.test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import unittest 4 | 5 | import ament_index_python 6 | 7 | import launch 8 | import launch.actions 9 | 10 | import launch_testing 11 | import launch_testing.actions 12 | from launch_testing.asserts import assertSequentialStdout 13 | from launch_ros.actions import Node 14 | 15 | import pytest 16 | 17 | import rclpy 18 | import yaml 19 | 20 | config_bridge = os.path.join( 21 | ament_index_python.get_package_prefix('phoenix_bridge'), 22 | 'share/phoenix_bridge/config', 23 | 'interface_description.yaml' 24 | ) 25 | 26 | node_bridge=Node( 27 | package = 'phoenix_bridge', 28 | # Do not specify node name, as we spawn multiple nodes from this executable 29 | executable = 'phoenix_bridge_node', 30 | output='screen', 31 | parameters = [config_bridge] 32 | ) 33 | 34 | @pytest.mark.launch_test 35 | def generate_test_description(): 36 | return launch.LaunchDescription([ 37 | node_bridge, 38 | launch_testing.actions.ReadyToTest(), 39 | ]), {'node_bridge': node_bridge} 40 | 41 | # These tests will run concurrently with the node_bridge. After all these tests are done, 42 | # the launch system will shut down the processes that it started up 43 | class TestGoodProcess(unittest.TestCase): 44 | 45 | @classmethod 46 | def setUpClass(self): 47 | rclpy.init() 48 | self.node = rclpy.create_node("test_node") 49 | 50 | @classmethod 51 | def tearDownClass(self): 52 | rclpy.shutdown() 53 | 54 | def test_node_started(self, proc_output): 55 | """ 56 | Check if node is started within 10 seconds 57 | """ 58 | proc_output.assertWaitFor('Node starting', timeout=10, stream='stdout', process=node_bridge) 59 | 60 | 61 | def test_topics(self, proc_output): 62 | """ 63 | Check if all the topics defined in the param file have been created 64 | """ 65 | proc_output.assertWaitFor('Node starting', timeout=10, stream='stdout', process=node_bridge) 66 | topics_and_types = self.node.get_topic_names_and_types() 67 | time.sleep(1.0) # Wait for the bridge to spawn all topics. Prevents sporadic test failure. 68 | 69 | current_topics = [] 70 | for topic in topics_and_types: 71 | current_topics.append(topic[0]) 72 | 73 | with open(config_bridge) as yamlfile: 74 | params_ = yaml.load(yamlfile, Loader = yaml.FullLoader) 75 | 76 | param_topics = [] 77 | for node in params_: 78 | if "publishers" in params_[node]['ros__parameters']: 79 | param_topics += params_[node]['ros__parameters']['publishers']['topics'] 80 | if "subscribers" in params_[node]['ros__parameters']: 81 | param_topics += params_[node]['ros__parameters']['subscribers']['topics'] 82 | 83 | for topic in param_topics: 84 | topic = "/"+topic 85 | assert topic in current_topics 86 | -------------------------------------------------------------------------------- /phoenix_bridge/src/liveliness_check.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 Fraunhofer IPA 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include "phoenix_bridge/phoenix_comm.hpp" 22 | 23 | class LivelinessCheck : public rclcpp::Node 24 | { 25 | public: 26 | explicit LivelinessCheck(rclcpp::NodeOptions options); 27 | 28 | private: 29 | PhoenixComm comm_; 30 | std::string grpc_address_; 31 | std::string liveliness_bool_; 32 | double liveliness_timeout_; 33 | int poll_rate_; 34 | bool beat_; 35 | bool previous_beat_; 36 | rclcpp::Publisher::SharedPtr pub_status_; 37 | rclcpp::TimerBase::SharedPtr timer_; 38 | 39 | void checkLiveliness(); 40 | }; 41 | 42 | using namespace std::chrono_literals; 43 | LivelinessCheck::LivelinessCheck(rclcpp::NodeOptions options) 44 | : rclcpp::Node("liveliness_check", options) 45 | { 46 | this->declare_parameter("grpc.address", "unix:/run/plcnext/grpc.sock"); 47 | this->declare_parameter("liveliness_bool", "Arp.Plc.Eclr/xLiveliness"); 48 | this->declare_parameter("liveliness_timeout_s", 10.0); 49 | this->declare_parameter("poll_rate_hz", 10); 50 | 51 | grpc_address_ = this->get_parameter("grpc.address").as_string(); 52 | liveliness_bool_ = this->get_parameter("liveliness_bool").as_string(); 53 | liveliness_timeout_ = this->get_parameter("liveliness_timeout_s").as_double(); 54 | poll_rate_ = this->get_parameter("poll_rate_hz").as_int(); 55 | 56 | beat_ = false; 57 | previous_beat_ = false; 58 | comm_.init(grpc_address_); 59 | pub_status_ = this->create_publisher("liveliness_status", 1000); 60 | 61 | timer_ = this->create_wall_timer( 62 | 1000ms / poll_rate_, std::bind(&LivelinessCheck::checkLiveliness, this)); 63 | 64 | RCLCPP_INFO_STREAM( 65 | this->get_logger(), "Init @" << grpc_address_ << " for " << liveliness_bool_ << " with " 66 | << liveliness_timeout_ << "s timout & " << poll_rate_ << " Hz."); 67 | } 68 | 69 | void LivelinessCheck::checkLiveliness() 70 | { 71 | // Set the bool to true from ROS 72 | if (!comm_.sendToPLC(liveliness_bool_, true)) { 73 | RCLCPP_WARN_STREAM_ONCE(this->get_logger(), ": Ping to PLC failed!"); 74 | } 75 | 76 | std::this_thread::sleep_for(1s * liveliness_timeout_); 77 | 78 | bool response = true; 79 | if (!comm_.getFromPLC(liveliness_bool_, response)) { 80 | RCLCPP_WARN_STREAM_ONCE(this->get_logger(), ": Ping from PLC failed!"); 81 | } 82 | 83 | // Expect response to be set false by PLC 84 | if (response) { 85 | RCLCPP_WARN_STREAM(this->get_logger(), ": Failed liveliness check!!"); 86 | } 87 | std_msgs::msg::Bool msg; 88 | msg.data = !response; 89 | pub_status_->publish(msg); 90 | } 91 | 92 | int main(int argc, char ** argv) 93 | { 94 | rclcpp::init(argc, argv); 95 | rclcpp::executors::MultiThreadedExecutor mte; 96 | rclcpp::NodeOptions options; 97 | auto node = std::make_shared(options); 98 | mte.add_node(node); 99 | mte.spin(); 100 | rclcpp::shutdown(); 101 | return 0; 102 | } 103 | -------------------------------------------------------------------------------- /phoenix_bridge/config/interface_description.yaml: -------------------------------------------------------------------------------- 1 | # Organise the description file per bridging type 2 | # Each type can have several pubs and subs 3 | # The values of a given index of the 3 arrays - topics, datapaths and frequencies - are together considered when creating one topic 4 | # Ex: publishers.topics[1], publishers.datapaths[1] & publishers.frequencies[1] are together considered to spawn pub[1] 5 | 6 | odom_bridge: 7 | ros__parameters: 8 | grpc: 9 | address: "unix:/run/plcnext/grpc.sock" 10 | msg_type: "nav_msgs/msg/Odometry" # Include header filename and Scope resolution is derived from this as well. 11 | #publishers: # The next 3 lists must have the same size. Elements of the same index from each list characterise one port. 12 | # topics: [pub_odom] 13 | # datapaths: [Arp.Plc.Eclr/MainInstance.gRPC_Obj.odom_data] 14 | # frequencies: [40] 15 | 16 | twist_bridge: 17 | ros__parameters: 18 | grpc: 19 | address: "unix:/run/plcnext/grpc.sock" 20 | msg_type: "geometry_msgs/msg/Twist" 21 | # subscribers: 22 | # topics: [sub_twist] 23 | # datapaths: [Arp.Plc.Eclr/MainInstance.gRPC_Obj.twist_data] 24 | # frequencies: [100] 25 | 26 | string_bridge: 27 | ros__parameters: 28 | grpc: 29 | address: "unix:/run/plcnext/grpc.sock" 30 | msg_type: "std_msgs/msg/String" 31 | # Uncomment and populate with values as needed 32 | # publishers: 33 | # topics: [] 34 | # datapaths: [] 35 | # frequencies: [] 36 | # subscribers: 37 | # topics: [] 38 | # datapaths: [] 39 | # frequencies: [] 40 | 41 | double_bridge: 42 | ros__parameters: 43 | grpc: 44 | address: "unix:/run/plcnext/grpc.sock" 45 | msg_type: "std_msgs/msg/Float64" 46 | # Uncomment and populate with values as needed 47 | # publishers: 48 | # topics: [] 49 | # datapaths: [] 50 | # frequencies: [] 51 | # subscribers: 52 | # topics: [] 53 | # datapaths: [] 54 | # frequencies: [] 55 | 56 | int_bridge: 57 | ros__parameters: 58 | grpc: 59 | address: "unix:/run/plcnext/grpc.sock" 60 | msg_type: "std_msgs/msg/Int64" 61 | # Uncomment and populate with values as needed 62 | # publishers: 63 | # topics: [] 64 | # datapaths: [] 65 | # frequencies: [] 66 | # subscribers: 67 | # topics: [] 68 | # datapaths: [] 69 | # frequencies: [] 70 | 71 | header_bridge: 72 | ros__parameters: 73 | grpc: 74 | address: "unix:/run/plcnext/grpc.sock" 75 | msg_type: "std_msgs/msg/Header" 76 | # Uncomment and populate with values as needed 77 | # publishers: 78 | # topics: [] 79 | # datapaths: [] 80 | # frequencies: [] 81 | # subscribers: 82 | # topics: [] 83 | # datapaths: [] 84 | # frequencies: [] 85 | 86 | bool_bridge: 87 | ros__parameters: 88 | grpc: 89 | address: "unix:/run/plcnext/grpc.sock" 90 | msg_type: "std_msgs/msg/Bool" 91 | publishers: # The next 3 lists must have the same size. Elements of the same index from each list characterise one port. 92 | topics: [pub_stVar] 93 | datapaths: [Arp.Plc.Eclr/stVar] 94 | frequencies: [40] 95 | subscribers: # The next 3 lists must have the same size. Elements of the same index from each list characterise one port. 96 | topics: [sub_stVar] 97 | datapaths: [Arp.Plc.Eclr/stVar] 98 | frequencies: [40] 99 | 100 | num_bridge: 101 | ros__parameters: 102 | grpc: 103 | address: "unix:/run/plcnext/grpc.sock" 104 | msg_type: "std_msgs/msg/UInt8" 105 | publishers: # The next 3 lists must have the same size. Elements of the same index from each list characterise one port. 106 | topics: [pub_stNumOutput] 107 | datapaths: [Arp.Plc.Eclr/stNumOutput] 108 | frequencies: [40] 109 | subscribers: # The next 3 lists must have the same size. Elements of the same index from each list characterise one port. 110 | topics: [sub_stNumInput] 111 | datapaths: [Arp.Plc.Eclr/main.stNumInput] 112 | frequencies: [40] 113 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/SubscriptionKind.pb.cc: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: SubscriptionKind.proto 3 | 4 | #include "SubscriptionKind.pb.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | // @@protoc_insertion_point(includes) 16 | #include 17 | namespace Arp { 18 | namespace Plc { 19 | namespace Gds { 20 | namespace Services { 21 | namespace Grpc { 22 | } // namespace Grpc 23 | } // namespace Services 24 | } // namespace Gds 25 | } // namespace Plc 26 | } // namespace Arp 27 | static constexpr ::PROTOBUF_NAMESPACE_ID::Metadata* file_level_metadata_SubscriptionKind_2eproto = nullptr; 28 | static const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* file_level_enum_descriptors_SubscriptionKind_2eproto[1]; 29 | static constexpr ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor const** file_level_service_descriptors_SubscriptionKind_2eproto = nullptr; 30 | const ::PROTOBUF_NAMESPACE_ID::uint32 TableStruct_SubscriptionKind_2eproto::offsets[1] = {}; 31 | static constexpr ::PROTOBUF_NAMESPACE_ID::internal::MigrationSchema* schemas = nullptr; 32 | static constexpr ::PROTOBUF_NAMESPACE_ID::Message* const* file_default_instances = nullptr; 33 | 34 | const char descriptor_table_protodef_SubscriptionKind_2eproto[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = 35 | "\n\026SubscriptionKind.proto\022\031Arp.Plc.Gds.Se" 36 | "rvices.Grpc*\204\001\n\020SubscriptionKind\022\013\n\007SK_N" 37 | "one\020\000\022\026\n\022SK_HighPerformance\020\001\022\017\n\013SK_Real" 38 | "Time\020\002\022\020\n\014SK_Recording\020\003\022\025\n\021SK_ClosedRea" 39 | "lTime\020\004\022\021\n\rSK_DirectRead\020\005b\006proto3" 40 | ; 41 | static const ::PROTOBUF_NAMESPACE_ID::internal::DescriptorTable*const descriptor_table_SubscriptionKind_2eproto_deps[1] = { 42 | }; 43 | static ::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase*const descriptor_table_SubscriptionKind_2eproto_sccs[1] = { 44 | }; 45 | static ::PROTOBUF_NAMESPACE_ID::internal::once_flag descriptor_table_SubscriptionKind_2eproto_once; 46 | const ::PROTOBUF_NAMESPACE_ID::internal::DescriptorTable descriptor_table_SubscriptionKind_2eproto = { 47 | false, false, descriptor_table_protodef_SubscriptionKind_2eproto, "SubscriptionKind.proto", 194, 48 | &descriptor_table_SubscriptionKind_2eproto_once, descriptor_table_SubscriptionKind_2eproto_sccs, descriptor_table_SubscriptionKind_2eproto_deps, 0, 0, 49 | schemas, file_default_instances, TableStruct_SubscriptionKind_2eproto::offsets, 50 | file_level_metadata_SubscriptionKind_2eproto, 0, file_level_enum_descriptors_SubscriptionKind_2eproto, file_level_service_descriptors_SubscriptionKind_2eproto, 51 | }; 52 | 53 | // Force running AddDescriptors() at dynamic initialization time. 54 | static bool dynamic_init_dummy_SubscriptionKind_2eproto = (static_cast(::PROTOBUF_NAMESPACE_ID::internal::AddDescriptors(&descriptor_table_SubscriptionKind_2eproto)), true); 55 | namespace Arp { 56 | namespace Plc { 57 | namespace Gds { 58 | namespace Services { 59 | namespace Grpc { 60 | const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* SubscriptionKind_descriptor() { 61 | ::PROTOBUF_NAMESPACE_ID::internal::AssignDescriptors(&descriptor_table_SubscriptionKind_2eproto); 62 | return file_level_enum_descriptors_SubscriptionKind_2eproto[0]; 63 | } 64 | bool SubscriptionKind_IsValid(int value) { 65 | switch (value) { 66 | case 0: 67 | case 1: 68 | case 2: 69 | case 3: 70 | case 4: 71 | case 5: 72 | return true; 73 | default: 74 | return false; 75 | } 76 | } 77 | 78 | 79 | // @@protoc_insertion_point(namespace_scope) 80 | } // namespace Grpc 81 | } // namespace Services 82 | } // namespace Gds 83 | } // namespace Plc 84 | } // namespace Arp 85 | PROTOBUF_NAMESPACE_OPEN 86 | PROTOBUF_NAMESPACE_CLOSE 87 | 88 | // @@protoc_insertion_point(global_scope) 89 | #include 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Doxygen generated docs 2 | doc/ 3 | 4 | # Prerequisites 5 | *.d 6 | 7 | # Compiled Object files 8 | *.slo 9 | *.lo 10 | *.o 11 | *.obj 12 | 13 | # Precompiled Headers 14 | *.gch 15 | *.pch 16 | 17 | # Compiled Dynamic libraries 18 | *.so 19 | *.dylib 20 | *.dll 21 | 22 | # Fortran module files 23 | *.mod 24 | *.smod 25 | 26 | # Compiled Static libraries 27 | *.lai 28 | *.la 29 | *.a 30 | *.lib 31 | 32 | # Executables 33 | *.exe 34 | *.out 35 | *.app 36 | 37 | # Byte-compiled / optimized / DLL files 38 | __pycache__/ 39 | *.py[cod] 40 | *$py.class 41 | 42 | # C extensions 43 | *.so 44 | 45 | # Distribution / packaging 46 | .Python 47 | build/ 48 | develop-eggs/ 49 | dist/ 50 | downloads/ 51 | eggs/ 52 | .eggs/ 53 | lib/ 54 | lib64/ 55 | parts/ 56 | sdist/ 57 | var/ 58 | wheels/ 59 | share/python-wheels/ 60 | *.egg-info/ 61 | .installed.cfg 62 | *.egg 63 | MANIFEST 64 | 65 | # PyInstaller 66 | # Usually these files are written by a python script from a template 67 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 68 | *.manifest 69 | *.spec 70 | 71 | # Installer logs 72 | pip-log.txt 73 | pip-delete-this-directory.txt 74 | 75 | # Unit test / coverage reports 76 | htmlcov/ 77 | .tox/ 78 | .nox/ 79 | .coverage 80 | .coverage.* 81 | .cache 82 | nosetests.xml 83 | coverage.xml 84 | *.cover 85 | *.py,cover 86 | .hypothesis/ 87 | .pytest_cache/ 88 | cover/ 89 | 90 | # Translations 91 | *.mo 92 | *.pot 93 | 94 | # Django stuff: 95 | *.log 96 | local_settings.py 97 | db.sqlite3 98 | db.sqlite3-journal 99 | 100 | # Flask stuff: 101 | instance/ 102 | .webassets-cache 103 | 104 | # Scrapy stuff: 105 | .scrapy 106 | 107 | # Sphinx documentation 108 | docs/_build/ 109 | 110 | # PyBuilder 111 | .pybuilder/ 112 | target/ 113 | 114 | # Jupyter Notebook 115 | .ipynb_checkpoints 116 | 117 | # IPython 118 | profile_default/ 119 | ipython_config.py 120 | 121 | # pyenv 122 | # For a library or package, you might want to ignore these files since the code is 123 | # intended to run in multiple environments; otherwise, check them in: 124 | # .python-version 125 | 126 | # pipenv 127 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 128 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 129 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 130 | # install all needed dependencies. 131 | #Pipfile.lock 132 | 133 | # poetry 134 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 135 | # This is especially recommended for binary packages to ensure reproducibility, and is more 136 | # commonly ignored for libraries. 137 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 138 | #poetry.lock 139 | 140 | # pdm 141 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 142 | #pdm.lock 143 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 144 | # in version control. 145 | # https://pdm.fming.dev/#use-with-ide 146 | .pdm.toml 147 | 148 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 149 | __pypackages__/ 150 | 151 | # Celery stuff 152 | celerybeat-schedule 153 | celerybeat.pid 154 | 155 | # SageMath parsed files 156 | *.sage.py 157 | 158 | # Environments 159 | .env 160 | .venv 161 | env/ 162 | venv/ 163 | ENV/ 164 | env.bak/ 165 | venv.bak/ 166 | 167 | # Spyder project settings 168 | .spyderproject 169 | .spyproject 170 | 171 | # Rope project settings 172 | .ropeproject 173 | 174 | # mkdocs documentation 175 | /site 176 | 177 | # mypy 178 | .mypy_cache/ 179 | .dmypy.json 180 | dmypy.json 181 | 182 | # Pyre type checker 183 | .pyre/ 184 | 185 | # pytype static type analyzer 186 | .pytype/ 187 | 188 | # Cython debug symbols 189 | cython_debug/ 190 | 191 | # PyCharm 192 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 193 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 194 | # and can be added to the global gitignore or merged into this file. For a more nuclear 195 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 196 | #.idea/ 197 | 198 | .vscode 199 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/phoenix_comm.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 Fraunhofer IPA 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #ifndef PHOENIX_BRIDGE__PHOENIX_COMM_HPP_ 18 | #define PHOENIX_BRIDGE__PHOENIX_COMM_HPP_ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "phoenix_bridge/read_conversions.hpp" 25 | #include "phoenix_bridge/write_conversions.hpp" 26 | 27 | /** 28 | * @brief This will form the communication layer for the birdge b/w ROS and PLC. 29 | * Implemented with gRPC 30 | * Each bridge type will instantiate one instance of a gRPC channel per type 31 | */ 32 | template 33 | class PhoenixComm 34 | { 35 | public: 36 | PhoenixComm(); 37 | bool sendToPLC(const std::string instance_path, const T & data); 38 | bool getFromPLC(const std::string instance_path, T & data); 39 | void init(const std::string address); 40 | 41 | private: 42 | std::unique_ptr stub_; /// Stub to access underlying grpc functionality 43 | }; 44 | 45 | /** 46 | * @brief Constructor 47 | * 48 | * @tparam T Template type 49 | */ 50 | template 51 | inline PhoenixComm::PhoenixComm() 52 | { 53 | } 54 | 55 | /** 56 | * @brief Send data to the PLC through grpc. 57 | * @param instance_path Send data to this path in the PLC GDS 58 | * @param data the data to send. The type is one of the ROS msgs as defined under phoenix_contact/include_types.h, or a base type. 59 | * @return if sending was succesfull 60 | * @todo Room for improvement? Error catching? Performance optimisation? 61 | */ 62 | template 63 | inline bool PhoenixComm::sendToPLC(const std::string instance_path, const T & data) 64 | { 65 | IDataAccessServiceWriteRequest request; 66 | 67 | ::Arp::Plc::Gds::Services::Grpc::WriteItem * grpc_object = request.add_data(); 68 | grpc_object->set_portname(instance_path); 69 | 70 | conversions::packWriteItem(grpc_object, data); 71 | 72 | ClientContext context; 73 | IDataAccessServiceWriteResponse reply; 74 | Status status = stub_->Write(&context, request, &reply); 75 | 76 | if (status.ok()) { 77 | return true; 78 | } else { 79 | RCLCPP_WARN_STREAM_ONCE( 80 | rclcpp::get_logger(instance_path), status.error_code() << ": " << status.error_message()); 81 | return false; 82 | } 83 | } 84 | 85 | /** 86 | * @brief Get data from the PLC through the gRPC server 87 | * @param instance_path Get data from the PLC GDS at this address 88 | * @param data Cast and write the received data into this variable. 89 | * The type is one of the ROS msgs as defined under phoenix_contact/include_types.h or a basic type 90 | * @return if retrieval was succesfull 91 | */ 92 | template 93 | inline bool PhoenixComm::getFromPLC(const std::string instance_path, T & data) 94 | { 95 | IDataAccessServiceReadRequest request; 96 | ClientContext context; 97 | IDataAccessServiceReadResponse reply; 98 | 99 | request.add_portnames(instance_path); 100 | Status status = stub_->Read(&context, request, &reply); 101 | 102 | if (status.ok()) { 103 | ObjectType grpc_object = reply._returnvalue(0).value(); 104 | conversions::unpackReadObject(grpc_object, data); 105 | return true; 106 | } else { 107 | RCLCPP_WARN_STREAM_ONCE( 108 | rclcpp::get_logger(instance_path), status.error_code() << ": " << status.error_message()); 109 | return false; 110 | } 111 | } 112 | 113 | /** 114 | * @brief Initialise the communication layer by creating the gRPC channel from the address 115 | * @param address Unix socket address of channel 116 | */ 117 | template 118 | inline void PhoenixComm::init(const std::string address) 119 | { 120 | stub_ = 121 | IDataAccessService::NewStub(grpc::CreateChannel(address, grpc::InsecureChannelCredentials())); 122 | } 123 | #endif // PHOENIX_BRIDGE__PHOENIX_COMM_HPP_ 124 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/DataAccessError.pb.cc: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: DataAccessError.proto 3 | 4 | #include "DataAccessError.pb.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | // @@protoc_insertion_point(includes) 16 | #include 17 | namespace Arp { 18 | namespace Plc { 19 | namespace Gds { 20 | namespace Services { 21 | namespace Grpc { 22 | } // namespace Grpc 23 | } // namespace Services 24 | } // namespace Gds 25 | } // namespace Plc 26 | } // namespace Arp 27 | static constexpr ::PROTOBUF_NAMESPACE_ID::Metadata* file_level_metadata_DataAccessError_2eproto = nullptr; 28 | static const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* file_level_enum_descriptors_DataAccessError_2eproto[1]; 29 | static constexpr ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor const** file_level_service_descriptors_DataAccessError_2eproto = nullptr; 30 | const ::PROTOBUF_NAMESPACE_ID::uint32 TableStruct_DataAccessError_2eproto::offsets[1] = {}; 31 | static constexpr ::PROTOBUF_NAMESPACE_ID::internal::MigrationSchema* schemas = nullptr; 32 | static constexpr ::PROTOBUF_NAMESPACE_ID::Message* const* file_default_instances = nullptr; 33 | 34 | const char descriptor_table_protodef_DataAccessError_2eproto[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = 35 | "\n\025DataAccessError.proto\022\031Arp.Plc.Gds.Ser" 36 | "vices.Grpc*\332\002\n\017DataAccessError\022\014\n\010DAE_No" 37 | "ne\020\000\022\021\n\rDAE_NotExists\020\001\022\025\n\021DAE_NotAuthor" 38 | "ized\020\002\022\024\n\020DAE_TypeMismatch\020\003\022\033\n\027DAE_Port" 39 | "NameSyntaxError\020\004\022\035\n\031DAE_PortNameSemanti" 40 | "cError\020\005\022\027\n\023DAE_IndexOutOfRange\020\006\022\026\n\022DAE" 41 | "_NotImplemented\020\007\022\024\n\020DAE_NotSupported\020\010\022" 42 | "\034\n\030DAE_CurrentlyUnavailable\020\t\022\033\n\027DAE_Unv" 43 | "alidSubscription\020\n\022\016\n\nDAE_NoData\020\013\022\025\n\021DA" 44 | "E_InvalidConfig\020\014\022\024\n\017DAE_Unspecified\020\377\001b" 45 | "\006proto3" 46 | ; 47 | static const ::PROTOBUF_NAMESPACE_ID::internal::DescriptorTable*const descriptor_table_DataAccessError_2eproto_deps[1] = { 48 | }; 49 | static ::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase*const descriptor_table_DataAccessError_2eproto_sccs[1] = { 50 | }; 51 | static ::PROTOBUF_NAMESPACE_ID::internal::once_flag descriptor_table_DataAccessError_2eproto_once; 52 | const ::PROTOBUF_NAMESPACE_ID::internal::DescriptorTable descriptor_table_DataAccessError_2eproto = { 53 | false, false, descriptor_table_protodef_DataAccessError_2eproto, "DataAccessError.proto", 407, 54 | &descriptor_table_DataAccessError_2eproto_once, descriptor_table_DataAccessError_2eproto_sccs, descriptor_table_DataAccessError_2eproto_deps, 0, 0, 55 | schemas, file_default_instances, TableStruct_DataAccessError_2eproto::offsets, 56 | file_level_metadata_DataAccessError_2eproto, 0, file_level_enum_descriptors_DataAccessError_2eproto, file_level_service_descriptors_DataAccessError_2eproto, 57 | }; 58 | 59 | // Force running AddDescriptors() at dynamic initialization time. 60 | static bool dynamic_init_dummy_DataAccessError_2eproto = (static_cast(::PROTOBUF_NAMESPACE_ID::internal::AddDescriptors(&descriptor_table_DataAccessError_2eproto)), true); 61 | namespace Arp { 62 | namespace Plc { 63 | namespace Gds { 64 | namespace Services { 65 | namespace Grpc { 66 | const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* DataAccessError_descriptor() { 67 | ::PROTOBUF_NAMESPACE_ID::internal::AssignDescriptors(&descriptor_table_DataAccessError_2eproto); 68 | return file_level_enum_descriptors_DataAccessError_2eproto[0]; 69 | } 70 | bool DataAccessError_IsValid(int value) { 71 | switch (value) { 72 | case 0: 73 | case 1: 74 | case 2: 75 | case 3: 76 | case 4: 77 | case 5: 78 | case 6: 79 | case 7: 80 | case 8: 81 | case 9: 82 | case 10: 83 | case 11: 84 | case 12: 85 | case 255: 86 | return true; 87 | default: 88 | return false; 89 | } 90 | } 91 | 92 | 93 | // @@protoc_insertion_point(namespace_scope) 94 | } // namespace Grpc 95 | } // namespace Services 96 | } // namespace Gds 97 | } // namespace Plc 98 | } // namespace Arp 99 | PROTOBUF_NAMESPACE_OPEN 100 | PROTOBUF_NAMESPACE_CLOSE 101 | 102 | // @@protoc_insertion_point(global_scope) 103 | #include 104 | -------------------------------------------------------------------------------- /ci/gitlab_templates/RULES.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | ROS2_DEV_BRANCH: ros2/devel 3 | ROS1_DEV_BRANCH: ros1/devel 4 | ROS2_PREFIX: ros2 5 | ROS1_PREFIX: ros1 6 | 7 | .on_ros2: 8 | rules: 9 | - !reference [.rules-map, not_merge_event] 10 | - !reference [.rules-map, not_ros2_branch] 11 | - !reference [.rules-map, allow_failure_distros] 12 | - !reference [.rules-map, not_allow_failure_distros] 13 | 14 | .on_ros1: 15 | rules: 16 | - !reference [.rules-map, not_merge_event] 17 | - !reference [.rules-map, not_ros1_branch] 18 | - !reference [.rules-map, allow_failure_distros] 19 | - !reference [.rules-map, not_allow_failure_distros] 20 | 21 | .on_ros2_deps: 22 | rules: 23 | - !reference [.rules-map, not_merge_event] 24 | - !reference [.rules-map, not_ros2_branch] 25 | - !reference [.rules-map, deps_changes_allow_failure_distros] 26 | - !reference [.rules-map, deps_changes_not_allow_failure_distros] 27 | 28 | .on_ros1_deps: 29 | rules: 30 | - !reference [.rules-map, not_merge_event] 31 | - !reference [.rules-map, not_ros1_branch] 32 | - !reference [.rules-map, deps_changes_allow_failure_distros] 33 | - !reference [.rules-map, deps_changes_not_allow_failure_distros] 34 | 35 | .on_ros2_tag: 36 | rules: 37 | - !reference [.rules-map, not_ros2_branch] 38 | - !reference [.rules-map, not_commit_tag] 39 | - !reference [.rules-map, allow_failure_distros] 40 | - !reference [.rules-map, not_allow_failure_distros] 41 | 42 | .on_ros1_tag: 43 | rules: 44 | - !reference [.rules-map, not_ros1_branch] 45 | - !reference [.rules-map, not_commit_tag] 46 | - !reference [.rules-map, allow_failure_distros] 47 | - !reference [.rules-map, not_allow_failure_distros] 48 | 49 | .on_ros2_merge: 50 | rules: 51 | - !reference [.rules-map, not_ros2_branch] 52 | - !reference [.rules-map, not_merge_into_ros2_devel] 53 | - !reference [.rules-map, allow_failure_distros] 54 | - !reference [.rules-map, not_allow_failure_distros] 55 | 56 | .on_ros1_merge: 57 | rules: 58 | - !reference [.rules-map, not_ros1_branch] 59 | - !reference [.rules-map, not_merge_into_ros1_devel] 60 | - !reference [.rules-map, allow_failure_distros] 61 | - !reference [.rules-map, not_allow_failure_distros] 62 | 63 | .on_ros2_merge_tag: 64 | rules: 65 | - !reference [.rules-map, not_ros2_branch] 66 | - !reference [.rules-map, not_merge_into_ros2_devel_and_tag] 67 | - !reference [.rules-map, allow_failure_distros] 68 | - !reference [.rules-map, not_allow_failure_distros] 69 | 70 | .on_ros1_merge_tag: 71 | rules: 72 | - !reference [.rules-map, not_ros1_branch] 73 | - !reference [.rules-map, not_merge_into_ros1_devel_and_tag] 74 | - !reference [.rules-map, allow_failure_distros] 75 | - !reference [.rules-map, not_allow_failure_distros] 76 | 77 | .rules-map: 78 | ros2_branch: 79 | - if: $CI_COMMIT_REF_NAME =~ /ros2/ 80 | when: on_success 81 | not_ros2_branch: 82 | - if: $CI_COMMIT_REF_NAME !~ /^ros2.*/ 83 | when: never 84 | not_ros1_branch: 85 | - if: $CI_COMMIT_REF_NAME !~ /^ros1.*/ 86 | when: never 87 | allow_failure_distros: 88 | - if: $ROS_DISTRO !~ $DEFAULT_ROS_DISTROS 89 | when: on_success 90 | allow_failure: true 91 | not_allow_failure_distros: 92 | - if: $ROS_DISTRO =~ $DEFAULT_ROS_DISTROS 93 | when: on_success 94 | allow_failure: false 95 | deps_changes_allow_failure_distros: 96 | - if: $ROS_DISTRO !~ $DEFAULT_ROS_DISTROS 97 | changes: 98 | paths: 99 | - $DEP_PATH/* 100 | when: on_success 101 | allow_failure: true 102 | deps_changes_not_allow_failure_distros: 103 | - if: $ROS_DISTRO =~ $DEFAULT_ROS_DISTROS 104 | when: on_success 105 | changes: 106 | paths: 107 | - $DEP_PATH/* 108 | allow_failure: false 109 | not_merge_into_ros1_devel: 110 | - if: $CI_COMMIT_BRANCH != $ROS1_DEV_BRANCH 111 | when: never 112 | not_merge_into_ros2_devel: 113 | - if: $CI_COMMIT_BRANCH != $ROS2_DEV_BRANCH 114 | when: never 115 | not_merge_into_ros2_devel_and_tag: 116 | - if: $CI_COMMIT_BRANCH != $ROS2_DEV_BRANCH && $CI_COMMIT_TAG == null 117 | when: never 118 | not_merge_into_ros1_devel_and_tag: 119 | - if: $CI_COMMIT_BRANCH != $ROS1_DEV_BRANCH && $CI_COMMIT_TAG == null 120 | when: never 121 | commit_tag: 122 | - if: $CI_COMMIT_TAG != null 123 | when: on_success 124 | not_commit_tag: 125 | - if: $CI_COMMIT_TAG == null 126 | when: never 127 | not_merge_event: 128 | - if: $CI_PIPELINE_SOURCE == "merge_request_event" 129 | when: never 130 | -------------------------------------------------------------------------------- /app/initscript.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # keep track of the last executed command 3 | trap 'last_command=$current_command; current_command=$BASH_COMMAND' DEBUG 4 | # echo an error message before exiting 5 | trap 'echo "$(date): \"${last_command}\" command filed with exit code $?."' EXIT 6 | 7 | #set -x # for debugging 8 | 9 | ### BEGIN INIT INFO 10 | # Provides: @@@APPNAME@@@ 11 | # Required-Start: $syslog $remote_fs 12 | # Required-Stop: $syslog $remote_fs 13 | # Default-Start: 2 3 4 5 14 | # Default-Stop: 0 1 6 15 | ### END INIT INFO 16 | 17 | USER=root 18 | CONTAINER_ENGINE=podman 19 | COMPOSE_ENGINE=podman-compose 20 | APP_NAME="@@@APPNAME@@@" 21 | APP_HOME="/opt/plcnext/appshome" 22 | APP_ID="@@@APPIDENTIFIER@@@" 23 | 24 | ##________Usefull environment variables________## 25 | 26 | export APP_UNIQUE_NAME="@@@APPNAME@@@_@@@APPIDENTIFIER@@@" # unique AppName to use 27 | export APP_PATH="/opt/plcnext/apps/${APP_ID}" # mountpoint for ro app file 28 | export APP_TMP_PATH="/var/tmp/appsdata/${APP_ID}" # app temporary data storage 29 | export APP_DATA_PATH="${APP_HOME}/data/${APP_ID}" # app persistent data storage 30 | export APP_LOG="${APP_DATA_PATH}/${APP_NAME}.log" # logfile 31 | export ROS_IP=$(ip -o addr show | awk '{print $4}' | cut -d / -f 1 | head -5 | tail +5) 32 | 33 | #add path to app-binaries 34 | export PATH=$PATH:$APP_HOME/bin 35 | 36 | start () 37 | { 38 | if [ ! -e "$APP_LOG" ] 39 | then 40 | touch $APP_LOG 41 | fi 42 | echo "$(date): Executing start()" >> $APP_LOG 43 | 44 | # When start() is executed then 45 | # either 46 | # the image is not loaded and the container not started. 47 | # Then load the docker image from the app directory and run container with 48 | # restart option to start it again after each boot of the controller. 49 | # or 50 | # the container is already loaded into the container cache. 51 | # It will be started by the container engine automatically. 52 | 53 | echo "$(date) Executing start()" >> $APP_LOG 54 | 55 | # Check app, install, run 56 | if [ -e "$APP_DATA_PATH/dockerapp_install" ]; then 57 | # app is already installed and container exists 58 | # just start it 59 | echo "$NAME is already installed. podman will start it automatically" >> $APP_LOG 60 | 61 | chmod -R 777 $APP_DATA_PATH 62 | cd $APP_DATA_PATH 63 | 64 | podman-compose down 65 | podman-compose up -d 66 | 67 | else 68 | echo "$NAME is not installed --> load images and install" >> $APP_LOG 69 | echo "Copy content" >> $APP_LOG 70 | cp -r $APP_PATH "$APP_HOME/data" 71 | 72 | chmod -R 777 $APP_DATA_PATH 73 | 74 | echo "Load images" >> $APP_LOG 75 | podman load -i $APP_DATA_PATH/images/plcnext-ros-bridge.tar >> $APP_LOG 2>&1 76 | if [ ! $? -eq 0 ] 77 | then 78 | stop 79 | echo "$(date): Wasn't able to load $APP_NAME." >> $APP_LOG 80 | exit 201 81 | fi 82 | 83 | # Remove tmp tar file from container load 84 | rm /media/rfs/rw/data/system/containers/docker-tar* >> $APP_LOG 2>&1 85 | if [ ! $? -eq 0 ] 86 | then 87 | echo "$(date): Wasn't able to remove with command: '$0'." >> $APP_LOG 88 | fi 89 | 90 | echo "Start compose" >> $APP_LOG 91 | cd $APP_DATA_PATH 92 | podman-compose up -d >> $APP_LOG 2>&1 93 | if [ ! $? -eq 0 ] 94 | then 95 | stop 96 | echo "$(date): Wasn't able to start podman compose." >> $APP_LOG 97 | exit 201 98 | fi 99 | 100 | # set app is installed 101 | touch $APP_DATA_PATH/dockerapp_install 102 | echo "Installation finished" >> $APP_LOG 103 | fi 104 | 105 | echo "$(date) start() finished" >> $APP_LOG 106 | 107 | # write Podman events to syslog 108 | logger -f /run/libpod/events/events.log 109 | rm -f /run/libpod/events/events.log 110 | } 111 | 112 | stop () 113 | { 114 | echo "$(date) Executing stop()" >> $APP_LOG 115 | # stop() is called when App is stopped by the AppManager e.g. via WBM 116 | # in this case the container needs to be stopped and removed from 117 | # the container cache. The goal is to keep the controller clean. 118 | # stop() is also called when the system will shutdown. 119 | # In this case the container should not be removed. 120 | # The container should just be started when the controller starts up again. 121 | if [ -e "$APP_DATA_PATH/dockerapp_install" ]; then 122 | # Distinguish whether the controller is in shutdown phase 123 | # if not shutdown then the app is explicitly stopped -> remove container and image 124 | currentRunlevel=$(runlevel | cut -d ' ' -f2) 125 | echo current runlevel=$currentRunlevel >> $APP_LOG 126 | 127 | if [ "$currentRunlevel" -ne "6" ]; then 128 | echo "Stop $NAME" >> $APP_LOG 129 | cd $APP_DATA_PATH 130 | echo "Remove compose and all used images." 131 | podman-compose down 132 | podman image rm --force §§IMAGE_ID§§ 133 | rm $APP_DATA_PATH/dockerapp_install 134 | fi 135 | else 136 | echo "Stop $NAME: container not installed" >> $APP_LOG 137 | # the container is not installed. 138 | # nothing to be done 139 | fi 140 | echo "$(date) stop() finished" >> $APP_LOG 141 | 142 | # write Podman events to syslog 143 | logger -f /run/libpod/events/events.log 144 | rm -f /run/libpod/events/events.log 145 | } 146 | 147 | case "$1" in 148 | start) 149 | start 150 | ;; 151 | stop) 152 | stop 153 | ;; 154 | restart) 155 | stop 156 | start 157 | ;; 158 | esac -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/SubscriptionKind.pb.h: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: SubscriptionKind.proto 3 | 4 | #ifndef GOOGLE_PROTOBUF_INCLUDED_SubscriptionKind_2eproto 5 | #define GOOGLE_PROTOBUF_INCLUDED_SubscriptionKind_2eproto 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #if PROTOBUF_VERSION < 3014000 12 | #error This file was generated by a newer version of protoc which is 13 | #error incompatible with your Protocol Buffer headers. Please update 14 | #error your headers. 15 | #endif 16 | #if 3014000 < PROTOBUF_MIN_PROTOC_VERSION 17 | #error This file was generated by an older version of protoc which is 18 | #error incompatible with your Protocol Buffer headers. Please 19 | #error regenerate this file with a newer version of protoc. 20 | #endif 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include // IWYU pragma: export 31 | #include // IWYU pragma: export 32 | #include 33 | // @@protoc_insertion_point(includes) 34 | #include 35 | #define PROTOBUF_INTERNAL_EXPORT_SubscriptionKind_2eproto 36 | PROTOBUF_NAMESPACE_OPEN 37 | namespace internal { 38 | class AnyMetadata; 39 | } // namespace internal 40 | PROTOBUF_NAMESPACE_CLOSE 41 | 42 | // Internal implementation detail -- do not use these members. 43 | struct TableStruct_SubscriptionKind_2eproto { 44 | static const ::PROTOBUF_NAMESPACE_ID::internal::ParseTableField entries[] 45 | PROTOBUF_SECTION_VARIABLE(protodesc_cold); 46 | static const ::PROTOBUF_NAMESPACE_ID::internal::AuxiliaryParseTableField aux[] 47 | PROTOBUF_SECTION_VARIABLE(protodesc_cold); 48 | static const ::PROTOBUF_NAMESPACE_ID::internal::ParseTable schema[1] 49 | PROTOBUF_SECTION_VARIABLE(protodesc_cold); 50 | static const ::PROTOBUF_NAMESPACE_ID::internal::FieldMetadata field_metadata[]; 51 | static const ::PROTOBUF_NAMESPACE_ID::internal::SerializationTable serialization_table[]; 52 | static const ::PROTOBUF_NAMESPACE_ID::uint32 offsets[]; 53 | }; 54 | extern const ::PROTOBUF_NAMESPACE_ID::internal::DescriptorTable descriptor_table_SubscriptionKind_2eproto; 55 | PROTOBUF_NAMESPACE_OPEN 56 | PROTOBUF_NAMESPACE_CLOSE 57 | namespace Arp { 58 | namespace Plc { 59 | namespace Gds { 60 | namespace Services { 61 | namespace Grpc { 62 | 63 | enum SubscriptionKind : int { 64 | SK_None = 0, 65 | SK_HighPerformance = 1, 66 | SK_RealTime = 2, 67 | SK_Recording = 3, 68 | SK_ClosedRealTime = 4, 69 | SK_DirectRead = 5, 70 | SubscriptionKind_INT_MIN_SENTINEL_DO_NOT_USE_ = std::numeric_limits<::PROTOBUF_NAMESPACE_ID::int32>::min(), 71 | SubscriptionKind_INT_MAX_SENTINEL_DO_NOT_USE_ = std::numeric_limits<::PROTOBUF_NAMESPACE_ID::int32>::max() 72 | }; 73 | bool SubscriptionKind_IsValid(int value); 74 | constexpr SubscriptionKind SubscriptionKind_MIN = SK_None; 75 | constexpr SubscriptionKind SubscriptionKind_MAX = SK_DirectRead; 76 | constexpr int SubscriptionKind_ARRAYSIZE = SubscriptionKind_MAX + 1; 77 | 78 | const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* SubscriptionKind_descriptor(); 79 | template 80 | inline const std::string& SubscriptionKind_Name(T enum_t_value) { 81 | static_assert(::std::is_same::value || 82 | ::std::is_integral::value, 83 | "Incorrect type passed to function SubscriptionKind_Name."); 84 | return ::PROTOBUF_NAMESPACE_ID::internal::NameOfEnum( 85 | SubscriptionKind_descriptor(), enum_t_value); 86 | } 87 | inline bool SubscriptionKind_Parse( 88 | ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, SubscriptionKind* value) { 89 | return ::PROTOBUF_NAMESPACE_ID::internal::ParseNamedEnum( 90 | SubscriptionKind_descriptor(), name, value); 91 | } 92 | // =================================================================== 93 | 94 | 95 | // =================================================================== 96 | 97 | 98 | // =================================================================== 99 | 100 | #ifdef __GNUC__ 101 | #pragma GCC diagnostic push 102 | #pragma GCC diagnostic ignored "-Wstrict-aliasing" 103 | #endif // __GNUC__ 104 | #ifdef __GNUC__ 105 | #pragma GCC diagnostic pop 106 | #endif // __GNUC__ 107 | 108 | // @@protoc_insertion_point(namespace_scope) 109 | 110 | } // namespace Grpc 111 | } // namespace Services 112 | } // namespace Gds 113 | } // namespace Plc 114 | } // namespace Arp 115 | 116 | PROTOBUF_NAMESPACE_OPEN 117 | 118 | template <> struct is_proto_enum< ::Arp::Plc::Gds::Services::Grpc::SubscriptionKind> : ::std::true_type {}; 119 | template <> 120 | inline const EnumDescriptor* GetEnumDescriptor< ::Arp::Plc::Gds::Services::Grpc::SubscriptionKind>() { 121 | return ::Arp::Plc::Gds::Services::Grpc::SubscriptionKind_descriptor(); 122 | } 123 | 124 | PROTOBUF_NAMESPACE_CLOSE 125 | 126 | // @@protoc_insertion_point(global_scope) 127 | 128 | #include 129 | #endif // GOOGLE_PROTOBUF_INCLUDED_GOOGLE_PROTOBUF_INCLUDED_SubscriptionKind_2eproto 130 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/phoenix_io_services.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 Fraunhofer IPA 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #ifndef PHOENIX_BRIDGE__PHOENIX_IO_SERVICES_HPP_ 18 | #define PHOENIX_BRIDGE__PHOENIX_IO_SERVICES_HPP_ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "phoenix_bridge/phoenix_comm.hpp" 25 | #include "phoenix_interfaces/msg/set_io.hpp" 26 | #include "phoenix_interfaces/srv/analog_io.hpp" 27 | #include "phoenix_interfaces/srv/batch_get_io.hpp" 28 | #include "phoenix_interfaces/srv/batch_set_io.hpp" 29 | #include "phoenix_interfaces/srv/single_get_io.hpp" 30 | #include "phoenix_interfaces/srv/single_set_io.hpp" 31 | 32 | using phoenix_interfaces::srv::AnalogIO; 33 | using phoenix_interfaces::srv::BatchGetIO; 34 | using phoenix_interfaces::srv::BatchSetIO; 35 | using phoenix_interfaces::srv::SingleGetIO; 36 | using phoenix_interfaces::srv::SingleSetIO; 37 | 38 | /** 39 | * @brief Class to provide the ros services for setting/getting IOs on the PLC 40 | * Services are offered to read/write digital IOs in a batch or individually, and analog IOs only individually. 41 | * 42 | */ 43 | class PhoenixIOServices : public rclcpp::Node 44 | { 45 | public: 46 | /** 47 | * @brief Construct a new Phoenix I O Services:: Phoenix I O Services object 48 | * 49 | * @param node_name Node name 50 | * @param options Options 51 | */ 52 | PhoenixIOServices(const std::string node_name, const rclcpp::NodeOptions & options); 53 | 54 | private: 55 | rclcpp::Service::SharedPtr single_set_service_; /// Write one DIO 56 | rclcpp::Service::SharedPtr single_get_service_; /// Read one DIO 57 | rclcpp::Service::SharedPtr batch_set_service_; /// Write a batch of DIOs 58 | rclcpp::Service::SharedPtr batch_get_service_; /// Read a batch of DIOs 59 | rclcpp::Service::SharedPtr read_analog_service; /// Read one AIO 60 | rclcpp::Service::SharedPtr write_analog_service; /// Write one AIO 61 | 62 | PhoenixComm digital_comm_; /// Communication layer object for DIOs 63 | PhoenixComm analog_comm_; /// Communication layer object for AIOs 64 | 65 | /** 66 | * @brief Callback for the Single set DIO service 67 | * 68 | * @param request DIO instance path and value to set 69 | * @param response grpc call status 70 | * @return true If succesfully set 71 | * @return false If setting failed 72 | */ 73 | bool singleSetCB( 74 | const std::shared_ptr request, 75 | std::shared_ptr response); 76 | 77 | /** 78 | * @brief Callback for the single get DIO service 79 | * 80 | * @param request DIO instance path to read 81 | * @param response grpc call status and value retrieved 82 | * @return true If succesfully retrieved 83 | * @return false If getting failed 84 | */ 85 | bool singleGetCB( 86 | const std::shared_ptr request, 87 | std::shared_ptr response); 88 | 89 | /** 90 | * @brief Callback for the Batch set DIO service 91 | * 92 | * @param request Array of DIO paths and values to set 93 | * @param response grpc call status 94 | * @return true If succesfully set 95 | * @return false If setting failed 96 | * @todo Replace looped call of single DIO to a single call with arrays of DIOS 97 | */ 98 | bool batchSetCB( 99 | const std::shared_ptr request, 100 | std::shared_ptr response); 101 | 102 | /** 103 | * @brief Callback for the Batch get DIO service 104 | * 105 | * @param request Array of paths to get 106 | * @param response grpc call status and arrays of retrieved values 107 | * @return true If succesfully retrieved 108 | * @return false If getting failed 109 | * @todo Replace looped call of single DIO to a single call with arrays of DIOS 110 | */ 111 | bool batchGetCB( 112 | const std::shared_ptr request, 113 | std::shared_ptr response); 114 | 115 | /** 116 | * @brief Callback for the Single read AIO service 117 | * 118 | * @param request Instance path of AIO to get 119 | * @param response grpc call status and value of AIO 120 | * @return true If succesfully retrieved 121 | * @return false If getting failed 122 | */ 123 | bool readAnalogIOCB( 124 | const std::shared_ptr request, std::shared_ptr response); 125 | 126 | /** 127 | * @brief Callback for the Single write AIO service 128 | * 129 | * @param request Instance path & value of AIO to write 130 | * @param response grpc call status 131 | * @return true If succesfully written 132 | * @return false If writing failed 133 | */ 134 | bool writeAnalogIOCB( 135 | const std::shared_ptr request, std::shared_ptr response); 136 | }; 137 | 138 | #endif // PHOENIX_BRIDGE__PHOENIX_IO_SERVICES_HPP_ 139 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/DataAccessError.pb.h: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: DataAccessError.proto 3 | 4 | #ifndef GOOGLE_PROTOBUF_INCLUDED_DataAccessError_2eproto 5 | #define GOOGLE_PROTOBUF_INCLUDED_DataAccessError_2eproto 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #if PROTOBUF_VERSION < 3014000 12 | #error This file was generated by a newer version of protoc which is 13 | #error incompatible with your Protocol Buffer headers. Please update 14 | #error your headers. 15 | #endif 16 | #if 3014000 < PROTOBUF_MIN_PROTOC_VERSION 17 | #error This file was generated by an older version of protoc which is 18 | #error incompatible with your Protocol Buffer headers. Please 19 | #error regenerate this file with a newer version of protoc. 20 | #endif 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include // IWYU pragma: export 31 | #include // IWYU pragma: export 32 | #include 33 | // @@protoc_insertion_point(includes) 34 | #include 35 | #define PROTOBUF_INTERNAL_EXPORT_DataAccessError_2eproto 36 | PROTOBUF_NAMESPACE_OPEN 37 | namespace internal { 38 | class AnyMetadata; 39 | } // namespace internal 40 | PROTOBUF_NAMESPACE_CLOSE 41 | 42 | // Internal implementation detail -- do not use these members. 43 | struct TableStruct_DataAccessError_2eproto { 44 | static const ::PROTOBUF_NAMESPACE_ID::internal::ParseTableField entries[] 45 | PROTOBUF_SECTION_VARIABLE(protodesc_cold); 46 | static const ::PROTOBUF_NAMESPACE_ID::internal::AuxiliaryParseTableField aux[] 47 | PROTOBUF_SECTION_VARIABLE(protodesc_cold); 48 | static const ::PROTOBUF_NAMESPACE_ID::internal::ParseTable schema[1] 49 | PROTOBUF_SECTION_VARIABLE(protodesc_cold); 50 | static const ::PROTOBUF_NAMESPACE_ID::internal::FieldMetadata field_metadata[]; 51 | static const ::PROTOBUF_NAMESPACE_ID::internal::SerializationTable serialization_table[]; 52 | static const ::PROTOBUF_NAMESPACE_ID::uint32 offsets[]; 53 | }; 54 | extern const ::PROTOBUF_NAMESPACE_ID::internal::DescriptorTable descriptor_table_DataAccessError_2eproto; 55 | PROTOBUF_NAMESPACE_OPEN 56 | PROTOBUF_NAMESPACE_CLOSE 57 | namespace Arp { 58 | namespace Plc { 59 | namespace Gds { 60 | namespace Services { 61 | namespace Grpc { 62 | 63 | enum DataAccessError : int { 64 | DAE_None = 0, 65 | DAE_NotExists = 1, 66 | DAE_NotAuthorized = 2, 67 | DAE_TypeMismatch = 3, 68 | DAE_PortNameSyntaxError = 4, 69 | DAE_PortNameSemanticError = 5, 70 | DAE_IndexOutOfRange = 6, 71 | DAE_NotImplemented = 7, 72 | DAE_NotSupported = 8, 73 | DAE_CurrentlyUnavailable = 9, 74 | DAE_UnvalidSubscription = 10, 75 | DAE_NoData = 11, 76 | DAE_InvalidConfig = 12, 77 | DAE_Unspecified = 255, 78 | DataAccessError_INT_MIN_SENTINEL_DO_NOT_USE_ = std::numeric_limits<::PROTOBUF_NAMESPACE_ID::int32>::min(), 79 | DataAccessError_INT_MAX_SENTINEL_DO_NOT_USE_ = std::numeric_limits<::PROTOBUF_NAMESPACE_ID::int32>::max() 80 | }; 81 | bool DataAccessError_IsValid(int value); 82 | constexpr DataAccessError DataAccessError_MIN = DAE_None; 83 | constexpr DataAccessError DataAccessError_MAX = DAE_Unspecified; 84 | constexpr int DataAccessError_ARRAYSIZE = DataAccessError_MAX + 1; 85 | 86 | const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* DataAccessError_descriptor(); 87 | template 88 | inline const std::string& DataAccessError_Name(T enum_t_value) { 89 | static_assert(::std::is_same::value || 90 | ::std::is_integral::value, 91 | "Incorrect type passed to function DataAccessError_Name."); 92 | return ::PROTOBUF_NAMESPACE_ID::internal::NameOfEnum( 93 | DataAccessError_descriptor(), enum_t_value); 94 | } 95 | inline bool DataAccessError_Parse( 96 | ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, DataAccessError* value) { 97 | return ::PROTOBUF_NAMESPACE_ID::internal::ParseNamedEnum( 98 | DataAccessError_descriptor(), name, value); 99 | } 100 | // =================================================================== 101 | 102 | 103 | // =================================================================== 104 | 105 | 106 | // =================================================================== 107 | 108 | #ifdef __GNUC__ 109 | #pragma GCC diagnostic push 110 | #pragma GCC diagnostic ignored "-Wstrict-aliasing" 111 | #endif // __GNUC__ 112 | #ifdef __GNUC__ 113 | #pragma GCC diagnostic pop 114 | #endif // __GNUC__ 115 | 116 | // @@protoc_insertion_point(namespace_scope) 117 | 118 | } // namespace Grpc 119 | } // namespace Services 120 | } // namespace Gds 121 | } // namespace Plc 122 | } // namespace Arp 123 | 124 | PROTOBUF_NAMESPACE_OPEN 125 | 126 | template <> struct is_proto_enum< ::Arp::Plc::Gds::Services::Grpc::DataAccessError> : ::std::true_type {}; 127 | template <> 128 | inline const EnumDescriptor* GetEnumDescriptor< ::Arp::Plc::Gds::Services::Grpc::DataAccessError>() { 129 | return ::Arp::Plc::Gds::Services::Grpc::DataAccessError_descriptor(); 130 | } 131 | 132 | PROTOBUF_NAMESPACE_CLOSE 133 | 134 | // @@protoc_insertion_point(global_scope) 135 | 136 | #include 137 | #endif // GOOGLE_PROTOBUF_INCLUDED_GOOGLE_PROTOBUF_INCLUDED_DataAccessError_2eproto 138 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/DataType.pb.cc: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: DataType.proto 3 | 4 | #include "DataType.pb.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | // @@protoc_insertion_point(includes) 16 | #include 17 | namespace Arp { 18 | namespace Plc { 19 | namespace Grpc { 20 | } // namespace Grpc 21 | } // namespace Plc 22 | } // namespace Arp 23 | static constexpr ::PROTOBUF_NAMESPACE_ID::Metadata* file_level_metadata_DataType_2eproto = nullptr; 24 | static const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* file_level_enum_descriptors_DataType_2eproto[1]; 25 | static constexpr ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor const** file_level_service_descriptors_DataType_2eproto = nullptr; 26 | const ::PROTOBUF_NAMESPACE_ID::uint32 TableStruct_DataType_2eproto::offsets[1] = {}; 27 | static constexpr ::PROTOBUF_NAMESPACE_ID::internal::MigrationSchema* schemas = nullptr; 28 | static constexpr ::PROTOBUF_NAMESPACE_ID::Message* const* file_default_instances = nullptr; 29 | 30 | const char descriptor_table_protodef_DataType_2eproto[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = 31 | "\n\016DataType.proto\022\014Arp.Plc.Grpc*\373\005\n\010DataT" 32 | "ype\022\013\n\007DT_None\020\000\022\013\n\007DT_Void\020\001\022\n\n\006DT_Bit\020" 33 | "\002\022\016\n\nDT_Boolean\020\003\022\014\n\010DT_UInt8\020\004\022\013\n\007DT_In" 34 | "t8\020\005\022\014\n\010DT_Char8\020\006\022\r\n\tDT_Char16\020\007\022\r\n\tDT_" 35 | "UInt16\020\010\022\014\n\010DT_Int16\020\t\022\r\n\tDT_UInt32\020\n\022\014\n" 36 | "\010DT_Int32\020\013\022\r\n\tDT_UInt64\020\014\022\014\n\010DT_Int64\020\r" 37 | "\022\016\n\nDT_Float32\020\016\022\016\n\nDT_Float64\020\017\022\020\n\014DT_P" 38 | "rimitive\020 \022\017\n\013DT_DateTime\020!\022\016\n\nDT_IecTim" 39 | "e\020\"\022\020\n\014DT_IecTime64\020#\022\016\n\nDT_IecDate\020$\022\020\n" 40 | "\014DT_IecDate64\020%\022\022\n\016DT_IecDateTime\020&\022\024\n\020D" 41 | "T_IecDateTime64\020\'\022\023\n\017DT_IecTimeOfDay\020(\022\025" 42 | "\n\021DT_IecTimeOfDay64\020)\022\023\n\017DT_StaticString" 43 | "\020*\022\020\n\014DT_IecString\020+\022\020\n\014DT_ClrString\020,\022\r" 44 | "\n\tDT_String\020-\022\021\n\rDT_Elementary\020@\022\023\n\017DT_A" 45 | "rrayElement\020A\022\r\n\tDT_Struct\020B\022\014\n\010DT_Class" 46 | "\020C\022\024\n\020DT_FunctionBlock\020D\022\020\n\014DT_Subsystem" 47 | "\020E\022\016\n\nDT_Program\020F\022\020\n\014DT_Component\020G\022\016\n\n" 48 | "DT_Library\020H\022\017\n\nDT_Complex\020\376\001\022\017\n\nDT_Poin" 49 | "ter\020\200\004\022\r\n\010DT_Array\020\200\010\022\014\n\007DT_Enum\020\200\020\022\021\n\014D" 50 | "T_Reference\020\200 \022\024\n\017DT_BaseTypeMask\020\377\001b\006pr" 51 | "oto3" 52 | ; 53 | static const ::PROTOBUF_NAMESPACE_ID::internal::DescriptorTable*const descriptor_table_DataType_2eproto_deps[1] = { 54 | }; 55 | static ::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase*const descriptor_table_DataType_2eproto_sccs[1] = { 56 | }; 57 | static ::PROTOBUF_NAMESPACE_ID::internal::once_flag descriptor_table_DataType_2eproto_once; 58 | const ::PROTOBUF_NAMESPACE_ID::internal::DescriptorTable descriptor_table_DataType_2eproto = { 59 | false, false, descriptor_table_protodef_DataType_2eproto, "DataType.proto", 804, 60 | &descriptor_table_DataType_2eproto_once, descriptor_table_DataType_2eproto_sccs, descriptor_table_DataType_2eproto_deps, 0, 0, 61 | schemas, file_default_instances, TableStruct_DataType_2eproto::offsets, 62 | file_level_metadata_DataType_2eproto, 0, file_level_enum_descriptors_DataType_2eproto, file_level_service_descriptors_DataType_2eproto, 63 | }; 64 | 65 | // Force running AddDescriptors() at dynamic initialization time. 66 | static bool dynamic_init_dummy_DataType_2eproto = (static_cast(::PROTOBUF_NAMESPACE_ID::internal::AddDescriptors(&descriptor_table_DataType_2eproto)), true); 67 | namespace Arp { 68 | namespace Plc { 69 | namespace Grpc { 70 | const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* DataType_descriptor() { 71 | ::PROTOBUF_NAMESPACE_ID::internal::AssignDescriptors(&descriptor_table_DataType_2eproto); 72 | return file_level_enum_descriptors_DataType_2eproto[0]; 73 | } 74 | bool DataType_IsValid(int value) { 75 | switch (value) { 76 | case 0: 77 | case 1: 78 | case 2: 79 | case 3: 80 | case 4: 81 | case 5: 82 | case 6: 83 | case 7: 84 | case 8: 85 | case 9: 86 | case 10: 87 | case 11: 88 | case 12: 89 | case 13: 90 | case 14: 91 | case 15: 92 | case 32: 93 | case 33: 94 | case 34: 95 | case 35: 96 | case 36: 97 | case 37: 98 | case 38: 99 | case 39: 100 | case 40: 101 | case 41: 102 | case 42: 103 | case 43: 104 | case 44: 105 | case 45: 106 | case 64: 107 | case 65: 108 | case 66: 109 | case 67: 110 | case 68: 111 | case 69: 112 | case 70: 113 | case 71: 114 | case 72: 115 | case 254: 116 | case 255: 117 | case 512: 118 | case 1024: 119 | case 2048: 120 | case 4096: 121 | return true; 122 | default: 123 | return false; 124 | } 125 | } 126 | 127 | 128 | // @@protoc_insertion_point(namespace_scope) 129 | } // namespace Grpc 130 | } // namespace Plc 131 | } // namespace Arp 132 | PROTOBUF_NAMESPACE_OPEN 133 | PROTOBUF_NAMESPACE_CLOSE 134 | 135 | // @@protoc_insertion_point(global_scope) 136 | #include 137 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/DataType.pb.h: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: DataType.proto 3 | 4 | #ifndef GOOGLE_PROTOBUF_INCLUDED_DataType_2eproto 5 | #define GOOGLE_PROTOBUF_INCLUDED_DataType_2eproto 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #if PROTOBUF_VERSION < 3014000 12 | #error This file was generated by a newer version of protoc which is 13 | #error incompatible with your Protocol Buffer headers. Please update 14 | #error your headers. 15 | #endif 16 | #if 3014000 < PROTOBUF_MIN_PROTOC_VERSION 17 | #error This file was generated by an older version of protoc which is 18 | #error incompatible with your Protocol Buffer headers. Please 19 | #error regenerate this file with a newer version of protoc. 20 | #endif 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include // IWYU pragma: export 31 | #include // IWYU pragma: export 32 | #include 33 | // @@protoc_insertion_point(includes) 34 | #include 35 | #define PROTOBUF_INTERNAL_EXPORT_DataType_2eproto 36 | PROTOBUF_NAMESPACE_OPEN 37 | namespace internal { 38 | class AnyMetadata; 39 | } // namespace internal 40 | PROTOBUF_NAMESPACE_CLOSE 41 | 42 | // Internal implementation detail -- do not use these members. 43 | struct TableStruct_DataType_2eproto { 44 | static const ::PROTOBUF_NAMESPACE_ID::internal::ParseTableField entries[] 45 | PROTOBUF_SECTION_VARIABLE(protodesc_cold); 46 | static const ::PROTOBUF_NAMESPACE_ID::internal::AuxiliaryParseTableField aux[] 47 | PROTOBUF_SECTION_VARIABLE(protodesc_cold); 48 | static const ::PROTOBUF_NAMESPACE_ID::internal::ParseTable schema[1] 49 | PROTOBUF_SECTION_VARIABLE(protodesc_cold); 50 | static const ::PROTOBUF_NAMESPACE_ID::internal::FieldMetadata field_metadata[]; 51 | static const ::PROTOBUF_NAMESPACE_ID::internal::SerializationTable serialization_table[]; 52 | static const ::PROTOBUF_NAMESPACE_ID::uint32 offsets[]; 53 | }; 54 | extern const ::PROTOBUF_NAMESPACE_ID::internal::DescriptorTable descriptor_table_DataType_2eproto; 55 | PROTOBUF_NAMESPACE_OPEN 56 | PROTOBUF_NAMESPACE_CLOSE 57 | namespace Arp { 58 | namespace Plc { 59 | namespace Grpc { 60 | 61 | enum DataType : int { 62 | DT_None = 0, 63 | DT_Void = 1, 64 | DT_Bit = 2, 65 | DT_Boolean = 3, 66 | DT_UInt8 = 4, 67 | DT_Int8 = 5, 68 | DT_Char8 = 6, 69 | DT_Char16 = 7, 70 | DT_UInt16 = 8, 71 | DT_Int16 = 9, 72 | DT_UInt32 = 10, 73 | DT_Int32 = 11, 74 | DT_UInt64 = 12, 75 | DT_Int64 = 13, 76 | DT_Float32 = 14, 77 | DT_Float64 = 15, 78 | DT_Primitive = 32, 79 | DT_DateTime = 33, 80 | DT_IecTime = 34, 81 | DT_IecTime64 = 35, 82 | DT_IecDate = 36, 83 | DT_IecDate64 = 37, 84 | DT_IecDateTime = 38, 85 | DT_IecDateTime64 = 39, 86 | DT_IecTimeOfDay = 40, 87 | DT_IecTimeOfDay64 = 41, 88 | DT_StaticString = 42, 89 | DT_IecString = 43, 90 | DT_ClrString = 44, 91 | DT_String = 45, 92 | DT_Elementary = 64, 93 | DT_ArrayElement = 65, 94 | DT_Struct = 66, 95 | DT_Class = 67, 96 | DT_FunctionBlock = 68, 97 | DT_Subsystem = 69, 98 | DT_Program = 70, 99 | DT_Component = 71, 100 | DT_Library = 72, 101 | DT_Complex = 254, 102 | DT_Pointer = 512, 103 | DT_Array = 1024, 104 | DT_Enum = 2048, 105 | DT_Reference = 4096, 106 | DT_BaseTypeMask = 255, 107 | DataType_INT_MIN_SENTINEL_DO_NOT_USE_ = std::numeric_limits<::PROTOBUF_NAMESPACE_ID::int32>::min(), 108 | DataType_INT_MAX_SENTINEL_DO_NOT_USE_ = std::numeric_limits<::PROTOBUF_NAMESPACE_ID::int32>::max() 109 | }; 110 | bool DataType_IsValid(int value); 111 | constexpr DataType DataType_MIN = DT_None; 112 | constexpr DataType DataType_MAX = DT_Reference; 113 | constexpr int DataType_ARRAYSIZE = DataType_MAX + 1; 114 | 115 | const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* DataType_descriptor(); 116 | template 117 | inline const std::string& DataType_Name(T enum_t_value) { 118 | static_assert(::std::is_same::value || 119 | ::std::is_integral::value, 120 | "Incorrect type passed to function DataType_Name."); 121 | return ::PROTOBUF_NAMESPACE_ID::internal::NameOfEnum( 122 | DataType_descriptor(), enum_t_value); 123 | } 124 | inline bool DataType_Parse( 125 | ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, DataType* value) { 126 | return ::PROTOBUF_NAMESPACE_ID::internal::ParseNamedEnum( 127 | DataType_descriptor(), name, value); 128 | } 129 | // =================================================================== 130 | 131 | 132 | // =================================================================== 133 | 134 | 135 | // =================================================================== 136 | 137 | #ifdef __GNUC__ 138 | #pragma GCC diagnostic push 139 | #pragma GCC diagnostic ignored "-Wstrict-aliasing" 140 | #endif // __GNUC__ 141 | #ifdef __GNUC__ 142 | #pragma GCC diagnostic pop 143 | #endif // __GNUC__ 144 | 145 | // @@protoc_insertion_point(namespace_scope) 146 | 147 | } // namespace Grpc 148 | } // namespace Plc 149 | } // namespace Arp 150 | 151 | PROTOBUF_NAMESPACE_OPEN 152 | 153 | template <> struct is_proto_enum< ::Arp::Plc::Grpc::DataType> : ::std::true_type {}; 154 | template <> 155 | inline const EnumDescriptor* GetEnumDescriptor< ::Arp::Plc::Grpc::DataType>() { 156 | return ::Arp::Plc::Grpc::DataType_descriptor(); 157 | } 158 | 159 | PROTOBUF_NAMESPACE_CLOSE 160 | 161 | // @@protoc_insertion_point(global_scope) 162 | 163 | #include 164 | #endif // GOOGLE_PROTOBUF_INCLUDED_GOOGLE_PROTOBUF_INCLUDED_DataType_2eproto 165 | -------------------------------------------------------------------------------- /phoenix_bridge/src/phoenix_io_services.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 Fraunhofer IPA 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #include "phoenix_bridge/phoenix_io_services.hpp" 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | using phoenix_interfaces::srv::AnalogIO; 24 | using phoenix_interfaces::srv::BatchGetIO; 25 | using phoenix_interfaces::srv::BatchSetIO; 26 | using phoenix_interfaces::srv::SingleGetIO; 27 | using phoenix_interfaces::srv::SingleSetIO; 28 | 29 | PhoenixIOServices::PhoenixIOServices( 30 | const std::string node_name, const rclcpp::NodeOptions & options) 31 | : rclcpp::Node(node_name, options) 32 | { 33 | RCLCPP_INFO_STREAM(this->get_logger(), "Initialising services"); 34 | 35 | this->declare_parameter("grpc.address", "unix:/run/plcnext/grpc.sock"); 36 | 37 | digital_comm_.init(this->get_parameter("grpc.address").as_string()); 38 | analog_comm_.init(this->get_parameter("grpc.address").as_string()); 39 | 40 | single_set_service_ = this->create_service( 41 | "single_set_IO", 42 | std::bind(&PhoenixIOServices::singleSetCB, this, std::placeholders::_1, std::placeholders::_2)); 43 | single_get_service_ = this->create_service( 44 | "single_get_IO", 45 | std::bind(&PhoenixIOServices::singleGetCB, this, std::placeholders::_1, std::placeholders::_2)); 46 | batch_set_service_ = this->create_service( 47 | "batch_set_IO", 48 | std::bind(&PhoenixIOServices::batchSetCB, this, std::placeholders::_1, std::placeholders::_2)); 49 | batch_get_service_ = this->create_service( 50 | "batch_get_IO", 51 | std::bind(&PhoenixIOServices::batchGetCB, this, std::placeholders::_1, std::placeholders::_2)); 52 | read_analog_service = this->create_service( 53 | "read_analog_IO", 54 | std::bind( 55 | &PhoenixIOServices::readAnalogIOCB, this, std::placeholders::_1, std::placeholders::_2)); 56 | write_analog_service = this->create_service( 57 | "write_analog_IO", 58 | std::bind( 59 | &PhoenixIOServices::writeAnalogIOCB, this, std::placeholders::_1, std::placeholders::_2)); 60 | } 61 | 62 | bool PhoenixIOServices::singleSetCB( 63 | const std::shared_ptr request, 64 | std::shared_ptr response) 65 | { 66 | RCLCPP_INFO_STREAM(this->get_logger(), "Single set service called"); 67 | response->status = digital_comm_.sendToPLC(request->datapath, request->value); 68 | if (!response->status) { 69 | RCLCPP_WARN_STREAM(this->get_logger(), " set_single_IO failed"); 70 | } 71 | return response->status; 72 | } 73 | 74 | bool PhoenixIOServices::singleGetCB( 75 | const std::shared_ptr request, 76 | std::shared_ptr response) 77 | { 78 | RCLCPP_INFO_STREAM(this->get_logger(), "Single get service called"); 79 | bool val; 80 | response->status = digital_comm_.getFromPLC(request->datapath, val); 81 | response->value = val; 82 | if (!response->status) { 83 | RCLCPP_WARN_STREAM(this->get_logger(), "single_get_service failed"); 84 | } 85 | return response->status; 86 | } 87 | 88 | bool PhoenixIOServices::batchSetCB( 89 | const std::shared_ptr request, 90 | std::shared_ptr response) 91 | { 92 | if (request->payload.size() == 0) { 93 | RCLCPP_WARN_STREAM(this->get_logger(), "batch_set_service got empty payload request!!"); 94 | response->status = false; 95 | return false; 96 | } 97 | response->status = true; 98 | for (uint64_t i = 0; i < request->payload.size(); i++) { 99 | response->status = 100 | digital_comm_.sendToPLC(request->payload[i].datapath, request->payload[i].value); 101 | if (!response->status) { 102 | RCLCPP_WARN_STREAM( 103 | this->get_logger(), "batch_set_service failed setting " << request->payload[i].datapath); 104 | return false; 105 | } 106 | } 107 | return response->status; 108 | } 109 | 110 | bool PhoenixIOServices::batchGetCB( 111 | const std::shared_ptr request, 112 | std::shared_ptr response) 113 | { 114 | RCLCPP_INFO_STREAM(this->get_logger(), "Batch set service called"); 115 | if (request->datapaths.size() == 0) { 116 | RCLCPP_WARN_STREAM(this->get_logger(), "batch_get_service got empty payload request!!"); 117 | response->status = false; 118 | return false; 119 | } 120 | response->status = true; 121 | 122 | for (uint64_t i = 0; i < request->datapaths.size(); i++) { 123 | bool val = true; 124 | if (!digital_comm_.getFromPLC(request->datapaths[i], val)) { 125 | response->status = false; 126 | RCLCPP_WARN_STREAM( 127 | this->get_logger(), "batch_get_service failed getting " << request->datapaths[i]); 128 | return false; 129 | } 130 | response->values.push_back(val); 131 | } 132 | return response->status; 133 | } 134 | 135 | bool PhoenixIOServices::readAnalogIOCB( 136 | const std::shared_ptr request, std::shared_ptr response) 137 | { 138 | RCLCPP_INFO_STREAM(this->get_logger(), "Read analog IO service called"); 139 | double val; 140 | response->status = analog_comm_.getFromPLC(request->instance_path, val); 141 | response->instance_path = request->instance_path; 142 | response->value = val; 143 | if (!response->status) { 144 | RCLCPP_WARN_STREAM(this->get_logger(), "read_analog_IO failed"); 145 | } 146 | return response->status; 147 | } 148 | 149 | bool PhoenixIOServices::writeAnalogIOCB( 150 | const std::shared_ptr request, std::shared_ptr response) 151 | { 152 | RCLCPP_INFO_STREAM(this->get_logger(), "Write analog IO service called"); 153 | response->status = analog_comm_.sendToPLC(request->instance_path, request->value); 154 | response->instance_path = request->instance_path; 155 | if (!response->status) { 156 | RCLCPP_WARN_STREAM(this->get_logger(), " write_analog_IO failed"); 157 | } 158 | return response->status; 159 | } 160 | -------------------------------------------------------------------------------- /phoenix_bridge/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(phoenix_bridge) 3 | 4 | # Default to C99 5 | if(NOT CMAKE_C_STANDARD) 6 | set(CMAKE_C_STANDARD 99) 7 | endif() 8 | 9 | # Default to C++14 10 | if(NOT CMAKE_CXX_STANDARD) 11 | set(CMAKE_CXX_STANDARD 14) 12 | endif() 13 | 14 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 15 | add_compile_options(-Wall -Wextra -Wpedantic) 16 | endif() 17 | 18 | add_custom_target( 19 | cog ALL 20 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 21 | COMMAND echo "Generating bridge types based on config file" 22 | COMMAND cog -r ${CMAKE_CURRENT_SOURCE_DIR}/include/phoenix_bridge/include_types.h 23 | COMMAND cog -r ${CMAKE_CURRENT_SOURCE_DIR}/include/phoenix_bridge/read_conversions.hpp 24 | COMMAND cog -r ${CMAKE_CURRENT_SOURCE_DIR}/include/phoenix_bridge/write_conversions.hpp 25 | COMMAND cog -r ${CMAKE_CURRENT_SOURCE_DIR}/src/phoenix_bridge_node.cpp 26 | COMMENT "COGGING" 27 | ) 28 | 29 | list(APPEND MSG_TYPES_TO_LINK 30 | geometry_msgs 31 | nav_msgs 32 | std_msgs 33 | ) 34 | 35 | # Find all required messages 36 | foreach(msg ${MSG_TYPES_TO_LINK}) 37 | find_package(${msg} REQUIRED) 38 | endforeach() 39 | 40 | find_package(ament_cmake REQUIRED) 41 | find_package(ament_cmake_python REQUIRED) 42 | find_package(rclcpp REQUIRED) 43 | find_package(gRPC CONFIG REQUIRED) 44 | find_package(phoenix_interfaces REQUIRED) 45 | 46 | # C++ stuff 47 | include_directories(include) 48 | 49 | set(WILDCARD_SOURCE *.cpp *.cc) 50 | set(WILDCARD_HEADER *.h *.hpp *.hxx) 51 | file(GLOB_RECURSE Sources 52 | include/${PROJECT_NAME}/${WILDCARD_SOURCE} 53 | ) 54 | file(GLOB_RECURSE Headers 55 | include/${PROJECT_NAME}/${WILDCARD_HEADER} 56 | ) 57 | 58 | add_executable(phoenix_bridge_node src/phoenix_bridge_node.cpp 59 | ${Headers} 60 | ${Sources}) 61 | target_link_libraries(phoenix_bridge_node gRPC::gpr gRPC::grpc gRPC::grpc++) 62 | add_dependencies(phoenix_bridge_node cog) 63 | ament_target_dependencies(phoenix_bridge_node rclcpp ${MSG_TYPES_TO_LINK}) 64 | target_include_directories(phoenix_bridge_node 65 | PUBLIC 66 | $ 67 | $ 68 | $ 69 | $ 70 | ) 71 | 72 | add_executable(phoenix_io_services_node src/phoenix_io_services_node.cpp 73 | src/phoenix_io_services.cpp 74 | ${Headers} 75 | ${Sources}) 76 | target_link_libraries(phoenix_io_services_node gRPC::gpr gRPC::grpc gRPC::grpc++) 77 | ament_target_dependencies(phoenix_io_services_node rclcpp phoenix_interfaces ${MSG_TYPES_TO_LINK}) 78 | target_include_directories(phoenix_io_services_node 79 | PUBLIC 80 | $ 81 | $ 82 | $ 83 | $ 84 | ) 85 | 86 | add_executable(liveliness_check src/liveliness_check.cpp 87 | ${Headers} 88 | ${Sources}) 89 | target_link_libraries(liveliness_check gRPC::gpr gRPC::grpc gRPC::grpc++) 90 | ament_target_dependencies(liveliness_check rclcpp ${MSG_TYPES_TO_LINK}) 91 | target_include_directories(liveliness_check 92 | PUBLIC 93 | $ 94 | $ 95 | $ 96 | $ 97 | ) 98 | 99 | install(TARGETS 100 | phoenix_bridge_node 101 | phoenix_io_services_node 102 | liveliness_check 103 | DESTINATION lib/${PROJECT_NAME}) 104 | 105 | # Python stuff 106 | ament_python_install_package(${PROJECT_NAME}) 107 | 108 | install(DIRECTORY 109 | launch 110 | config 111 | DESTINATION share/${PROJECT_NAME} 112 | ) 113 | 114 | if(BUILD_TESTING) 115 | # Linting 116 | find_package(ament_cmake_clang_format REQUIRED) 117 | find_package(ament_cmake_cpplint REQUIRED) 118 | find_package(ament_cmake_cppcheck REQUIRED) 119 | find_package(ament_cmake_xmllint REQUIRED) 120 | 121 | set(files_to_lint 122 | include/${PROJECT_NAME}/bridge_type.hpp 123 | include/${PROJECT_NAME}/phoenix_comm.hpp 124 | include/${PROJECT_NAME}/phoenix_io_services.hpp 125 | src/phoenix_bridge_node.cpp 126 | src/phoenix_io_services_node.cpp 127 | src/phoenix_io_services.cpp 128 | src/liveliness_check.cpp 129 | ) 130 | 131 | ament_clang_format(${files_to_lint}) 132 | ament_cpplint(${files_to_lint} FILTERS "-build/include_order") 133 | ament_cppcheck(${files_to_lint}) 134 | ament_xmllint() 135 | 136 | # Unit testing 137 | find_package(ament_cmake_gtest REQUIRED) 138 | 139 | ament_add_gtest(test_write_conversions test/test_write_conversions.cpp ${Headers} ${Sources}) 140 | target_link_libraries(test_write_conversions gRPC::gpr gRPC::grpc gRPC::grpc++) 141 | target_include_directories(test_write_conversions PUBLIC 142 | $ 143 | $ 144 | $ 145 | $) 146 | ament_target_dependencies(test_write_conversions rclcpp ${MSG_TYPES_TO_LINK}) 147 | 148 | ament_add_gtest(test_read_conversions test/test_read_conversions.cpp ${Headers} ${Sources}) 149 | target_link_libraries(test_read_conversions gRPC::gpr gRPC::grpc gRPC::grpc++) 150 | target_include_directories(test_read_conversions PUBLIC 151 | $ 152 | $ 153 | $ 154 | $) 155 | ament_target_dependencies(test_read_conversions rclcpp ${MSG_TYPES_TO_LINK}) 156 | 157 | # Component testing 158 | find_package(launch_testing_ament_cmake) 159 | add_launch_test(test/launch_test_bridge.test.py) 160 | 161 | endif() 162 | 163 | ament_package() 164 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - local: ci/gitlab_templates/RULES.yml 3 | 4 | variables: 5 | ROS_BRIDGE_VERSION: "2.0" 6 | TARGET_DEVICES: AXC F 3152 #,EPC 1522,EPC 1502 7 | 8 | DOCKER_IMAGE_PATH: ${CI_PROJECT_DIR}/generated 9 | APP_PATH: ${CI_PROJECT_DIR}/app/images 10 | APP_NAME: plcnext-ros-bridge 11 | BUILDER_PREFIX: "ghcr.io/ipa-rwu/" 12 | BUILDER_SUFFIX: ":latest" 13 | CMAKE_ARGS: "-DCMAKE_BUILD_TYPE=RELEASE" 14 | DEFAULT_ROS_DISTROS: noetic foxy galactic rolling humble 15 | DEP_REPO_NAME: gRPC-client-deps 16 | DEP_REPO_URL: https://github.com/PLCnext/${DEP_REPO_NAME}.git 17 | DOCKER_BUILDKIT: 1 18 | DOCKER_TLS_CERTDIR: "" 19 | FOLDER: ${CI_JOB_NAME}/ 20 | NAME: ${CI_JOB_NAME} 21 | PREFIX: "${CI_PIPELINE_ID}:" 22 | ROS_VERSIONS: ros1 ros2 23 | ROSINSTALL_CI_JOB_TOKEN: "true" 24 | STABLE_BRANCH: devel 25 | SUFFIX: "" 26 | TARGET_LATEST: ${NAME}:latest 27 | TARGET_RELEASE: ${NAME}:${CI_COMMIT_TAG//\//-} 28 | 29 | .common: 30 | tags: &kaniko_runner 31 | - shared-runner-kubernetes 32 | create_release_tag_script: 33 | - | 34 | if [ "$CI_COMMIT_REF_NAME" = "main" ] || [ "$CI_COMMIT_REF_NAME" = "master" ]; then 35 | TARGET=$TARGET_LATEST 36 | fi 37 | if [[ $(expr match "$CI_COMMIT_REF_NAME" ".*$STABLE_BRANCH") != 0 ]]; then 38 | TARGET=$TARGET_LATEST 39 | fi 40 | if [ $CI_COMMIT_TAG ]; then 41 | convert_tag=${CI_COMMIT_TAG//\//-} 42 | distro_prefix=${ROS_DISTRO}- 43 | remove_distro_tag=${convert_tag#"$distro_prefix"} 44 | TARGET_RELEASE=${NAME}:${remove_distro_tag} 45 | TARGET=$TARGET_RELEASE 46 | fi 47 | 48 | .build: 49 | stage: build 50 | tags: *kaniko_runner 51 | image: 52 | name: gcr.io/kaniko-project/executor:v1.9.0-debug 53 | entrypoint: [""] 54 | script: 55 | - /kaniko/executor 56 | --context "${CI_PROJECT_DIR}/${FOLDER}" 57 | --build-arg SUFFIX 58 | --build-arg PREFIX 59 | --build-arg DEP_REPO_NAME 60 | --build-arg DEP_REPO_URL 61 | --build-arg BUILDER_PREFIX 62 | --build-arg BUILDER_SUFFIX 63 | --build-arg ROS_DISTRO 64 | --build-arg ROSINSTALL_CI_JOB_TOKEN 65 | --build-arg CI_JOB_TOKEN 66 | --build-arg BUILDKIT_INLINE_CACHE=1 67 | --build-arg CMAKE_ARGS 68 | --dockerfile "${CI_PROJECT_DIR}/${FOLDER}Dockerfile" 69 | --no-push 70 | --destination "${TARGET}" 71 | --tarPath ${PREFIX//:/-}${NAME}${SUFFIX}.tar 72 | variables: 73 | NAME: ${CI_JOB_NAME}_${ROS_DISTRO} 74 | artifacts: 75 | name: ${NAME} 76 | paths: 77 | - ${PREFIX//:/-}${NAME}${SUFFIX}.tar 78 | expire_in: 10 minutes 79 | 80 | build_${APP_NAME}_ros1: 81 | extends: 82 | - .build 83 | - .on_ros1 84 | before_script: 85 | - TARGET=${PREFIX}${NAME}${SUFFIX} 86 | - !reference [.common, create_release_tag_script] 87 | variables: 88 | NAME: ${APP_NAME}_${ROS_DISTRO} 89 | FOLDER: "" 90 | parallel: 91 | matrix: 92 | - ROS_DISTRO: 93 | - noetic 94 | 95 | build_${APP_NAME}_ros2: 96 | extends: 97 | - .build 98 | - .on_ros2 99 | before_script: 100 | - TARGET=${PREFIX}${NAME}${SUFFIX} 101 | - !reference [.common, create_release_tag_script] 102 | variables: 103 | NAME: ${APP_NAME}_${ROS_DISTRO} 104 | FOLDER: "" 105 | parallel: 106 | matrix: 107 | - ROS_DISTRO: 108 | - foxy 109 | #- galactic 110 | - humble 111 | - rolling 112 | 113 | .publish: 114 | stage: publish 115 | tags: *kaniko_runner 116 | image: alpine:latest 117 | script: 118 | - TARGET=${NAME}:${CI_COMMIT_REF_NAME//\//-} 119 | - !reference [.common, create_release_tag_script] 120 | - mkdir ${DOCKER_IMAGE_PATH} 121 | - mv ${PREFIX//:/-}${NAME}${SUFFIX}.tar ${DOCKER_IMAGE_PATH}/${TARGET//:/-}.tar 122 | needs: 123 | - build 124 | artifacts: 125 | name: ${NAME} 126 | paths: 127 | - ${DOCKER_IMAGE_PATH}/*.tar 128 | expire_in: 10 minutes 129 | 130 | publish_${APP_NAME}_ros1: 131 | extends: 132 | - .publish 133 | - .on_ros1_merge_tag 134 | variables: 135 | NAME: ${APP_NAME}_${ROS_DISTRO} 136 | needs: 137 | - build_${APP_NAME}_ros1 138 | parallel: 139 | matrix: 140 | - ROS_DISTRO: 141 | - noetic 142 | 143 | publish_${APP_NAME}_ros2: 144 | extends: 145 | - .publish 146 | - .on_ros2_merge_tag 147 | variables: 148 | NAME: ${APP_NAME}_${ROS_DISTRO} 149 | needs: 150 | - build_${APP_NAME}_ros2 151 | parallel: 152 | matrix: 153 | - ROS_DISTRO: 154 | - foxy 155 | # - galactic 156 | - humble 157 | - rolling 158 | 159 | .deploy_app: 160 | stage: deploy_app 161 | tags: *kaniko_runner 162 | image: 163 | name: debian:bookworm 164 | before_script: 165 | - apt-get update 166 | - apt-get install --yes squashfs-tools rpm 167 | - TARGET=${NAME}:${CI_COMMIT_REF_NAME//\//-} 168 | - !reference [.common, create_release_tag_script] 169 | script: 170 | - tar -tf ${DOCKER_IMAGE_PATH}/${TARGET//:/-}.tar --wildcards 'sha256*' >> ./image.id 171 | - sed -i 's/[^:]*:\(.*\)/\1/' image.id 172 | - mkdir $APP_PATH 173 | - cp ${DOCKER_IMAGE_PATH}/${TARGET//:/-}.tar $APP_PATH/$APP_NAME.tar 174 | - sed -i "s/§§IMAGE_ID§§/$(>> underscore("DeviceType") 64 | 'device_type' 65 | 66 | As a rule of thumb you can think of :func:`underscore` as the inverse of 67 | :func:`camelize`, though there are cases where that does not hold:: 68 | 69 | >>> camelize(underscore("IOError")) 70 | 'IoError' 71 | 72 | """ 73 | word = re.sub(r"([A-Z]+)([A-Z][a-z])", r'\1_\2', word) 74 | word = re.sub(r"([a-z\d])([A-Z])", r'\1_\2', word) 75 | word = word.replace("-", "_") 76 | return word.lower() 77 | 78 | # def getResolvedTypeName(typename: str): 79 | # """ Converts the ROS msg typename into a fully scope resolved C++ class name """ 80 | # parts = typename.split("/") 81 | # result = "" 82 | # for i in range(size(parts)): 83 | # if i < size(parts) - 1: 84 | # result = result + parts[i] + "::" 85 | # else: 86 | # result = result + parts[i].title() 87 | # return result 88 | 89 | class ParamParser(object): 90 | """ 91 | Class to access parameter parsing functionality. Constructor does parsing and saves resulting model in varialbe nodes_. 92 | nodes_ is a list of the bridge types found. 93 | @todo: Find a way to pass the param file as an argument instead. Challenging to do with cog at build time. 94 | """ 95 | nodes_ = [] 96 | 97 | def __init__(self): 98 | """ Parse the param file from a hardcoded path. Save resulting model in nodes_ variable """ 99 | ## Trim subdirectories from path. Needed depending on where this module is called from. 100 | with open(os.path.join(os.getcwd(), 'config/interface_description.yaml')) as yamfile: 101 | params_ = yaml.load(yamfile, Loader = yaml.FullLoader) 102 | 103 | for node in params_: 104 | # Safe get ros__parameters 105 | if "ros__parameters" not in params_[node]: 106 | print(node, ": Improperly formatted ros2 param yaml, must contain field ros__parametrs!! Exiting") 107 | return 108 | rosparams = params_[node]['ros__parameters'] 109 | 110 | # Safe get msg_type rosparam 111 | if "msg_type" not in rosparams: 112 | print(node, ": msg_type not defined as a rosparam!! Exiting") 113 | return 114 | msg_type = rosparams['msg_type'] 115 | 116 | # Safe get grpc params 117 | if "grpc" not in rosparams or "address" not in rosparams['grpc']: 118 | print(node, ": grpc.address not defined under rosaparams!! Exiting") 119 | return 120 | grpc_address = rosparams['grpc']['address'] 121 | grpc_type = rosparams['grpc']['type'] if "type" in rosparams['grpc'] else "" 122 | 123 | # Safe get publisher params 124 | publishers = rosparams['publishers'] if "publishers" in rosparams else dict() 125 | if publishers is not None: 126 | pub_topics = publishers['topics'] if "topics" in publishers else [] 127 | pub_datapaths = publishers['datapaths'] if "datapaths" in publishers else [] 128 | pub_frequencies = publishers['frequencies'] if "frequencies" in publishers else [] 129 | if ((size(pub_topics) != size (pub_datapaths)) or (size(pub_topics) != size (pub_frequencies))): 130 | print(node, "PUBLISHER PARAMS NOT OF EQUAL SIZE!! Exiting") 131 | return 132 | else: 133 | pub_topics = pub_datapaths = pub_frequencies = [] 134 | 135 | # Safe get subscriber params 136 | subscribers = rosparams['subscribers'] if "subscribers" in rosparams else dict() 137 | if subscribers is not None: 138 | sub_topics = subscribers['topics'] if "topics" in subscribers else [] 139 | sub_datapaths = subscribers['datapaths'] if "datapaths" in subscribers else [] 140 | sub_frequencies = subscribers['frequencies'] if "frequencies" in subscribers else [] 141 | if ((size(sub_topics) != size (sub_datapaths)) or (size(sub_topics) != size (sub_frequencies))): 142 | print(node, "SUBSCRIBER PARAMS NOT OF EQUAL SIZE!! Exiting") 143 | return 144 | else: 145 | sub_topics = sub_datapaths = sub_frequencies = [] 146 | 147 | publishers_list = [] 148 | for x in range(size(pub_topics)): 149 | publishers_list.append(Port(pub_topics[x], pub_datapaths[x], pub_frequencies[x])) 150 | 151 | subscribers_list = [] 152 | for x in range(size(sub_topics)): 153 | subscribers_list.append(Port(sub_topics[x], sub_datapaths[x], sub_frequencies[x])) 154 | 155 | self.nodes_.append(BridgeType(node, 156 | msg_type, 157 | CommPort(grpc_address, grpc_type), 158 | publishers_list, 159 | subscribers_list)) 160 | 161 | if __name__ == "__main__": 162 | obj = ParamParser() 163 | node: BridgeType 164 | for node in obj.nodes_: 165 | print(node.node_name) 166 | print(" Msg type-", node.msg_type) 167 | print(" C++ resolved class-", node.msg_type.replace("/","::")) 168 | print(" C++ include header-", getSnakeCase(node.msg_type)+".hpp") 169 | print(" gRPC channel- ", node.grpc.address, node.grpc.type) 170 | print(" Ports-") 171 | for pub in node.publishers: 172 | print(" pub:", pub.topic, pub.datapath, pub.frequency) 173 | for sub in node.subscribers: 174 | print(" sub:", sub.topic, sub.datapath, sub.frequency) 175 | print("") 176 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/bridge_type.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 Fraunhofer IPA 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #ifndef PHOENIX_BRIDGE__BRIDGE_TYPE_HPP_ 18 | #define PHOENIX_BRIDGE__BRIDGE_TYPE_HPP_ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "phoenix_bridge/include_types.h" 28 | #include "phoenix_bridge/phoenix_comm.hpp" 29 | 30 | /** 31 | * @brief Class to handle creating a bridge per supported ROS msg type 32 | * We create one BridgeType for odom, one for twist, etc... 33 | * A given bridge type can have several "ports" under it, where a port is a publisher or a subscriber 34 | * This way we can have several pub/subs for odom, several pub/subs for twist etc.. 35 | * The number of ports is derived from config/interface_description.yaml 36 | */ 37 | template 38 | class BridgeType : public rclcpp::Node 39 | { 40 | public: 41 | explicit BridgeType(std::string node_name); 42 | void init(); 43 | 44 | private: 45 | PhoenixComm comm_; /// Communication layer object. Must be separately initialised. 46 | std::vector pub_topics_, pub_datapaths_; /// Contain captured parameters 47 | std::vector sub_topics_, sub_datapaths_; /// Contain captured parameters 48 | std::vector pub_freqs_, sub_freqs_; /// Contain captured parameters 49 | std::vector 50 | pub_timers_; /// Contain spawned publisher timers to hold them in scope 51 | std::vector::SharedPtr> 52 | pubs_; /// Contain spawned publishers to hold them in scope 53 | std::vector::SharedPtr> 54 | subs_; /// Contain spawned subscribers to hold them in scope 55 | 56 | bool getPortParams(); 57 | void spawnPublishers(); 58 | void spawnSubscribers(); 59 | }; 60 | 61 | /** 62 | * @brief Constructor to create node. Logical init decoupled to allow flexibility. 63 | * 64 | * @tparam T Template type parameter 65 | * @param node_name Name of the node 66 | */ 67 | template 68 | inline BridgeType::BridgeType(std::string node_name) : Node(node_name) 69 | { 70 | init(); 71 | } 72 | 73 | /** 74 | * @brief Read the rosparams to create ports and spawn pubs & subs 75 | */ 76 | template 77 | inline void BridgeType::init() 78 | { 79 | if (!this->getPortParams()) return; 80 | 81 | /// Initialise communication layer 82 | RCLCPP_INFO_STREAM( 83 | this->get_logger(), 84 | "Initialising gRCP channel @" << this->get_parameter("grpc.address").as_string()); 85 | comm_.init(this->get_parameter("grpc.address").as_string()); 86 | 87 | /// Spawn pubs 88 | this->spawnPublishers(); 89 | 90 | /// Spawn subs 91 | this->spawnSubscribers(); 92 | } 93 | 94 | /** 95 | * @brief Read params for this node. Param names are hardcoded so the param file should have a very specific structure. 96 | * @param param_name The fully resolved param path ex: "/odometry/publishers" 97 | * @param port_vector Save all the read param data into a vector of PortParams 98 | */ 99 | template 100 | inline bool BridgeType::getPortParams() 101 | { 102 | /// @todo Try catch everything 103 | this->declare_parameter("grpc.address", "unix:/run/plcnext/grpc.sock"); /// Channel address 104 | this->declare_parameter("grpc.type", ""); /// Unused 105 | this->declare_parameter("msg_type", ""); /// The type of the topic to create 106 | this->declare_parameter( 107 | "publishers.topics", std::vector()); /// Array of publisher topic names 108 | /// Array of corresponding instance paths in PLC GDS for the varaible to write to 109 | this->declare_parameter("publishers.datapaths", std::vector()); 110 | /// Array of corresponding publisher frequencies 111 | this->declare_parameter("publishers.frequencies", std::vector()); 112 | this->declare_parameter( 113 | "subscribers.topics", std::vector()); /// Array of subscriber topic names 114 | /// Array of corresponding instance paths in PLC GDS for the varaible to write to 115 | this->declare_parameter("subscribers.datapaths", std::vector()); 116 | this->declare_parameter("subscribers.frequencies", std::vector()); /// Unused 117 | 118 | pub_topics_ = this->get_parameter("publishers.topics").as_string_array(); 119 | pub_datapaths_ = this->get_parameter("publishers.datapaths").as_string_array(); 120 | pub_freqs_ = this->get_parameter("publishers.frequencies").as_integer_array(); 121 | 122 | sub_topics_ = this->get_parameter("subscribers.topics").as_string_array(); 123 | sub_datapaths_ = this->get_parameter("subscribers.datapaths").as_string_array(); 124 | sub_freqs_ = this->get_parameter("subscribers.frequencies").as_integer_array(); 125 | /// The sizes of all 3 publisher arrays should be equal, since the data corresponds per index 126 | if (!((pub_topics_.size() == pub_datapaths_.size()) && 127 | (pub_topics_.size() == pub_freqs_.size()))) { 128 | RCLCPP_ERROR_STREAM(this->get_logger(), "Publisher array sizes not equal!!"); 129 | rclcpp::shutdown(); 130 | } 131 | /// The sizes of all 3 subscriber arrays should be equal, since the data corresponds per index 132 | if (!((sub_topics_.size() == sub_datapaths_.size()) && 133 | (sub_topics_.size() == sub_freqs_.size()))) { 134 | RCLCPP_ERROR_STREAM(this->get_logger(), "Subscriber array sizes not equal!!"); 135 | rclcpp::shutdown(); 136 | } 137 | return true; 138 | } 139 | 140 | /** 141 | * @brief Spawn publishers based on the read rosparams for this type 142 | */ 143 | template 144 | inline void BridgeType::spawnPublishers() 145 | { 146 | for (size_t i = 0; i < pub_topics_.size(); i++) { 147 | RCLCPP_INFO( 148 | this->get_logger(), "Spawning publisher [%s, %s, %ld]", pub_topics_[i].c_str(), 149 | pub_datapaths_[i].c_str(), pub_freqs_[i]); 150 | 151 | typename rclcpp::Publisher::SharedPtr pub = this->create_publisher(pub_topics_[i], 1000); 152 | pubs_.push_back(pub); 153 | pub_timers_.push_back(this->create_wall_timer( 154 | std::chrono::milliseconds(1000 / pub_freqs_[i]), [this, i, pub]() -> void { 155 | T msg_rcvd; 156 | if (comm_.getFromPLC(pub_datapaths_[i], msg_rcvd)) { 157 | pub->publish(msg_rcvd); 158 | } else { 159 | RCLCPP_ERROR_STREAM_ONCE( 160 | this->get_logger(), 161 | pub_topics_[i] << " Failed to get data from PLC at " << pub_datapaths_[i]); 162 | } 163 | })); 164 | } 165 | } 166 | 167 | /** 168 | * @brief Spawn subscribers based on the captures rosparams for this type 169 | */ 170 | template 171 | inline void BridgeType::spawnSubscribers() 172 | { 173 | for (size_t i = 0; i < sub_topics_.size(); i++) { 174 | { 175 | RCLCPP_INFO( 176 | this->get_logger(), "Spawning subscriber [%s, %s, %ld]", sub_topics_[i].c_str(), 177 | sub_datapaths_[i].c_str(), sub_freqs_[i]); 178 | subs_.push_back(this->create_subscription( 179 | sub_topics_[i], 10, [this, i](const typename T::SharedPtr msg) -> void { 180 | if (!comm_.sendToPLC(sub_datapaths_[i], *msg.get())) 181 | RCLCPP_ERROR_STREAM_ONCE( 182 | this->get_logger(), 183 | sub_topics_[i] << " Failed to send data to PLC at " << sub_datapaths_[i]); 184 | })); 185 | } 186 | } 187 | } 188 | 189 | #endif // PHOENIX_BRIDGE__BRIDGE_TYPE_HPP_ 190 | -------------------------------------------------------------------------------- /phoenix_bridge/test/test_write_conversions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "phoenix_bridge/write_conversions.hpp" 6 | 7 | TEST(WriteConversionTests, TestStringMsg) 8 | { 9 | std::string test_data = "test_msg"; 10 | std::string test_instance_path = "test_path"; 11 | 12 | std_msgs::msg::String msg_to_test; 13 | msg_to_test.data = test_data; 14 | 15 | IDataAccessServiceWriteRequest request; 16 | ::Arp::Plc::Gds::Services::Grpc::WriteItem * grpc_object = request.add_data(); 17 | grpc_object->set_portname(test_instance_path); 18 | grpc_object->mutable_value()->set_typecode(::Arp::Type::Grpc::CoreType::CT_Struct); 19 | 20 | conversions::packWriteItem(grpc_object, msg_to_test); 21 | 22 | std::string debugstr; 23 | google::protobuf::TextFormat::PrintToString(*grpc_object, &debugstr); 24 | 25 | EXPECT_TRUE(debugstr.find("PortName: \"test_path\"") !=std::string::npos); 26 | /// Counted by first manually verifying debugstr 27 | EXPECT_EQ(static_cast(debugstr.find("StringValue: \"test_msg\"")), 121); // string.data 28 | } 29 | 30 | TEST(WriteConversionTests, TestDoubleMsg) 31 | { 32 | std::string test_instance_path = "test_path"; 33 | std_msgs::msg::Float64 msg_to_test; 34 | msg_to_test.data = 12345.09876; 35 | 36 | IDataAccessServiceWriteRequest request; 37 | ::Arp::Plc::Gds::Services::Grpc::WriteItem * grpc_object = request.add_data(); 38 | grpc_object->set_portname(test_instance_path); 39 | grpc_object->mutable_value()->set_typecode(::Arp::Type::Grpc::CoreType::CT_Struct); 40 | 41 | conversions::packWriteItem(grpc_object, msg_to_test); 42 | 43 | std::string debugstr; 44 | google::protobuf::TextFormat::PrintToString(*grpc_object, &debugstr); 45 | 46 | EXPECT_TRUE(debugstr.find("PortName: \"test_path\"") !=std::string::npos); 47 | /// Counted by first manually verifying debugstr 48 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 12345.09876")), 121); 49 | } 50 | 51 | TEST(WriteConversionTests, TestIntMsg) 52 | { 53 | std::string test_instance_path = "test_path"; 54 | std_msgs::msg::Int64 msg_to_test; 55 | msg_to_test.data = 1234509876; 56 | 57 | IDataAccessServiceWriteRequest request; 58 | ::Arp::Plc::Gds::Services::Grpc::WriteItem * grpc_object = request.add_data(); 59 | grpc_object->set_portname(test_instance_path); 60 | grpc_object->mutable_value()->set_typecode(::Arp::Type::Grpc::CoreType::CT_Int64); 61 | 62 | conversions::packWriteItem(grpc_object, msg_to_test); 63 | 64 | std::string debugstr; 65 | google::protobuf::TextFormat::PrintToString(*grpc_object, &debugstr); 66 | 67 | EXPECT_TRUE(debugstr.find("PortName: \"test_path\"") !=std::string::npos); 68 | /// Counted by first manually verifying debugstr 69 | EXPECT_EQ(static_cast(debugstr.find("Int64Value: 1234509876")), 120); 70 | } 71 | 72 | 73 | TEST(WriteConversionTests, TestTwistMsg) 74 | { 75 | std::string test_instance_path = "test_path"; 76 | 77 | geometry_msgs::msg::Twist msg_to_test; 78 | msg_to_test.linear.x = 1.11; 79 | msg_to_test.linear.y = 2.22; 80 | msg_to_test.linear.z = 3.33; 81 | msg_to_test.angular.x = 4.44; 82 | msg_to_test.angular.y = 5.55; 83 | msg_to_test.angular.z = 6.66; 84 | 85 | IDataAccessServiceWriteRequest request; 86 | ::Arp::Plc::Gds::Services::Grpc::WriteItem * grpc_object = request.add_data(); 87 | grpc_object->set_portname(test_instance_path); 88 | grpc_object->mutable_value()->set_typecode(::Arp::Type::Grpc::CoreType::CT_Struct); 89 | 90 | conversions::packWriteItem(grpc_object, msg_to_test); 91 | 92 | std::string debugstr; 93 | google::protobuf::TextFormat::PrintToString(*grpc_object, &debugstr); 94 | 95 | EXPECT_TRUE(debugstr.find("PortName: \"test_path\"") !=std::string::npos); 96 | /// Counted by first manually verifying debugstr 97 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 1.11")), 200); // linear.x 98 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 2.22")), 293); // linear.y 99 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 3.33")), 386); // linear.z 100 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 4.44")), 560); // angular.x 101 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 5.55")), 653); // angular.y 102 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 6.66")), 746); // angular.z 103 | } 104 | 105 | 106 | TEST(WriteConversionTests, TestHeaderMsg) 107 | { 108 | std::string test_instance_path = "test_path"; 109 | 110 | std_msgs::msg::Header msg_to_test; 111 | msg_to_test.stamp.sec = 111; 112 | msg_to_test.stamp.nanosec = 222; 113 | msg_to_test.frame_id = "frame_id"; 114 | 115 | IDataAccessServiceWriteRequest request; 116 | ::Arp::Plc::Gds::Services::Grpc::WriteItem * grpc_object = request.add_data(); 117 | grpc_object->set_portname(test_instance_path); 118 | grpc_object->mutable_value()->set_typecode(::Arp::Type::Grpc::CoreType::CT_Struct); 119 | 120 | conversions::packWriteItem(grpc_object, msg_to_test); 121 | 122 | std::string debugstr; 123 | google::protobuf::TextFormat::PrintToString(*grpc_object, &debugstr); 124 | 125 | EXPECT_TRUE(debugstr.find("PortName: \"test_path\"") !=std::string::npos); 126 | /// Counted by first manually verifying debugstr 127 | EXPECT_EQ(static_cast(debugstr.find("Int32Value: 111")) ,199); 128 | EXPECT_EQ(static_cast(debugstr.find("Uint32Value: 222")) , 290); 129 | EXPECT_EQ(static_cast(debugstr.find("StringValue: \"frame_id\"")), 384); 130 | } 131 | 132 | TEST(WriteConversionTests, TestOdomMsg) 133 | { 134 | std::string test_instance_path = "test_path"; 135 | 136 | nav_msgs::msg::Odometry msg_to_test; 137 | msg_to_test.header.stamp.sec = 111; 138 | msg_to_test.header.stamp.nanosec = 222; 139 | msg_to_test.header.frame_id = "header_frame_id"; 140 | msg_to_test.child_frame_id = "child_frame_id"; 141 | msg_to_test.pose.pose.position.x = 1.11; 142 | msg_to_test.pose.pose.position.y = 2.22; 143 | msg_to_test.pose.pose.position.z = 3.33; 144 | msg_to_test.pose.pose.orientation.x = 4.44; 145 | msg_to_test.pose.pose.orientation.y = 5.55; 146 | msg_to_test.pose.pose.orientation.z = 6.66; 147 | msg_to_test.pose.pose.orientation.w = 7.77; 148 | msg_to_test.pose.covariance.at(5) = 8.88; 149 | msg_to_test.twist.twist.linear.x = 9.99; 150 | msg_to_test.twist.twist.linear.y = 10.1010; 151 | msg_to_test.twist.twist.linear.z = 11.1111; 152 | msg_to_test.twist.twist.angular.x = 12.1212; 153 | msg_to_test.twist.twist.angular.y = 13.1313; 154 | msg_to_test.twist.twist.angular.z = 14.1414; 155 | msg_to_test.twist.covariance.at(7) = 15.1515; 156 | 157 | IDataAccessServiceWriteRequest request; 158 | ::Arp::Plc::Gds::Services::Grpc::WriteItem * grpc_object = request.add_data(); 159 | grpc_object->set_portname(test_instance_path); 160 | grpc_object->mutable_value()->set_typecode(::Arp::Type::Grpc::CoreType::CT_Struct); 161 | 162 | conversions::packWriteItem(grpc_object, msg_to_test); 163 | 164 | std::string debugstr; 165 | google::protobuf::TextFormat::PrintToString(*grpc_object, &debugstr); 166 | 167 | EXPECT_TRUE(debugstr.find("PortName: \"test_path\"") !=std::string::npos); 168 | /// Counted by first manually verifying debugstr 169 | EXPECT_EQ(static_cast(debugstr.find("Int32Value: 111")) ,290); 170 | EXPECT_EQ(static_cast(debugstr.find("Uint32Value: 222")) , 397); 171 | EXPECT_EQ(static_cast(debugstr.find("StringValue: \"header_frame_id\"")), 515); 172 | EXPECT_EQ(static_cast(debugstr.find("StringValue: \"child_frame_id\"")), 623); 173 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 1.11")),985); 174 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 2.22")), 1110); 175 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 3.33")), 1235); 176 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 4.44")), 1481); 177 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 5.55")), 1606); 178 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 6.66")), 1731); 179 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 7.77")), 1856); 180 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 8.88")), 2622); 181 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 9.99")), 6166); 182 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 10.101")), 6291); 183 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 11.11")), 6418); 184 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 12.1212")), 6667); 185 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 13.1313")), 6795); 186 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 14.1414")), 6923); 187 | EXPECT_EQ(static_cast(debugstr.find("DoubleValue: 15.1515")), 7902); 188 | 189 | } 190 | 191 | int main(int argc, char ** argv) 192 | { 193 | testing::InitGoogleTest(&argc, argv); 194 | return RUN_ALL_TESTS(); 195 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 ipa326 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /phoenix_bridge/phoenix_bridge/msg_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | ### 4 | ### Copyright 2022 Fraunhofer IPA 5 | ### 6 | ### Licensed under the Apache License, Version 2.0 (the "License"); 7 | ### you may not use this file except in compliance with the License. 8 | ### You may obtain a copy of the License at 9 | ### 10 | ### http:###www.apache.org/licenses/LICENSE-2.0 11 | ### 12 | ### Unless required by applicable law or agreed to in writing, software 13 | ### distributed under the License is distributed on an "AS IS" BASIS, 14 | ### WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | ### See the License for the specific language governing permissions and 16 | ### limitations under the License. 17 | ### 18 | 19 | import sys 20 | import argparse 21 | 22 | from pydoc import locate 23 | from numpy import size 24 | 25 | """ 26 | This script parses a ROS msg type to build an internal model that can easily be iterated through 27 | The functions offered by this script are designed to be used together with the neighbouring script param_parser.py 28 | Running this script directly from the base package directory like phoenix_bridge/msg_parser.py 29 | (after sourcing ROS) shows an example output of what cog could generate into include/phoenix_bridge/read\write_conversions.hpp 30 | """ 31 | 32 | def decompose_ros_msg_type(msg_type): 33 | """ 34 | Main entry point to the script to be used to get processed fields of a ros msg. 35 | 36 | @param msg_type: Any ROS msg type. The type class , not the msg object itself. 37 | 38 | @return fields: A list of processed fields, where a field is a 3-tuple which represents every line in a ros msg (name, depth, type) 39 | name - The name. Depth is appended to struct names to make them unique. Ex: twist_1, header_1, stamp_1 etc.. 40 | depth - In an embedded structure, how far down is this field embedded. Ex: Header.stamp.sec has depth 3. 41 | type - Type of the field. Can by a base type supported by ros, or a custom "STRCUT" which means it is a struct field and not variable. 42 | Ex: header has type STRUCT, header.stamp has type STRCUT & header.stamp.sec has type int32 43 | 44 | """ 45 | fields = [] 46 | msg_subtypes = parse_type(msg_type) 47 | max_depth = 0 48 | 49 | for subtype in msg_subtypes: 50 | if subtype[0] > max_depth: 51 | max_depth = subtype[0] 52 | 53 | gentype_name_parts = [""]*max_depth # The complete field name to be generated, represented as a list of its parts 54 | 55 | # Iterate through every subtype, and populate list of fields as either structs or varaibles 56 | for i in range(len(msg_subtypes)): 57 | lvl = msg_subtypes[i][0] # field level 58 | nam = msg_subtypes[i][1] # field name 59 | typ = msg_subtypes[i][2] # field type 60 | 61 | if typ.find("/") != -1: # If struct ## Empirical observation: all struct types have / in their typenames 62 | fields.append((nam+"_"+str(lvl), int(lvl), "STRUCT")) # add the struct, append lvl as unique identifier 63 | 64 | # Type correction, to resolve naming conflicts between ROS2 & grpc 65 | typ = "bool" if typ =="boolean" else typ 66 | 67 | gentype_name_parts[lvl-1] = nam # Replace the curent level name part 68 | for ind in range(lvl, max_depth, 1): # Flush the remaining name parts to the right 69 | gentype_name_parts[ind] = "" 70 | # Take the full field name 71 | if (i < len(msg_subtypes)-1 and msg_subtypes[i][0] >= msg_subtypes[i+1][0] and typ.find("/") == -1) \ 72 | or (i == len(msg_subtypes)-1 and typ.find("/") == -1): 73 | fields.append((get_type_name_from_parts(gentype_name_parts), int(lvl), typ)) # add the variable 74 | return fields 75 | 76 | """ Dictionary to hold type casting from cpp to grpc """ 77 | TypesDict = { 78 | "string" : "CT_String", 79 | "STRUCT" : "CT_Struct", 80 | "float64" : "CT_Real64", 81 | "float" : "CT_Real64", 82 | "double" : "CT_Real64", 83 | "uint8" : "CT_Uint8", 84 | "int32" : "CT_Int32", 85 | "uint32" : "CT_Uint32", 86 | "int64" : "CT_Int64", 87 | "bool" : "CT_Boolean", 88 | } 89 | 90 | def get_grpc_type(cpp_type:str): 91 | """ 92 | Get grpc type for given cpp type by looking up TypesDict defined internally 93 | 94 | @param cpp_type: Type name in cpp 95 | @return The name of type in grpc 96 | 97 | """ 98 | if cpp_type in TypesDict.keys(): 99 | return TypesDict[cpp_type] 100 | else: 101 | return cpp_type+"_CASTING_UNDEFINED" 102 | 103 | def parse_type(a_type:dict): 104 | """ 105 | Parse the ROS msg type directly to extract the underlying structure 106 | 107 | @param a_type: Any dict type. The class and not the object iteslf. 108 | @return List of the field subtypes within the dictionary 109 | """ 110 | subtypes = [] 111 | for item in a_type.__dict__.items(): 112 | if item[0] == "_fields_and_field_types": 113 | subtypes = get_subtypes(item[1], level=1) 114 | return subtypes 115 | 116 | def get_subtypes(type_dict:dict, level: int): 117 | """ 118 | Recursive function to get raw unprocessed subtypes within a dict 119 | 120 | @param type_dict: A dictionary type 121 | @param level: Used to track the level of recursion. Starting with 1 on top 122 | 123 | @return List of raw tuples which contain the level (i.e. depth), name and type of fields in a ROS msg 124 | """ 125 | subtypes = [] 126 | for key in type_dict: 127 | subtypes.append((level, key, type_dict[key])) 128 | if locate(to_import_format(type_dict[key])) is not None: 129 | for item in locate(to_import_format(type_dict[key])).__dict__.items(): 130 | if item[0] == "_fields_and_field_types": 131 | subtypes = subtypes + get_subtypes(item[1], level+1) 132 | return subtypes 133 | 134 | def to_import_format(a_type: str): 135 | """ Helper function. Converts ros msg format to python import format. Ex: std_msgs/Header to std_msgs.msg.Header """ 136 | parts = a_type.split("/") 137 | if size(parts) == 1: 138 | return a_type 139 | else: 140 | return parts[0]+".msg."+parts[1] 141 | 142 | def get_type_name_from_parts(parts): 143 | """ Helper function. Concatenate non empty strings in a list of strings to form a complete type name """ 144 | name = "" 145 | for part in parts: 146 | if part != "": 147 | name = name + "." + part 148 | return name[1:] 149 | 150 | def get_upper_struct(fields, level): 151 | """ Helper function. From the passed fields list, get the previous upper struct of required level """ 152 | for field in fields[::-1]: # reversed list to look backwards 153 | if field[1] == level and field[2] == "STRUCT": 154 | return field[0] 155 | 156 | def preview_write_codegen(): 157 | import param_parser 158 | params = param_parser.ParamParser() 159 | for node in params.nodes_: 160 | print("----------"+node.msg_type+"---------------") 161 | # locate does a lexical cast from a string to a type that can be found in sys.path 162 | fields = decompose_ros_msg_type(locate(node.msg_type.replace("/","."))) 163 | fields.insert(0,("grpc_object", 0, "STRUCT")) # Insert the received grpc_object as the uppermost base struct 164 | 165 | print("template <>") 166 | print("inline void packWriteObject<{}>(ObjectType &grpc_object, {}& unpack_to_data)" 167 | .format(node.msg_type.replace("/","::"), node.msg_type.replace("/","::"))) 168 | 169 | print("{") 170 | for ind in range(1, len(fields)): # skip the 0th element, which is the base struct 171 | nam = fields[ind][0] 172 | lvl = fields[ind][1] 173 | typ = fields[ind][2] 174 | 175 | var_name = nam.replace(".","_") 176 | grpc_typ = get_grpc_type(typ) 177 | upper = get_upper_struct(fields[:ind], lvl-1) # slice till current index, look for first higher struct 178 | 179 | # Line 1 of boilerplate code 180 | if upper == "grpc_object": 181 | print("::Arp::Type::Grpc::ObjectType* {} = {}->mutable_value()->mutable_structvalue()->add_structelements();" 182 | .format(var_name, upper)) 183 | else: 184 | print("::Arp::Type::Grpc::ObjectType* {} = {}->mutable_structvalue()->add_structelements();" 185 | .format(var_name, upper)) 186 | 187 | # Line 2 of boilerplate code 188 | # Assuming from empirical evidence that fixed length array types have '[' in the type names 189 | # and variable length arrays have 'sequence' in the type names 190 | if "[" in typ or "sequence" in typ: 191 | print("{}->set_typecode(::Arp::Type::Grpc::CoreType::CT_Array);".format(var_name)) 192 | else: 193 | print("{}->set_typecode(::Arp::Type::Grpc::CoreType::{});".format(var_name, grpc_typ)) 194 | 195 | # Line 3 of boilerplate code 196 | if typ != "STRUCT": 197 | # Assuming from empirical evidence that fixed length array types have '[' in the type names 198 | # and variable length arrays have 'sequence' in the type names 199 | if "[" in typ or "sequence" in typ: 200 | array_typ = typ.split('[')[0] if "[" in typ else typ.split('<')[1].split('>')[0] 201 | array_var = var_name+"_array" 202 | print("::Arp::Type::Grpc::TypeArray* {} = {}->mutable_arrayvalue();".format(array_var, var_name)) 203 | print("for (auto datum : data_to_pack.{})".format(nam)) 204 | print("{") 205 | print(" ObjectType* elem = {}->add_arrayelements();".format(array_var)) 206 | print(" elem->set_typecode(::Arp::Type::Grpc::CoreType::{});".format(get_grpc_type(array_typ))) 207 | print(" elem->set_{}value(datum);".format(array_typ)) 208 | print("}") 209 | else: 210 | print("{}->set_{}value(data_to_pack.{});".format(var_name, typ, nam)) 211 | print("") 212 | print("}") 213 | 214 | def preview_read_codegen(): 215 | import param_parser 216 | params = param_parser.ParamParser() 217 | for node in params.nodes_: 218 | print("----------"+node.msg_type+"---------------") 219 | # locate does a lexical cast from a string to a type that can be found in sys.path 220 | fields = decompose_ros_msg_type(locate(node.msg_type.replace("/","."))) 221 | fields.insert(0,("grpc_object", 0, "STRUCT")) # Insert the received grpc_object as the uppermost base struct 222 | 223 | parent_dict = {} 224 | 225 | print("template <>") 226 | print("inline void unpackReadObject<{}>(const ObjectType &grpc_object, {}& unpack_to_data)" 227 | .format(node.msg_type.replace("/","::"), 228 | node.msg_type.replace("/","::"))) 229 | 230 | print("{") 231 | for ind in range(1, len(fields)): # skip the 0th element, which is the base struct 232 | nam = fields[ind][0] 233 | lvl = fields[ind][1] 234 | typ = fields[ind][2] 235 | 236 | var_name = nam.replace(".","_") 237 | upper = get_upper_struct(fields[:ind], lvl-1) # slice till current index, look for first higher struct 238 | 239 | child_index = 0 240 | if not upper in parent_dict.keys(): 241 | parent_dict[upper] = 0 242 | else: 243 | child_index = parent_dict[upper] 244 | parent_dict[upper] += 1 245 | 246 | print("ObjectType {} = {}.structvalue().structelements({});".format(var_name, upper, child_index)) 247 | if typ != "STRUCT": 248 | # Assuming from empirical evidence that fixed length array types have '[' in the type names 249 | # and variable length arrays have 'sequence' in the type names 250 | if "[" in typ or "sequence" in typ: 251 | array_typ = typ.split('[')[0] if "[" in typ else typ.split('<')[1].split('>')[0] 252 | array_typ = "double" if array_typ=="float64" else array_typ 253 | print(" for (int i = 0; i < {}.arrayvalue().arrayelements_size(); i++)".format(var_name)) 254 | print(" {") 255 | print(" unpack_to_data.{}[i] = {}.arrayvalue().arrayelements(i).{}value();".format(nam,var_name, array_typ)) 256 | print(" }") 257 | else: 258 | print(" unpack_to_data.{} = {}.{}value();".format(nam, var_name, typ)) 259 | print("") 260 | print("}") 261 | 262 | if __name__ == '__main__': 263 | parser = argparse.ArgumentParser(description="Program designed to be used by cog codegen. Can be run manually to \ 264 | preview what the generated output would look like.") 265 | parser.add_argument("-t", "--type", 266 | help="Select which type of codegen output to print: [read, write] ", 267 | choices= ["read", "write"], default="read", type= str) 268 | args = parser.parse_args() 269 | 270 | if args.type == "read": 271 | preview_read_codegen() 272 | elif args.type == "write": 273 | preview_write_codegen() 274 | else: 275 | print("Wrong type chosen") 276 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/ServiceStubs/Plc/Gds/VariableInfo.pb.h: -------------------------------------------------------------------------------- 1 | // Generated by the protocol buffer compiler. DO NOT EDIT! 2 | // source: VariableInfo.proto 3 | 4 | #ifndef GOOGLE_PROTOBUF_INCLUDED_VariableInfo_2eproto 5 | #define GOOGLE_PROTOBUF_INCLUDED_VariableInfo_2eproto 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #if PROTOBUF_VERSION < 3014000 12 | #error This file was generated by a newer version of protoc which is 13 | #error incompatible with your Protocol Buffer headers. Please update 14 | #error your headers. 15 | #endif 16 | #if 3014000 < PROTOBUF_MIN_PROTOC_VERSION 17 | #error This file was generated by an older version of protoc which is 18 | #error incompatible with your Protocol Buffer headers. Please 19 | #error regenerate this file with a newer version of protoc. 20 | #endif 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include // IWYU pragma: export 32 | #include // IWYU pragma: export 33 | #include 34 | #include "DataType.pb.h" 35 | // @@protoc_insertion_point(includes) 36 | #include 37 | #define PROTOBUF_INTERNAL_EXPORT_VariableInfo_2eproto 38 | PROTOBUF_NAMESPACE_OPEN 39 | namespace internal { 40 | class AnyMetadata; 41 | } // namespace internal 42 | PROTOBUF_NAMESPACE_CLOSE 43 | 44 | // Internal implementation detail -- do not use these members. 45 | struct TableStruct_VariableInfo_2eproto { 46 | static const ::PROTOBUF_NAMESPACE_ID::internal::ParseTableField entries[] 47 | PROTOBUF_SECTION_VARIABLE(protodesc_cold); 48 | static const ::PROTOBUF_NAMESPACE_ID::internal::AuxiliaryParseTableField aux[] 49 | PROTOBUF_SECTION_VARIABLE(protodesc_cold); 50 | static const ::PROTOBUF_NAMESPACE_ID::internal::ParseTable schema[1] 51 | PROTOBUF_SECTION_VARIABLE(protodesc_cold); 52 | static const ::PROTOBUF_NAMESPACE_ID::internal::FieldMetadata field_metadata[]; 53 | static const ::PROTOBUF_NAMESPACE_ID::internal::SerializationTable serialization_table[]; 54 | static const ::PROTOBUF_NAMESPACE_ID::uint32 offsets[]; 55 | }; 56 | extern const ::PROTOBUF_NAMESPACE_ID::internal::DescriptorTable descriptor_table_VariableInfo_2eproto; 57 | namespace Arp { 58 | namespace Plc { 59 | namespace Gds { 60 | namespace Services { 61 | namespace Grpc { 62 | class VariableInfo; 63 | class VariableInfoDefaultTypeInternal; 64 | extern VariableInfoDefaultTypeInternal _VariableInfo_default_instance_; 65 | } // namespace Grpc 66 | } // namespace Services 67 | } // namespace Gds 68 | } // namespace Plc 69 | } // namespace Arp 70 | PROTOBUF_NAMESPACE_OPEN 71 | template<> ::Arp::Plc::Gds::Services::Grpc::VariableInfo* Arena::CreateMaybeMessage<::Arp::Plc::Gds::Services::Grpc::VariableInfo>(Arena*); 72 | PROTOBUF_NAMESPACE_CLOSE 73 | namespace Arp { 74 | namespace Plc { 75 | namespace Gds { 76 | namespace Services { 77 | namespace Grpc { 78 | 79 | // =================================================================== 80 | 81 | class VariableInfo PROTOBUF_FINAL : 82 | public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:Arp.Plc.Gds.Services.Grpc.VariableInfo) */ { 83 | public: 84 | inline VariableInfo() : VariableInfo(nullptr) {} 85 | virtual ~VariableInfo(); 86 | 87 | VariableInfo(const VariableInfo& from); 88 | VariableInfo(VariableInfo&& from) noexcept 89 | : VariableInfo() { 90 | *this = ::std::move(from); 91 | } 92 | 93 | inline VariableInfo& operator=(const VariableInfo& from) { 94 | CopyFrom(from); 95 | return *this; 96 | } 97 | inline VariableInfo& operator=(VariableInfo&& from) noexcept { 98 | if (GetArena() == from.GetArena()) { 99 | if (this != &from) InternalSwap(&from); 100 | } else { 101 | CopyFrom(from); 102 | } 103 | return *this; 104 | } 105 | 106 | static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { 107 | return GetDescriptor(); 108 | } 109 | static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { 110 | return GetMetadataStatic().descriptor; 111 | } 112 | static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { 113 | return GetMetadataStatic().reflection; 114 | } 115 | static const VariableInfo& default_instance(); 116 | 117 | static inline const VariableInfo* internal_default_instance() { 118 | return reinterpret_cast( 119 | &_VariableInfo_default_instance_); 120 | } 121 | static constexpr int kIndexInFileMessages = 122 | 0; 123 | 124 | friend void swap(VariableInfo& a, VariableInfo& b) { 125 | a.Swap(&b); 126 | } 127 | inline void Swap(VariableInfo* other) { 128 | if (other == this) return; 129 | if (GetArena() == other->GetArena()) { 130 | InternalSwap(other); 131 | } else { 132 | ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); 133 | } 134 | } 135 | void UnsafeArenaSwap(VariableInfo* other) { 136 | if (other == this) return; 137 | GOOGLE_DCHECK(GetArena() == other->GetArena()); 138 | InternalSwap(other); 139 | } 140 | 141 | // implements Message ---------------------------------------------- 142 | 143 | inline VariableInfo* New() const final { 144 | return CreateMaybeMessage(nullptr); 145 | } 146 | 147 | VariableInfo* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final { 148 | return CreateMaybeMessage(arena); 149 | } 150 | void CopyFrom(const ::PROTOBUF_NAMESPACE_ID::Message& from) final; 151 | void MergeFrom(const ::PROTOBUF_NAMESPACE_ID::Message& from) final; 152 | void CopyFrom(const VariableInfo& from); 153 | void MergeFrom(const VariableInfo& from); 154 | PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; 155 | bool IsInitialized() const final; 156 | 157 | size_t ByteSizeLong() const final; 158 | const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; 159 | ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize( 160 | ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; 161 | int GetCachedSize() const final { return _cached_size_.Get(); } 162 | 163 | private: 164 | inline void SharedCtor(); 165 | inline void SharedDtor(); 166 | void SetCachedSize(int size) const final; 167 | void InternalSwap(VariableInfo* other); 168 | friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; 169 | static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { 170 | return "Arp.Plc.Gds.Services.Grpc.VariableInfo"; 171 | } 172 | protected: 173 | explicit VariableInfo(::PROTOBUF_NAMESPACE_ID::Arena* arena); 174 | private: 175 | static void ArenaDtor(void* object); 176 | inline void RegisterArenaDtor(::PROTOBUF_NAMESPACE_ID::Arena* arena); 177 | public: 178 | 179 | ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; 180 | private: 181 | static ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadataStatic() { 182 | ::PROTOBUF_NAMESPACE_ID::internal::AssignDescriptors(&::descriptor_table_VariableInfo_2eproto); 183 | return ::descriptor_table_VariableInfo_2eproto.file_level_metadata[kIndexInFileMessages]; 184 | } 185 | 186 | public: 187 | 188 | // nested types ---------------------------------------------------- 189 | 190 | // accessors ------------------------------------------------------- 191 | 192 | enum : int { 193 | kNameFieldNumber = 1, 194 | kTypeFieldNumber = 2, 195 | }; 196 | // string Name = 1; 197 | void clear_name(); 198 | const std::string& name() const; 199 | void set_name(const std::string& value); 200 | void set_name(std::string&& value); 201 | void set_name(const char* value); 202 | void set_name(const char* value, size_t size); 203 | std::string* mutable_name(); 204 | std::string* release_name(); 205 | void set_allocated_name(std::string* name); 206 | private: 207 | const std::string& _internal_name() const; 208 | void _internal_set_name(const std::string& value); 209 | std::string* _internal_mutable_name(); 210 | public: 211 | 212 | // .Arp.Plc.Grpc.DataType Type = 2; 213 | void clear_type(); 214 | ::Arp::Plc::Grpc::DataType type() const; 215 | void set_type(::Arp::Plc::Grpc::DataType value); 216 | private: 217 | ::Arp::Plc::Grpc::DataType _internal_type() const; 218 | void _internal_set_type(::Arp::Plc::Grpc::DataType value); 219 | public: 220 | 221 | // @@protoc_insertion_point(class_scope:Arp.Plc.Gds.Services.Grpc.VariableInfo) 222 | private: 223 | class _Internal; 224 | 225 | template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; 226 | typedef void InternalArenaConstructable_; 227 | typedef void DestructorSkippable_; 228 | ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr name_; 229 | int type_; 230 | mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; 231 | friend struct ::TableStruct_VariableInfo_2eproto; 232 | }; 233 | // =================================================================== 234 | 235 | 236 | // =================================================================== 237 | 238 | #ifdef __GNUC__ 239 | #pragma GCC diagnostic push 240 | #pragma GCC diagnostic ignored "-Wstrict-aliasing" 241 | #endif // __GNUC__ 242 | // VariableInfo 243 | 244 | // string Name = 1; 245 | inline void VariableInfo::clear_name() { 246 | name_.ClearToEmpty(); 247 | } 248 | inline const std::string& VariableInfo::name() const { 249 | // @@protoc_insertion_point(field_get:Arp.Plc.Gds.Services.Grpc.VariableInfo.Name) 250 | return _internal_name(); 251 | } 252 | inline void VariableInfo::set_name(const std::string& value) { 253 | _internal_set_name(value); 254 | // @@protoc_insertion_point(field_set:Arp.Plc.Gds.Services.Grpc.VariableInfo.Name) 255 | } 256 | inline std::string* VariableInfo::mutable_name() { 257 | // @@protoc_insertion_point(field_mutable:Arp.Plc.Gds.Services.Grpc.VariableInfo.Name) 258 | return _internal_mutable_name(); 259 | } 260 | inline const std::string& VariableInfo::_internal_name() const { 261 | return name_.Get(); 262 | } 263 | inline void VariableInfo::_internal_set_name(const std::string& value) { 264 | 265 | name_.Set(::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::EmptyDefault{}, value, GetArena()); 266 | } 267 | inline void VariableInfo::set_name(std::string&& value) { 268 | 269 | name_.Set( 270 | ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::EmptyDefault{}, ::std::move(value), GetArena()); 271 | // @@protoc_insertion_point(field_set_rvalue:Arp.Plc.Gds.Services.Grpc.VariableInfo.Name) 272 | } 273 | inline void VariableInfo::set_name(const char* value) { 274 | GOOGLE_DCHECK(value != nullptr); 275 | 276 | name_.Set(::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::EmptyDefault{}, ::std::string(value), GetArena()); 277 | // @@protoc_insertion_point(field_set_char:Arp.Plc.Gds.Services.Grpc.VariableInfo.Name) 278 | } 279 | inline void VariableInfo::set_name(const char* value, 280 | size_t size) { 281 | 282 | name_.Set(::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::EmptyDefault{}, ::std::string( 283 | reinterpret_cast(value), size), GetArena()); 284 | // @@protoc_insertion_point(field_set_pointer:Arp.Plc.Gds.Services.Grpc.VariableInfo.Name) 285 | } 286 | inline std::string* VariableInfo::_internal_mutable_name() { 287 | 288 | return name_.Mutable(::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::EmptyDefault{}, GetArena()); 289 | } 290 | inline std::string* VariableInfo::release_name() { 291 | // @@protoc_insertion_point(field_release:Arp.Plc.Gds.Services.Grpc.VariableInfo.Name) 292 | return name_.Release(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), GetArena()); 293 | } 294 | inline void VariableInfo::set_allocated_name(std::string* name) { 295 | if (name != nullptr) { 296 | 297 | } else { 298 | 299 | } 300 | name_.SetAllocated(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited(), name, 301 | GetArena()); 302 | // @@protoc_insertion_point(field_set_allocated:Arp.Plc.Gds.Services.Grpc.VariableInfo.Name) 303 | } 304 | 305 | // .Arp.Plc.Grpc.DataType Type = 2; 306 | inline void VariableInfo::clear_type() { 307 | type_ = 0; 308 | } 309 | inline ::Arp::Plc::Grpc::DataType VariableInfo::_internal_type() const { 310 | return static_cast< ::Arp::Plc::Grpc::DataType >(type_); 311 | } 312 | inline ::Arp::Plc::Grpc::DataType VariableInfo::type() const { 313 | // @@protoc_insertion_point(field_get:Arp.Plc.Gds.Services.Grpc.VariableInfo.Type) 314 | return _internal_type(); 315 | } 316 | inline void VariableInfo::_internal_set_type(::Arp::Plc::Grpc::DataType value) { 317 | 318 | type_ = value; 319 | } 320 | inline void VariableInfo::set_type(::Arp::Plc::Grpc::DataType value) { 321 | _internal_set_type(value); 322 | // @@protoc_insertion_point(field_set:Arp.Plc.Gds.Services.Grpc.VariableInfo.Type) 323 | } 324 | 325 | #ifdef __GNUC__ 326 | #pragma GCC diagnostic pop 327 | #endif // __GNUC__ 328 | 329 | // @@protoc_insertion_point(namespace_scope) 330 | 331 | } // namespace Grpc 332 | } // namespace Services 333 | } // namespace Gds 334 | } // namespace Plc 335 | } // namespace Arp 336 | 337 | // @@protoc_insertion_point(global_scope) 338 | 339 | #include 340 | #endif // GOOGLE_PROTOBUF_INCLUDED_GOOGLE_PROTOBUF_INCLUDED_VariableInfo_2eproto 341 | -------------------------------------------------------------------------------- /phoenix_bridge/include/phoenix_bridge/read_conversions.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2022 Fraunhofer IPA 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | #ifndef READ_CONVERSIONS_HPP 18 | #define READ_CONVERSIONS_HPP 19 | 20 | #include "phoenix_bridge/include_types.h" 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "phoenix_bridge/ServiceStubs/Plc/Gds/IDataAccessService.grpc.pb.h" 30 | #include "phoenix_bridge/ServiceStubs/ArpTypes.grpc.pb.h" 31 | 32 | using grpc::Channel; 33 | using grpc::ClientContext; 34 | using grpc::ClientReader; 35 | using grpc::Status; 36 | 37 | using Arp::Type::Grpc::ObjectType; 38 | 39 | using Arp::Plc::Gds::Services::Grpc::IDataAccessService; 40 | using Arp::Plc::Gds::Services::Grpc::IDataAccessServiceReadRequest; 41 | using Arp::Plc::Gds::Services::Grpc::IDataAccessServiceReadResponse; 42 | using Arp::Plc::Gds::Services::Grpc::IDataAccessServiceReadSingleRequest; 43 | using Arp::Plc::Gds::Services::Grpc::IDataAccessServiceReadSingleResponse; 44 | using Arp::Plc::Gds::Services::Grpc::IDataAccessServiceWriteRequest; 45 | using Arp::Plc::Gds::Services::Grpc::IDataAccessServiceWriteResponse; 46 | using Arp::Plc::Gds::Services::Grpc::IDataAccessServiceWriteSingleRequest; 47 | using Arp::Plc::Gds::Services::Grpc::IDataAccessServiceWriteSingleResponse; 48 | 49 | /** 50 | * @brief Provides templated functions to unpackpack data from grpc::ObjectType, which should be extracted from a 51 | * gRPC PLC read service call, into ros msg (or any basic type) 52 | * The templated function specializations are generated by cog at build time for each of the types specified in the 53 | * parameters file. 54 | */ 55 | namespace conversions 56 | { 57 | /** 58 | * @brief Base template function to unpack data from a ReadResponse object 59 | * Implement template specializations for each type which has to be handled. 60 | * If type specilization not implemented but invoked, prints this error msg (@todo: and also kill node) 61 | * @param grpc_object grpc::ObjectType, extracted from the read response object, from which data needs to be unpacked 62 | * @param unpack_to_data The ros msg data variable into which the read resposne has to be unpacked. Can also be base types. 63 | */ 64 | template inline 65 | void unpackReadObject(const ObjectType &grpc_object, T& unpack_to_data) 66 | { 67 | (void) unpack_to_data; 68 | (void) grpc_object; 69 | RCLCPP_ERROR_STREAM(rclcpp::get_logger("conversions"), 70 | "Conversion to type " << typeid(unpack_to_data).name() << " not implemented!!"); 71 | } 72 | 73 | /** 74 | * @brief Bool specialization 75 | */ 76 | template <> 77 | inline void unpackReadObject(const ObjectType &grpc_object, bool& unpack_to_data) 78 | { 79 | unpack_to_data = grpc_object.boolvalue(); 80 | } 81 | 82 | /** 83 | * @brief Double specialization 84 | */ 85 | template <> 86 | inline void unpackReadObject(const ObjectType &grpc_object, double& unpack_to_data) 87 | { 88 | unpack_to_data = grpc_object.doublevalue(); 89 | } 90 | 91 | /// 92 | /// The following section is generated by cog at build time. Manual edits will be lost when rebuilt. 93 | /// 94 | 95 | /*[[[cog 96 | import cog 97 | import sys 98 | import os 99 | 100 | sys.path.append(os.getcwd()) # Necessary when colcon build invokes this script 101 | 102 | from pydoc import locate 103 | from phoenix_bridge.param_parser import ParamParser 104 | from phoenix_bridge.msg_parser import decompose_ros_msg_type, get_grpc_type, get_upper_struct 105 | 106 | params = ParamParser() 107 | for node in params.nodes_: 108 | fields = decompose_ros_msg_type(locate(node.msg_type.replace("/","."))) 109 | fields.insert(0,("grpc_object", 0, "STRUCT")) # Insert the received grpc_object as the uppermost base struct 110 | parent_dict = {} 111 | 112 | cog.outl("template <>") 113 | cog.outl("inline void unpackReadObject<{}>(const ObjectType &grpc_object, {}& unpack_to_data)" 114 | .format(node.msg_type.replace("/","::"), node.msg_type.replace("/","::"))) 115 | 116 | cog.outl("{") 117 | for ind in range(1, len(fields)): # skip the 0th element, which is the base struct 118 | nam = fields[ind][0] 119 | lvl = fields[ind][1] 120 | typ = fields[ind][2] 121 | 122 | var_name = fields[ind][0].replace(".","_") 123 | grpc_typ = get_grpc_type(fields[ind][2]) 124 | upper = get_upper_struct(fields[:ind], lvl-1) # slice till current index, look for first higher struct 125 | 126 | child_index = 0 127 | if not upper in parent_dict.keys(): 128 | parent_dict[upper] = 0 129 | else: 130 | child_index = parent_dict[upper] 131 | parent_dict[upper] += 1 132 | 133 | cog.outl(" ObjectType {} = {}.structvalue().structelements({});".format(var_name, upper, child_index)) 134 | if typ != "STRUCT": 135 | # Assuming from empirical evidence that fixed length array types have '[' in the type names 136 | # and variable length arrays have 'sequence' in the type names 137 | if "[" in typ or "sequence" in typ: 138 | array_typ = typ.split('[')[0] if "[" in typ else typ.split('<')[1].split('>')[0] 139 | array_typ = "double" if array_typ=="float64" else array_typ 140 | cog.outl(" for (int i = 0; i < {}.arrayvalue().arrayelements_size(); i++)".format(var_name)) 141 | cog.outl(" {") 142 | cog.outl(" unpack_to_data.{}[i] = {}.arrayvalue().arrayelements(i).{}value();".format(nam,var_name, array_typ)) 143 | cog.outl(" }") 144 | else: 145 | cog.outl(" unpack_to_data.{} = {}.{}value();".format(nam, var_name, typ)) 146 | cog.outl("") 147 | 148 | cog.outl("}") 149 | cog.outl("") 150 | ]]]*/ 151 | template <> 152 | inline void unpackReadObject(const ObjectType &grpc_object, nav_msgs::msg::Odometry& unpack_to_data) 153 | { 154 | ObjectType header_1 = grpc_object.structvalue().structelements(0); 155 | 156 | ObjectType stamp_2 = header_1.structvalue().structelements(0); 157 | 158 | ObjectType header_stamp_sec = stamp_2.structvalue().structelements(0); 159 | unpack_to_data.header.stamp.sec = header_stamp_sec.int32value(); 160 | 161 | ObjectType header_stamp_nanosec = stamp_2.structvalue().structelements(1); 162 | unpack_to_data.header.stamp.nanosec = header_stamp_nanosec.uint32value(); 163 | 164 | ObjectType header_frame_id = header_1.structvalue().structelements(1); 165 | unpack_to_data.header.frame_id = header_frame_id.stringvalue(); 166 | 167 | ObjectType child_frame_id = grpc_object.structvalue().structelements(1); 168 | unpack_to_data.child_frame_id = child_frame_id.stringvalue(); 169 | 170 | ObjectType pose_1 = grpc_object.structvalue().structelements(2); 171 | 172 | ObjectType pose_2 = pose_1.structvalue().structelements(0); 173 | 174 | ObjectType position_3 = pose_2.structvalue().structelements(0); 175 | 176 | ObjectType pose_pose_position_x = position_3.structvalue().structelements(0); 177 | unpack_to_data.pose.pose.position.x = pose_pose_position_x.doublevalue(); 178 | 179 | ObjectType pose_pose_position_y = position_3.structvalue().structelements(1); 180 | unpack_to_data.pose.pose.position.y = pose_pose_position_y.doublevalue(); 181 | 182 | ObjectType pose_pose_position_z = position_3.structvalue().structelements(2); 183 | unpack_to_data.pose.pose.position.z = pose_pose_position_z.doublevalue(); 184 | 185 | ObjectType orientation_3 = pose_2.structvalue().structelements(1); 186 | 187 | ObjectType pose_pose_orientation_x = orientation_3.structvalue().structelements(0); 188 | unpack_to_data.pose.pose.orientation.x = pose_pose_orientation_x.doublevalue(); 189 | 190 | ObjectType pose_pose_orientation_y = orientation_3.structvalue().structelements(1); 191 | unpack_to_data.pose.pose.orientation.y = pose_pose_orientation_y.doublevalue(); 192 | 193 | ObjectType pose_pose_orientation_z = orientation_3.structvalue().structelements(2); 194 | unpack_to_data.pose.pose.orientation.z = pose_pose_orientation_z.doublevalue(); 195 | 196 | ObjectType pose_pose_orientation_w = orientation_3.structvalue().structelements(3); 197 | unpack_to_data.pose.pose.orientation.w = pose_pose_orientation_w.doublevalue(); 198 | 199 | ObjectType pose_covariance = pose_1.structvalue().structelements(1); 200 | for (int i = 0; i < pose_covariance.arrayvalue().arrayelements_size(); i++) 201 | { 202 | unpack_to_data.pose.covariance[i] = pose_covariance.arrayvalue().arrayelements(i).doublevalue(); 203 | } 204 | 205 | ObjectType twist_1 = grpc_object.structvalue().structelements(3); 206 | 207 | ObjectType twist_2 = twist_1.structvalue().structelements(0); 208 | 209 | ObjectType linear_3 = twist_2.structvalue().structelements(0); 210 | 211 | ObjectType twist_twist_linear_x = linear_3.structvalue().structelements(0); 212 | unpack_to_data.twist.twist.linear.x = twist_twist_linear_x.doublevalue(); 213 | 214 | ObjectType twist_twist_linear_y = linear_3.structvalue().structelements(1); 215 | unpack_to_data.twist.twist.linear.y = twist_twist_linear_y.doublevalue(); 216 | 217 | ObjectType twist_twist_linear_z = linear_3.structvalue().structelements(2); 218 | unpack_to_data.twist.twist.linear.z = twist_twist_linear_z.doublevalue(); 219 | 220 | ObjectType angular_3 = twist_2.structvalue().structelements(1); 221 | 222 | ObjectType twist_twist_angular_x = angular_3.structvalue().structelements(0); 223 | unpack_to_data.twist.twist.angular.x = twist_twist_angular_x.doublevalue(); 224 | 225 | ObjectType twist_twist_angular_y = angular_3.structvalue().structelements(1); 226 | unpack_to_data.twist.twist.angular.y = twist_twist_angular_y.doublevalue(); 227 | 228 | ObjectType twist_twist_angular_z = angular_3.structvalue().structelements(2); 229 | unpack_to_data.twist.twist.angular.z = twist_twist_angular_z.doublevalue(); 230 | 231 | ObjectType twist_covariance = twist_1.structvalue().structelements(1); 232 | for (int i = 0; i < twist_covariance.arrayvalue().arrayelements_size(); i++) 233 | { 234 | unpack_to_data.twist.covariance[i] = twist_covariance.arrayvalue().arrayelements(i).doublevalue(); 235 | } 236 | 237 | } 238 | 239 | template <> 240 | inline void unpackReadObject(const ObjectType &grpc_object, geometry_msgs::msg::Twist& unpack_to_data) 241 | { 242 | ObjectType linear_1 = grpc_object.structvalue().structelements(0); 243 | 244 | ObjectType linear_x = linear_1.structvalue().structelements(0); 245 | unpack_to_data.linear.x = linear_x.doublevalue(); 246 | 247 | ObjectType linear_y = linear_1.structvalue().structelements(1); 248 | unpack_to_data.linear.y = linear_y.doublevalue(); 249 | 250 | ObjectType linear_z = linear_1.structvalue().structelements(2); 251 | unpack_to_data.linear.z = linear_z.doublevalue(); 252 | 253 | ObjectType angular_1 = grpc_object.structvalue().structelements(1); 254 | 255 | ObjectType angular_x = angular_1.structvalue().structelements(0); 256 | unpack_to_data.angular.x = angular_x.doublevalue(); 257 | 258 | ObjectType angular_y = angular_1.structvalue().structelements(1); 259 | unpack_to_data.angular.y = angular_y.doublevalue(); 260 | 261 | ObjectType angular_z = angular_1.structvalue().structelements(2); 262 | unpack_to_data.angular.z = angular_z.doublevalue(); 263 | 264 | } 265 | 266 | template <> 267 | inline void unpackReadObject(const ObjectType &grpc_object, std_msgs::msg::String& unpack_to_data) 268 | { 269 | ObjectType data = grpc_object.structvalue().structelements(0); 270 | unpack_to_data.data = data.stringvalue(); 271 | 272 | } 273 | 274 | template <> 275 | inline void unpackReadObject(const ObjectType &grpc_object, std_msgs::msg::Float64& unpack_to_data) 276 | { 277 | ObjectType data = grpc_object.structvalue().structelements(0); 278 | unpack_to_data.data = data.doublevalue(); 279 | 280 | } 281 | 282 | template <> 283 | inline void unpackReadObject(const ObjectType &grpc_object, std_msgs::msg::Int64& unpack_to_data) 284 | { 285 | ObjectType data = grpc_object.structvalue().structelements(0); 286 | unpack_to_data.data = data.int64value(); 287 | 288 | } 289 | 290 | template <> 291 | inline void unpackReadObject(const ObjectType &grpc_object, std_msgs::msg::Header& unpack_to_data) 292 | { 293 | ObjectType stamp_1 = grpc_object.structvalue().structelements(0); 294 | 295 | ObjectType stamp_sec = stamp_1.structvalue().structelements(0); 296 | unpack_to_data.stamp.sec = stamp_sec.int32value(); 297 | 298 | ObjectType stamp_nanosec = stamp_1.structvalue().structelements(1); 299 | unpack_to_data.stamp.nanosec = stamp_nanosec.uint32value(); 300 | 301 | ObjectType frame_id = grpc_object.structvalue().structelements(1); 302 | unpack_to_data.frame_id = frame_id.stringvalue(); 303 | 304 | } 305 | 306 | //[[[end]]] 307 | 308 | /// 309 | /// The above section is generated by cog at build time. Manual edits will be lost when rebuilt. 310 | /// 311 | } 312 | 313 | #endif // READ_CONVERSIONS_HPP 314 | --------------------------------------------------------------------------------