diff --git a/.github/workflows/nexus_integration_tests.yaml b/.github/workflows/nexus_integration_tests.yaml index 72a8da2d..f0089dcb 100644 --- a/.github/workflows/nexus_integration_tests.yaml +++ b/.github/workflows/nexus_integration_tests.yaml @@ -39,4 +39,17 @@ jobs: - name: Test - Unit Tests run: . ./install/setup.bash && RMW_IMPLEMENTATION=rmw_cyclonedds_cpp /ros_entrypoint.sh colcon test --packages-select nexus_motion_planner --event-handlers=console_direct+ - name: Test - Integration test - run: . ./install/setup.bash && cd nexus_demos && RMW_IMPLEMENTATION=rmw_cyclonedds_cpp /ros_entrypoint.sh python3 -m unittest + run: | + . ./install/setup.bash + cd nexus_demos + RMW_IMPLEMENTATION=rmw_zenoh_cpp /ros_entrypoint.sh python3 -m unittest \ + test_invalid_place_on_conveyor.py \ + test_parallel_duplicated_wo.py \ + test_parallel_wo.py \ + test_pick_and_place.py + - name: Test - Integration test with RMF + run: | + . ./install/setup.bash + cd nexus_demos + RMW_IMPLEMENTATION=rmw_zenoh_cpp /ros_entrypoint.sh python3 -m unittest \ + test_pick_and_place_rmf.py diff --git a/.github/workflows/style.yaml b/.github/workflows/style.yaml index 07b3bdb3..1e2baba3 100644 --- a/.github/workflows/style.yaml +++ b/.github/workflows/style.yaml @@ -22,10 +22,9 @@ jobs: # run: | # sudo apt update && sudo apt install -y ros-jazzy-rmf-utils # /ros_entrypoint.sh ament_uncrustify -c /opt/ros/jazzy/share/rmf_utils/rmf_code_style.cfg . --language C++ --exclude nexus_endpoints/nexus_endpoints.hpp - - name: pycodestyle + - name: deps run: | - sudo apt update && sudo apt install -y pycodestyle curl - pycodestyle nexus_network_configuration/ + sudo apt update && sudo apt install -y curl - name: Setup Rust uses: dtolnay/rust-toolchain@1.75 with: diff --git a/README.md b/README.md index 3e71c8d7..892e58a0 100644 --- a/README.md +++ b/README.md @@ -65,12 +65,6 @@ The framework to register capabilities and map them to processes that can be per At present, behavior trees can be viewed and modified using [Groot](https://github.com/BehaviorTree/Groot). Once `Groot` is launched, click "Load palette from file" and select [nexus_tree_nodes.xml](./nexus_tree_nodes.xml). Then any of the configured BTs can be loaded via the "Load Tree" button. A current limitation of this approach is the need to manually update the palette file when the plugins loaded by a task capability changes. In the future, the goals is to more closely couple the generation of this file and the skill plugins the orchestrators are capable of loading. -### Generating Zenoh bridge configurations - -The script in `nexus_network_configuration` helps to simplify configuration of Zenoh bridges for multiple machines. The Zenoh bridge files are generated from [NEXUS Network Configuration](nexus_demos/config/zenoh/nexus_network_config.yaml) and [nexus_endpoints.redf.yaml](./nexus_endpoints.redf.yaml). After configuring the [NEXUS Network Configuration](nexus_demos/config/zenoh/nexus_network_config.yaml), you can run `ros2 run nexus_network_configuration nexus_network_configuration -n -r -o ` to generate the Zenoh bridges. - -Further detailed instructions on running the Zenoh bridges with said configurations are in the [package README](nexus_network_configuration/README.md) - ## Demos ![](./docs/media/nexus_demo.png) diff --git a/nexus_calibration/CMakeLists.txt b/nexus_calibration/CMakeLists.txt index 760e9277..910fbb19 100644 --- a/nexus_calibration/CMakeLists.txt +++ b/nexus_calibration/CMakeLists.txt @@ -39,7 +39,7 @@ target_compile_features(nexus_calibration_component INTERFACE cxx_std_17) rclcpp_components_register_node(nexus_calibration_component PLUGIN "nexus::CalibrationNode" EXECUTABLE nexus_calibration_node - EXECUTOR SingleThreadedExecutor) + EXECUTOR EventsExecutor) #=============================================================================== diff --git a/nexus_demos/CMakeLists.txt b/nexus_demos/CMakeLists.txt index d81cb963..e381b4f4 100644 --- a/nexus_demos/CMakeLists.txt +++ b/nexus_demos/CMakeLists.txt @@ -32,7 +32,7 @@ target_compile_features(mock_gripper_component INTERFACE cxx_std_17) rclcpp_components_register_node(mock_gripper_component PLUGIN "nexus_demos::MockGripper" EXECUTABLE mock_gripper - EXECUTOR SingleThreadedExecutor) + EXECUTOR EventsExecutor) install( TARGETS mock_gripper_component EXPORT export_${PROJECT_NAME} @@ -59,7 +59,7 @@ target_compile_features(mock_detector_component INTERFACE cxx_std_17) rclcpp_components_register_node(mock_detector_component PLUGIN "nexus_demos::MockDetector" EXECUTABLE mock_detector - EXECUTOR SingleThreadedExecutor) + EXECUTOR EventsExecutor) install( TARGETS mock_detector_component EXPORT export_${PROJECT_NAME} @@ -80,7 +80,7 @@ target_link_libraries(mock_emergency_alarm_component rclcpp_components_register_node(mock_emergency_alarm_component PLUGIN "MockEmergencyAlarm" EXECUTABLE mock_emergency_alarm - EXECUTOR SingleThreadedExecutor) + EXECUTOR EventsExecutor) install( TARGETS mock_emergency_alarm_component EXPORT export_${PROJECT_NAME} @@ -110,7 +110,7 @@ target_compile_features(mock_printer INTERFACE cxx_std_17) rclcpp_components_register_node(mock_printer PLUGIN "MockPrinter" EXECUTABLE mock_printer_node - EXECUTOR SingleThreadedExecutor) + EXECUTOR EventsExecutor) install( TARGETS @@ -125,19 +125,8 @@ install( add_test(NAME all_tests COMMAND python3 -m unittest WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) ############################################################################### -# Generate zenoh bridge configuration files from a NEXUS network configuration file -set(nexus_network_cfg_path ${CMAKE_CURRENT_SOURCE_DIR}/config/zenoh/nexus_network_config.yaml) -set(zenoh_cfg_output_dir ${CMAKE_CURRENT_BINARY_DIR}/config/zenoh) - -# Invoke the nexus_network_configuration script and generate config json within the build directory. -add_custom_target(generate_zenoh_bridge_configs ALL - COMMAND ros2 run nexus_network_configuration nexus_network_configuration -n ${nexus_network_cfg_path} -o ${zenoh_cfg_output_dir} -) install(DIRECTORY launch config rviz scripts DESTINATION share/${PROJECT_NAME}) -# Install the zenoh config directory containing generated configs. -message("zenoh_cfg_output_dir: " ${zenoh_cfg_output_dir}) -install(DIRECTORY ${zenoh_cfg_output_dir} DESTINATION share/${PROJECT_NAME}/config/) if(BUILD_TESTING) find_package(rmf_utils REQUIRED) diff --git a/nexus_demos/README.md b/nexus_demos/README.md index 32e6c343..cfc315dc 100644 --- a/nexus_demos/README.md +++ b/nexus_demos/README.md @@ -3,63 +3,72 @@ ![](../docs/media/nexus_demo.png) ## Launch system and workcell orchestrators and all mock nodes -The [launch.py script](launch/launch.py) will launch the system orchestrator and 2 workcells orchestrators (each with a IRB910SC and IRB1300 robot), along with a Zenoh bridge to link selected ROS endpoints between them. These 3 orchestrators and their accompany components will be in different ROS_DOMAIN_IDs. To launch these components individually, use the following examples given. +The [launch.py script](launch/launch.py) will launch the inter-workcell nodes (including the system orchestrator) and 2 workcells orchestrators (each with a IRB910SC and IRB1300 robot). The inter-workcell system, as well as each workcell's system will also be launched with a Zenoh router, while each workcell will require a Zenoh router that acts as a bridge for specific topics, services and actions. To launch these components individually, use the following examples given. ->NOTE: The ROS_DOMAIN_ID occupied by the Zenoh bridges during launch time may be different from the `domain` values in the Zenoh bridge configurations. This is because the launch file overrides the domain ID of the zenoh bridges to ensure that it is same as that of the orchestrator. - -### Method 1: Launch system orchestrator, IRB1300 workcell and IRB910SC Workcell together with Zenoh bridge -> NOTE: Before running any of these commands, you must set the rmw implmentation to cyclonedds with -`export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp` +### Method 1: Launch system orchestrator, IRB1300 workcell and IRB910SC Workcell together with designated routers and bridges +> NOTE: Before running any of these commands, you must set the rmw implmentation to rmw_zenoh_cpp with +`export RMW_IMPLEMENTATION=rmw_zenoh_cpp` (If testing with real hardware, specify the arguments `use_fake_hardware=False`, `robot1_ip=` and `robot2_ip=`) ```bash ros2 launch nexus_demos launch.py headless:=False ``` -### Method 2: Launch System Orchestrator and 1 Workcell without Zenoh bridge (Same ROS_DOMAIN_ID) +### Method 2: Launch System Orchestrator and 1 Workcell Launch with Workcell 1 ```bash -ros2 launch nexus_demos launch.py headless:=False use_zenoh_bridge:=False run_workcell_1:=true run_workcell_2:=false +ros2 launch nexus_demos launch.py headless:=False run_workcell_1:=true run_workcell_2:=false ``` Launch with Workcell 2 ```bash -ros2 launch nexus_demos launch.py headless:=False use_zenoh_bridge:=False run_workcell_1:=false run_workcell_2:=true +ros2 launch nexus_demos launch.py headless:=False run_workcell_1:=false run_workcell_2:=true ``` Testing with real hardware ```bash -ros2 launch nexus_demos launch.py headless:=False use_zenoh_bridge:=False run_workcell_1:=True run_workcell_2:=False use_fake_hardware:=False robot1_ip:= +ros2 launch nexus_demos launch.py headless:=False run_workcell_1:=True run_workcell_2:=False use_fake_hardware:=False robot1_ip:= ``` ## Launch Orchestrators individually ### System Orchestrator ```bash -ros2 launch nexus_demos control_center.launch.py ros_domain_id:=0 headless:=False +ros2 launch nexus_demos inter_workcell.launch.py headless:=False ``` ### IRB910SC Workcell ```bash -ros2 launch nexus_demos workcell.launch.py workcell_id:=workcell_1 ros_domain_id:=1 support_package:=abb_irb910sc_support robot_xacro_file:=irb910sc_3_45.xacro moveit_config_package:=abb_irb910sc_3_45_moveit_config controllers_file:=abb_irb910sc_controllers.yaml moveit_config_file:=abb_irb910sc_3_45.srdf.xacro tf_publisher_launch_file:=irb910sc_tf.launch.py planner_config_package:=nexus_demos planner_config_file:=irb910sc_planner_params.yaml sku_detection_params_file:=irb910sc_detection.yaml zenoh_config_file:=workcell_1.json5 headless:=False +ros2 launch nexus_demos workcell.launch.py workcell_id:=workcell_1 support_package:=abb_irb910sc_support robot_xacro_file:=irb910sc_3_45.xacro moveit_config_package:=abb_irb910sc_3_45_moveit_config controllers_file:=abb_irb910sc_controllers.yaml moveit_config_file:=abb_irb910sc_3_45.srdf.xacro tf_publisher_launch_file:=irb910sc_tf.launch.py planner_config_package:=nexus_demos planner_config_file:=irb910sc_planner_params.yaml sku_detection_params_file:=irb910sc_detection.yaml zenoh_router_config_filename:=config/zenoh/workcell_1_router_config.json5 zenoh_session_config_filename:=config/zenoh/workcell_1_session_config.json5 io_stations_config_file_path:=src/nexus/nexus_demos/config/workcell_1_io_config.yaml headless:=False ``` -### IRB1300 Workcell +Start the bridge router, + ```bash -ros2 launch nexus_demos workcell.launch.py workcell_id:=workcell_2 ros_domain_id:=2 support_package:=abb_irb1300_support robot_xacro_file:=irb1300_10_115.xacro moveit_config_package:=abb_irb1300_10_115_moveit_config controllers_file:=abb_irb1300_controllers.yaml moveit_config_file:=abb_irb1300_10_115.srdf.xacro tf_publisher_launch_file:=irb1300_tf.launch.py sku_detection_params_file:=irb1300_detection.yaml zenoh_config_file:=workcell_2.json5 headless:=False +ros2 launch nexus_demos zenoh_router.launch.py zenoh_router_config_filename:=config/zenoh/workcell_1_connection_router_config.json5 ``` -## Submit a job +### IRB1300 Workcell +```bash +ros2 launch nexus_demos workcell.launch.py workcell_id:=workcell_2 support_package:=abb_irb1300_support robot_xacro_file:=irb1300_10_115.xacro moveit_config_package:=abb_irb1300_10_115_moveit_config controllers_file:=abb_irb1300_controllers.yaml moveit_config_file:=abb_irb1300_10_115.srdf.xacro tf_publisher_launch_file:=irb1300_tf.launch.py sku_detection_params_file:=irb1300_detection.yaml zenoh_router_config_filename:=config/zenoh/workcell_2_router_config.json5 zenoh_session_config_filename:=config/zenoh/workcell_2_session_config.json5 io_stations_config_file_path:=src/nexus/nexus_demos/config/workcell_2_io_config.yaml headless:=False +``` -> Note: Set your ROS_DOMAIN_ID environment variable to that of the system orchestrator before executing the work order +Start the bridge router, -`place_on_conveyor` work order: ```bash -ros2 action send_goal /system_orchestrator/execute_order nexus_orchestrator_msgs/action/ExecuteWorkOrder "{order: {work_order_id: '23', work_order: '$(cat config/place_on_conveyor.json)'}}" +ros2 launch nexus_demos zenoh_router.launch.py zenoh_router_config_filename:=config/zenoh/workcell_2_connection_router_config.json5 ``` -`pick_from_conveyor` work order: +## Submit a job + +Modify the values of `work_order_id` and the name of the work orders to these provided values in our examples, +- `place_on_conveyor` +- `pick_from_conveyor`, only works if `workcell_2` is launched +- `pick_and_place_conveyor`, only works if both `workcell_1` and `workcell_2` are launched +- `pick_and_place_amr`, only works if both `workcell_1` and `workcell_2` are launched, and the demo is started with `use_multiple_transporters:=True` + +Using `place_on_conveyor` as an example work order, with ID 23: ```bash -ros2 action send_goal /system_orchestrator/execute_order nexus_orchestrator_msgs/action/ExecuteWorkOrder "{order: {work_order_id: '24', work_order: '$(cat config/pick_from_conveyor.json)'}}" +ros2 action send_goal /system_orchestrator/execute_order nexus_orchestrator_msgs/action/ExecuteWorkOrder "{order: {work_order_id: '23', work_order: '$(cat config/place_on_conveyor.json)'}}" ``` ## Debugging diff --git a/nexus_demos/config/zenoh/inter_workcell_router_config.json5 b/nexus_demos/config/zenoh/inter_workcell_router_config.json5 new file mode 100644 index 00000000..7522eb5d --- /dev/null +++ b/nexus_demos/config/zenoh/inter_workcell_router_config.json5 @@ -0,0 +1,27 @@ +{ + mode: "router", + connect: { + endpoints: [], + }, + listen: { + endpoints: [ + "tcp/[::]:7447" + ], + }, + scouting: { + multicast: { + enabled: false, + }, + gossip: { + enabled: true, + multihop: false, + }, + }, + access_control: { + "enabled": false, + }, + timestamping: { + enabled: { router: true, peer: true, client: true }, + drop_future_timestamp: false, + }, +} diff --git a/nexus_demos/config/zenoh/inter_workcell_session_config.json5 b/nexus_demos/config/zenoh/inter_workcell_session_config.json5 new file mode 100644 index 00000000..e07e47a8 --- /dev/null +++ b/nexus_demos/config/zenoh/inter_workcell_session_config.json5 @@ -0,0 +1,24 @@ +{ + mode: "peer", + connect: { + endpoints: [ + "tcp/localhost:7447" + ], + }, + scouting: { + multicast: { + enabled: false, + }, + gossip: { + enabled: true, + multihop: false, + }, + }, + access_control: { + "enabled": false, + }, + timestamping: { + enabled: { router: true, peer: true, client: true }, + drop_future_timestamp: false, + }, +} diff --git a/nexus_demos/config/zenoh/nexus_network_config.yaml b/nexus_demos/config/zenoh/nexus_network_config.yaml deleted file mode 100644 index 2dc2d466..00000000 --- a/nexus_demos/config/zenoh/nexus_network_config.yaml +++ /dev/null @@ -1,69 +0,0 @@ -# Mode of Zenoh bridge, specified as either 'client' or 'peer'. -# Use 'client' when there is an available Zenoh router, otherwise -# use 'peer' for a distributed system. -mode: peer -# When true, activates a REST API used to administer Zenoh Bridge configurations -enable_rest_api: true - -system_orchestrators: - - namespace: system_orchestrator # ROS Namespace of the endpoints - # ROS Domain ID - domain_id: 0 - # Listening TCP Address of Zenoh bridge - tcp_listen: ["0.0.0.0:7447"] - # HTTP Port for the REST API - rest_api_http_port: 8000 - allow: - publishers: [] - # TODO(luca) check if we need estop - subscribers: ["/estop", "/.*/workcell_state"] - service_servers: ["/list_workcells", "/register_workcell", "/register_transporter"] - # TODO(luca) check if transporter needs available endpoint - service_clients: ["/.*/queue_task", "/.*/remove_pending_task", "/.*/pause", "/.*/signal", "/.*/get_state", "/.*/change_state", "/.*/is_task_doable"] - action_servers: [] - # TODO(luca) check if we really need transporter client - action_clients: ["/.*/request", "/.*/transport"] - queries_timeout: - default: 600.0 - - -workcell_orchestrators: - - namespace: workcell_1 - domain_id: 1 - tcp_connect: ["0.0.0.0:7447"] - rest_api_http_port: 8001 - allow: - publishers: ["/workcell_1/workcell_state"] - subscribers: [] - service_servers: ["/workcell_1/is_task_doable", "/workcell_1/queue_task", "/workcell_1/remove_pending_task", "/workcell_1/get_state", "/workcell_1/change_state", "/workcell_1/signal", "/workcell_1/pause"] - service_clients: ["/register_workcell"] - action_servers: ["/workcell_1/request"] - action_clients: [] - queries_timeout: - default: 600.0 - - namespace: workcell_2 - domain_id: 2 - tcp_connect: ["0.0.0.0:7447"] - rest_api_http_port: 8002 - allow: - publishers: ["/workcell_2/workcell_state"] - subscribers: [] - service_servers: ["/workcell_2/is_task_doable", "/workcell_2/queue_task", "/workcell_2/remove_pending_task", "/workcell_2/get_state", "/workcell_2/change_state", "/workcell_2/signal", "/workcell_2/pause"] - service_clients: ["/register_workcell"] - action_servers: ["/workcell_2/request"] - action_clients: [] - queries_timeout: - default: 600.0 - - namespace: workcell_3 - domain_id: 3 - tcp_connect: ["0.0.0.0:7447"] - rest_api_http_port: 8003 - allow: - publishers: ["workcell_3/workcell_state"] - subscribers: [] - service_servers: ["workcell_3/is_task_doable", "workcell_3/queue_task", "workcell_3/remove_pending_task", "workcell_3/get_state", "workcell_3/change_state"] - service_clients: ["/register_workcell"] - action_servers: ["workcell_3/request"] - action_clients: [] - queries_timeout: - default: 600.0 diff --git a/nexus_demos/config/zenoh/workcell_1_bridge_config.json5 b/nexus_demos/config/zenoh/workcell_1_bridge_config.json5 new file mode 100644 index 00000000..d8ab4c42 --- /dev/null +++ b/nexus_demos/config/zenoh/workcell_1_bridge_config.json5 @@ -0,0 +1,110 @@ +{ + mode: "router", + connect: { + endpoints: [ + "tcp/localhost:7447", + "tcp/localhost:7448", + ], + }, + listen: { + endpoints: [], + }, + scouting: { + multicast: { + enabled: false, + }, + gossip: { + enabled: true, + multihop: false, + }, + }, + access_control: { + "enabled": true, + "default_permission": "deny", + "rules": + [ + { + "id": "liveliness_tokens", + "messages": [ + "liveliness_token", + "liveliness_query", + "declare_liveliness_subscriber", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "@ros2_lv/**" + ], + }, + { + "id": "bridged_topics", + "messages": [ + "put", + "declare_subscriber", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "*/workcell_1/workcell_state/**", + ], + }, + { + "id": "bridged_services", + "messages": [ + "query", + "declare_queryable", + "reply", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "*/register_workcell/**", + "*/workcell_1/is_task_doable/**", + "*/workcell_1/queue_task/**", + "*/workcell_1/remove_pending_task/**", + "*/workcell_1/get_state/**", + "*/workcell_1/change_state/**", + "*/workcell_1/signal/**", + "*/workcell_1/pause/**", + ], + }, + { + "id": "bridged_actions", + "messages": [ + "query", + "declare_queryable", + "reply", + "put", + "declare_subscriber", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "*/workcell_1/request/**", + ], + }, + ], + "subjects": + [ + { + "id": "all_subjects", + }, + ], + "policies": + [ + { + "rules": [ + "bridged_topics", + "bridged_services", + "bridged_actions", + "liveliness_tokens", + ], + "subjects": ["all_subjects"], + }, + ] + }, + timestamping: { + enabled: { router: true, peer: true, client: true }, + drop_future_timestamp: false, + }, +} diff --git a/nexus_demos/config/zenoh/workcell_1_connection_router_config.json5 b/nexus_demos/config/zenoh/workcell_1_connection_router_config.json5 new file mode 100644 index 00000000..d8ab4c42 --- /dev/null +++ b/nexus_demos/config/zenoh/workcell_1_connection_router_config.json5 @@ -0,0 +1,110 @@ +{ + mode: "router", + connect: { + endpoints: [ + "tcp/localhost:7447", + "tcp/localhost:7448", + ], + }, + listen: { + endpoints: [], + }, + scouting: { + multicast: { + enabled: false, + }, + gossip: { + enabled: true, + multihop: false, + }, + }, + access_control: { + "enabled": true, + "default_permission": "deny", + "rules": + [ + { + "id": "liveliness_tokens", + "messages": [ + "liveliness_token", + "liveliness_query", + "declare_liveliness_subscriber", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "@ros2_lv/**" + ], + }, + { + "id": "bridged_topics", + "messages": [ + "put", + "declare_subscriber", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "*/workcell_1/workcell_state/**", + ], + }, + { + "id": "bridged_services", + "messages": [ + "query", + "declare_queryable", + "reply", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "*/register_workcell/**", + "*/workcell_1/is_task_doable/**", + "*/workcell_1/queue_task/**", + "*/workcell_1/remove_pending_task/**", + "*/workcell_1/get_state/**", + "*/workcell_1/change_state/**", + "*/workcell_1/signal/**", + "*/workcell_1/pause/**", + ], + }, + { + "id": "bridged_actions", + "messages": [ + "query", + "declare_queryable", + "reply", + "put", + "declare_subscriber", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "*/workcell_1/request/**", + ], + }, + ], + "subjects": + [ + { + "id": "all_subjects", + }, + ], + "policies": + [ + { + "rules": [ + "bridged_topics", + "bridged_services", + "bridged_actions", + "liveliness_tokens", + ], + "subjects": ["all_subjects"], + }, + ] + }, + timestamping: { + enabled: { router: true, peer: true, client: true }, + drop_future_timestamp: false, + }, +} diff --git a/nexus_demos/config/zenoh/workcell_1_router_config.json5 b/nexus_demos/config/zenoh/workcell_1_router_config.json5 new file mode 100644 index 00000000..be25179e --- /dev/null +++ b/nexus_demos/config/zenoh/workcell_1_router_config.json5 @@ -0,0 +1,27 @@ +{ + mode: "router", + connect: { + endpoints: [], + }, + listen: { + endpoints: [ + "tcp/[::]:7448" + ], + }, + scouting: { + multicast: { + enabled: false, + }, + gossip: { + enabled: true, + multihop: false, + }, + }, + access_control: { + "enabled": false, + }, + timestamping: { + enabled: { router: true, peer: true, client: true }, + drop_future_timestamp: false, + }, +} diff --git a/nexus_demos/config/zenoh/workcell_1_session_config.json5 b/nexus_demos/config/zenoh/workcell_1_session_config.json5 new file mode 100644 index 00000000..6d40df63 --- /dev/null +++ b/nexus_demos/config/zenoh/workcell_1_session_config.json5 @@ -0,0 +1,24 @@ +{ + mode: "peer", + connect: { + endpoints: [ + "tcp/localhost:7448", + ], + }, + scouting: { + multicast: { + enabled: false, + }, + gossip: { + enabled: true, + multihop: false, + }, + }, + access_control: { + "enabled": false, + }, + timestamping: { + enabled: { router: true, peer: true, client: true }, + drop_future_timestamp: false, + }, +} diff --git a/nexus_demos/config/zenoh/workcell_2_bridge_config.json5 b/nexus_demos/config/zenoh/workcell_2_bridge_config.json5 new file mode 100644 index 00000000..92579282 --- /dev/null +++ b/nexus_demos/config/zenoh/workcell_2_bridge_config.json5 @@ -0,0 +1,110 @@ +{ + mode: "router", + connect: { + endpoints: [ + "tcp/localhost:7447", + "tcp/localhost:7449", + ], + }, + listen: { + endpoints: [], + }, + scouting: { + multicast: { + enabled: false, + }, + gossip: { + enabled: true, + multihop: false, + }, + }, + access_control: { + "enabled": true, + "default_permission": "deny", + "rules": + [ + { + "id": "liveliness_tokens", + "messages": [ + "liveliness_token", + "liveliness_query", + "declare_liveliness_subscriber", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "@ros2_lv/**" + ], + }, + { + "id": "bridged_topics", + "messages": [ + "put", + "declare_subscriber", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "*/workcell_2/workcell_state/**", + ], + }, + { + "id": "bridged_services", + "messages": [ + "query", + "declare_queryable", + "reply", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "*/register_workcell/**", + "*/workcell_2/is_task_doable/**", + "*/workcell_2/queue_task/**", + "*/workcell_2/remove_pending_task/**", + "*/workcell_2/get_state/**", + "*/workcell_2/change_state/**", + "*/workcell_2/signal/**", + "*/workcell_2/pause/**", + ], + }, + { + "id": "bridged_actions", + "messages": [ + "query", + "declare_queryable", + "reply", + "put", + "declare_subscriber", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "*/workcell_2/request/**", + ], + }, + ], + "subjects": + [ + { + "id": "all_subjects", + }, + ], + "policies": + [ + { + "rules": [ + "bridged_topics", + "bridged_services", + "bridged_actions", + "liveliness_tokens", + ], + "subjects": ["all_subjects"], + }, + ] + }, + timestamping: { + enabled: { router: true, peer: true, client: true }, + drop_future_timestamp: false, + }, +} diff --git a/nexus_demos/config/zenoh/workcell_2_connection_router_config.json5 b/nexus_demos/config/zenoh/workcell_2_connection_router_config.json5 new file mode 100644 index 00000000..92579282 --- /dev/null +++ b/nexus_demos/config/zenoh/workcell_2_connection_router_config.json5 @@ -0,0 +1,110 @@ +{ + mode: "router", + connect: { + endpoints: [ + "tcp/localhost:7447", + "tcp/localhost:7449", + ], + }, + listen: { + endpoints: [], + }, + scouting: { + multicast: { + enabled: false, + }, + gossip: { + enabled: true, + multihop: false, + }, + }, + access_control: { + "enabled": true, + "default_permission": "deny", + "rules": + [ + { + "id": "liveliness_tokens", + "messages": [ + "liveliness_token", + "liveliness_query", + "declare_liveliness_subscriber", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "@ros2_lv/**" + ], + }, + { + "id": "bridged_topics", + "messages": [ + "put", + "declare_subscriber", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "*/workcell_2/workcell_state/**", + ], + }, + { + "id": "bridged_services", + "messages": [ + "query", + "declare_queryable", + "reply", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "*/register_workcell/**", + "*/workcell_2/is_task_doable/**", + "*/workcell_2/queue_task/**", + "*/workcell_2/remove_pending_task/**", + "*/workcell_2/get_state/**", + "*/workcell_2/change_state/**", + "*/workcell_2/signal/**", + "*/workcell_2/pause/**", + ], + }, + { + "id": "bridged_actions", + "messages": [ + "query", + "declare_queryable", + "reply", + "put", + "declare_subscriber", + ], + "flows": ["ingress", "egress"], + "permission": "allow", + "key_exprs": [ + "*/workcell_2/request/**", + ], + }, + ], + "subjects": + [ + { + "id": "all_subjects", + }, + ], + "policies": + [ + { + "rules": [ + "bridged_topics", + "bridged_services", + "bridged_actions", + "liveliness_tokens", + ], + "subjects": ["all_subjects"], + }, + ] + }, + timestamping: { + enabled: { router: true, peer: true, client: true }, + drop_future_timestamp: false, + }, +} diff --git a/nexus_demos/config/zenoh/workcell_2_router_config.json5 b/nexus_demos/config/zenoh/workcell_2_router_config.json5 new file mode 100644 index 00000000..4760cbc5 --- /dev/null +++ b/nexus_demos/config/zenoh/workcell_2_router_config.json5 @@ -0,0 +1,27 @@ +{ + mode: "router", + connect: { + endpoints: [], + }, + listen: { + endpoints: [ + "tcp/[::]:7449" + ], + }, + scouting: { + multicast: { + enabled: false, + }, + gossip: { + enabled: true, + multihop: false, + }, + }, + access_control: { + "enabled": false, + }, + timestamping: { + enabled: { router: true, peer: true, client: true }, + drop_future_timestamp: false, + }, +} diff --git a/nexus_demos/config/zenoh/workcell_2_session_config.json5 b/nexus_demos/config/zenoh/workcell_2_session_config.json5 new file mode 100644 index 00000000..2b1be2d1 --- /dev/null +++ b/nexus_demos/config/zenoh/workcell_2_session_config.json5 @@ -0,0 +1,24 @@ +{ + mode: "peer", + connect: { + endpoints: [ + "tcp/localhost:7449", + ], + }, + scouting: { + multicast: { + enabled: false, + }, + gossip: { + enabled: true, + multihop: false, + }, + }, + access_control: { + "enabled": false, + }, + timestamping: { + enabled: { router: true, peer: true, client: true }, + drop_future_timestamp: false, + }, +} diff --git a/nexus_demos/launch/inter_workcell.launch.py b/nexus_demos/launch/inter_workcell.launch.py index 6d6b4190..d29d1c36 100644 --- a/nexus_demos/launch/inter_workcell.launch.py +++ b/nexus_demos/launch/inter_workcell.launch.py @@ -108,12 +108,11 @@ def activate_node(target_node: LifecycleNode, depend_node: LifecycleNode = None) def launch_setup(context, *args, **kwargs): - ros_domain_id = LaunchConfiguration("ros_domain_id") use_fake_hardware = LaunchConfiguration("use_fake_hardware") use_multiple_transporters = LaunchConfiguration("use_multiple_transporters") - use_zenoh_bridge = LaunchConfiguration("use_zenoh_bridge") zenoh_config_package = LaunchConfiguration("zenoh_config_package") - zenoh_config_filename = LaunchConfiguration("zenoh_config_filename") + zenoh_router_config_filename = LaunchConfiguration("zenoh_router_config_filename") + zenoh_session_config_filename = LaunchConfiguration("zenoh_session_config_filename") activate_system_orchestrator = LaunchConfiguration("activate_system_orchestrator") headless = LaunchConfiguration("headless") main_bt_filename = LaunchConfiguration("main_bt_filename") @@ -230,7 +229,7 @@ def launch_setup(context, *args, **kwargs): condition=UnlessCondition(headless), ) - zenoh_bridge = GroupAction( + zenoh_router = GroupAction( [ IncludeLaunchDescription( [ @@ -238,18 +237,16 @@ def launch_setup(context, *args, **kwargs): [ FindPackageShare("nexus_demos"), "launch", - "zenoh_bridge.launch.py", + "zenoh_router.launch.py", ] ) ], launch_arguments={ "zenoh_config_package": zenoh_config_package, - "zenoh_config_filename": zenoh_config_filename, - "ros_domain_id": ros_domain_id.perform(context), + "zenoh_router_config_filename": zenoh_router_config_filename, }.items(), ) - ], - condition=IfCondition(use_zenoh_bridge), + ] ) activate_system_orchestrator = GroupAction( @@ -260,7 +257,16 @@ def launch_setup(context, *args, **kwargs): ) return [ - SetEnvironmentVariable("ROS_DOMAIN_ID", ros_domain_id), + zenoh_router, + SetEnvironmentVariable( + "ZENOH_SESSION_CONFIG_URI", + PathJoinSubstitution( + [ + FindPackageShare(zenoh_config_package), + zenoh_session_config_filename, + ] + ) + ), system_orchestrator_node, building_map_node, rmf_transporter, @@ -268,7 +274,6 @@ def launch_setup(context, *args, **kwargs): rmf_transporter_node, mock_emergency_alarm_node, nexus_panel, - zenoh_bridge, activate_system_orchestrator, activate_mock_transporter_node, activate_rmf_transporter_node, @@ -280,11 +285,6 @@ def generate_launch_description(): return launch.LaunchDescription( [ - DeclareLaunchArgument( - "ros_domain_id", - default_value="0", - description="ROS_DOMAIN_ID environment variable", - ), DeclareLaunchArgument( "use_fake_hardware", default_value="true", @@ -296,20 +296,20 @@ def generate_launch_description(): description="Set true to run multiple transporters including the Open-RMF managed fleet to transport material\ between workcells.", ), - DeclareLaunchArgument( - "use_zenoh_bridge", - default_value="true", - description="Set true to launch the Zenoh DDS Bridge", - ), DeclareLaunchArgument( name="zenoh_config_package", default_value="nexus_demos", - description="Package containing Zenoh DDS bridge configurations", + description="Package containing Zenoh configurations", + ), + DeclareLaunchArgument( + name="zenoh_router_config_filename", + default_value="config/zenoh/inter_workcell_router_config.json5", + description="RMW Zenoh router configuration filepath", ), DeclareLaunchArgument( - name="zenoh_config_filename", - default_value="config/zenoh/system_orchestrator.json5", - description="Zenoh DDS bridge configuration filepath", + name="zenoh_session_config_filename", + default_value="config/zenoh/inter_workcell_session_config.json5", + description="RMW Zenoh session configuration filepath", ), DeclareLaunchArgument( "activate_system_orchestrator", @@ -328,7 +328,7 @@ def generate_launch_description(): ), DeclareLaunchArgument( "remap_task_types", - default_value="\"pick_and_place: [place_on_conveyor, pick_from_conveyor]\"", + default_value="\"pick_and_place: [place_on_amr, place_on_conveyor, pick_from_amr, pick_from_conveyor]\"", description="A yaml containing a dictionary of task types and an array of remaps", ), DeclareLaunchArgument( diff --git a/nexus_demos/launch/launch.py b/nexus_demos/launch/launch.py index 20e46664..d959833d 100644 --- a/nexus_demos/launch/launch.py +++ b/nexus_demos/launch/launch.py @@ -33,17 +33,16 @@ def launch_setup(context, *args, **kwargs): if ( "RMW_IMPLEMENTATION" not in os.environ - or os.environ["RMW_IMPLEMENTATION"] != "rmw_cyclonedds_cpp" + or os.environ["RMW_IMPLEMENTATION"] != "rmw_zenoh_cpp" ): print( - "Only cycloneDDS is supported, the environment variable RMW_IMPLEMENTATION must be set to rmw_cyclonedds_cpp", + "Only rmw_zenoh_cpp is supported, the environment variable RMW_IMPLEMENTATION must be set to rmw_zenoh_cpp", file=sys.stderr, ) exit(1) headless = LaunchConfiguration("headless") use_multiple_transporters = LaunchConfiguration("use_multiple_transporters") - use_zenoh_bridge = LaunchConfiguration("use_zenoh_bridge") use_fake_hardware = LaunchConfiguration("use_fake_hardware") robot1_ip = LaunchConfiguration("robot1_ip") robot2_ip = LaunchConfiguration("robot2_ip") @@ -52,39 +51,8 @@ def launch_setup(context, *args, **kwargs): run_workcell_2 = LaunchConfiguration("run_workcell_2") workcell_2_remap_task_types = LaunchConfiguration("workcell_2_remap_task_types") - inter_workcell_domain_id = 0 - workcell_1_domain_id = 0 - workcell_2_domain_id = 0 log_msg = "" - if "ROS_DOMAIN_ID" in os.environ: - inter_workcell_domain_id = int(os.environ["ROS_DOMAIN_ID"]) - if not 0 < inter_workcell_domain_id < 230: - log_msg += ( - "ROS_DOMAIN_ID not within the range of 0 to 230, setting it to 0. \n" - ) - inter_workcell_domain_id = 0 - - if use_zenoh_bridge.perform(context).lower() == "true": - log_msg += "Using the zenoh bridge\n" - workcell_1_domain_id = inter_workcell_domain_id + 1 - workcell_2_domain_id = inter_workcell_domain_id + 2 - else: - log_msg += "Not using zenoh bridge\n" - if ( - run_workcell_1.perform(context).lower() == "true" - and run_workcell_2.perform(context).lower() == "true" - ): - print("To run both workcells, enable the Zenoh Bridge") - sys.exit(1) - workcell_1_domain_id = inter_workcell_domain_id - workcell_2_domain_id = inter_workcell_domain_id - log_msg += f"Inter-workcell has ROS_DOMAIN_ID {inter_workcell_domain_id}\n" - if run_workcell_1.perform(context).lower() == "true": - log_msg += f"Workcell 1 has ROS_DOMAIN_ID {workcell_1_domain_id}\n" - if run_workcell_2.perform(context).lower() == "true": - log_msg += f"Workcell 2 has ROS_DOMAIN_ID {workcell_2_domain_id}\n" - main_bt_filename = "main.xml" remap_task_types = """{ pick_and_place: [place_on_amr, place_on_conveyor, pick_from_amr, pick_from_conveyor], @@ -106,9 +74,9 @@ def launch_setup(context, *args, **kwargs): ) ], launch_arguments={ - "ros_domain_id": str(inter_workcell_domain_id), "zenoh_config_package": "nexus_demos", - "zenoh_config_filename": "config/zenoh/system_orchestrator.json5", + "zenoh_router_config_filename": "config/zenoh/inter_workcell_router_config.json5", + "zenoh_session_config_filename": "config/zenoh/inter_workcell_session_config.json5", "use_multiple_transporters": use_multiple_transporters, "activate_system_orchestrator": headless, "headless": headless, @@ -141,9 +109,7 @@ def launch_setup(context, *args, **kwargs): "remap_task_types": workcell_1_remap_task_types, "task_checker_plugin": "nexus::task_checkers::FilepathChecker", "max_jobs": max_workcell_jobs, - "ros_domain_id": str(workcell_1_domain_id), "headless": headless, - "use_zenoh_bridge": use_zenoh_bridge, "controller_config_package": "nexus_demos", "planner_config_package": "nexus_demos", "planner_config_file": "abb_irb910sc_planner_params.yaml", @@ -158,7 +124,8 @@ def launch_setup(context, *args, **kwargs): "use_fake_hardware": use_fake_hardware, "robot_ip": robot1_ip, "zenoh_config_package": "nexus_demos", - "zenoh_config_filename": "config/zenoh/workcell_1.json5", + "zenoh_router_config_filename": "config/zenoh/workcell_1_router_config.json5", + "zenoh_session_config_filename": "config/zenoh/workcell_1_session_config.json5", "io_stations_config_file_path": os.path.join(get_package_share_directory("nexus_demos"), "config", "workcell_1_io_config.yaml"), }.items(), condition=IfCondition(run_workcell_1), @@ -166,6 +133,27 @@ def launch_setup(context, *args, **kwargs): ], ) + launch_workcell_1_connection_router_router = GroupAction( + [ + IncludeLaunchDescription( + [ + PathJoinSubstitution( + [ + FindPackageShare("nexus_demos"), + "launch", + "zenoh_router.launch.py", + ] + ) + ], + launch_arguments={ + "zenoh_config_package": "nexus_demos", + "zenoh_router_config_filename": "config/zenoh/workcell_1_connection_router_config.json5", + }.items(), + condition=IfCondition(run_workcell_1), + ) + ] + ) + launch_workcell_2 = GroupAction( actions=[ IncludeLaunchDescription( @@ -187,9 +175,7 @@ def launch_setup(context, *args, **kwargs): "remap_task_types": workcell_2_remap_task_types, "task_checker_plugin": "nexus::task_checkers::FilepathChecker", "max_jobs": max_workcell_jobs, - "ros_domain_id": str(workcell_2_domain_id), "headless": headless, - "use_zenoh_bridge": use_zenoh_bridge, "controller_config_package": "nexus_demos", "planner_config_package": "nexus_demos", "planner_config_file": "abb_irb1300_planner_params.yaml", @@ -204,7 +190,8 @@ def launch_setup(context, *args, **kwargs): "use_fake_hardware": use_fake_hardware, "robot_ip": robot2_ip, "zenoh_config_package": "nexus_demos", - "zenoh_config_filename": "config/zenoh/workcell_2.json5", + "zenoh_router_config_filename": "config/zenoh/workcell_2_router_config.json5", + "zenoh_session_config_filename": "config/zenoh/workcell_2_session_config.json5", "io_stations_config_file_path": os.path.join(get_package_share_directory("nexus_demos"), "config", "workcell_2_io_config.yaml"), }.items(), condition=IfCondition(run_workcell_2), @@ -212,11 +199,34 @@ def launch_setup(context, *args, **kwargs): ], ) + launch_workcell_2_connection_router_router = GroupAction( + [ + IncludeLaunchDescription( + [ + PathJoinSubstitution( + [ + FindPackageShare("nexus_demos"), + "launch", + "zenoh_router.launch.py", + ] + ) + ], + launch_arguments={ + "zenoh_config_package": "nexus_demos", + "zenoh_router_config_filename": "config/zenoh/workcell_2_connection_router_config.json5", + }.items(), + condition=IfCondition(run_workcell_2), + ) + ] + ) + return [ LogInfo(msg=log_msg), launch_inter_workcell, launch_workcell_1, + launch_workcell_1_connection_router_router, launch_workcell_2, + launch_workcell_2_connection_router_router, ] @@ -234,13 +244,6 @@ def generate_launch_description(): description="Set true to run multiple transporters including the Open-RMF managed fleet to transport material\ between workcells.", ), - DeclareLaunchArgument( - "use_zenoh_bridge", - default_value="true", - description="Set true to launch the Zenoh DDS Bridge with each orchestrator\ - in different domains. Else, all orchestrators are launched under the \ - same Domain ID without a Zenoh bridge.", - ), DeclareLaunchArgument( "use_fake_hardware", default_value="true", diff --git a/nexus_demos/launch/workcell.launch.py b/nexus_demos/launch/workcell.launch.py index 7f75997e..62ada2e5 100644 --- a/nexus_demos/launch/workcell.launch.py +++ b/nexus_demos/launch/workcell.launch.py @@ -37,14 +37,13 @@ from typing import List -def activate_node_service(node_name, ros_domain_id): +def activate_node_service(node_name): activate_node_proc = ExecuteProcess( cmd=[ 'python3', [FindPackageShare('nexus_demos'), "/scripts/activate_node.py"], node_name, - ], - additional_env={'ROS_DOMAIN_ID': ros_domain_id}, + ] ) def check_activate_return_code(event, _): @@ -77,7 +76,6 @@ def launch_setup(context, *args, **kwargs): bt_path = LaunchConfiguration("bt_path") task_checker_plugin = LaunchConfiguration("task_checker_plugin") max_jobs = LaunchConfiguration("max_jobs") - ros_domain_id = LaunchConfiguration("ros_domain_id") headless = LaunchConfiguration("headless") controller_config_package = LaunchConfiguration("controller_config_package") planner_config_package = LaunchConfiguration("planner_config_package") @@ -92,9 +90,9 @@ def launch_setup(context, *args, **kwargs): dispenser_properties = LaunchConfiguration("dispenser_properties") use_fake_hardware = LaunchConfiguration("use_fake_hardware") robot_ip = LaunchConfiguration("robot_ip") - use_zenoh_bridge = LaunchConfiguration("use_zenoh_bridge") zenoh_config_package = LaunchConfiguration("zenoh_config_package") - zenoh_config_filename = LaunchConfiguration("zenoh_config_filename") + zenoh_router_config_filename = LaunchConfiguration("zenoh_router_config_filename") + zenoh_session_config_filename = LaunchConfiguration("zenoh_session_config_filename") remap_task_types = LaunchConfiguration("remap_task_types") # todo(Yadunund): There is no good way to get a list of strings via CLI and parse it using # LaunchConfiguration. The best way to configure this would be via a YAML params which we @@ -183,8 +181,37 @@ def launch_setup(context, *args, **kwargs): arguments=['--ros-args', '--log-level', 'info'], ) + zenoh_router = GroupAction( + [ + IncludeLaunchDescription( + [ + PathJoinSubstitution( + [ + FindPackageShare("nexus_demos"), + "launch", + "zenoh_router.launch.py", + ] + ) + ], + launch_arguments={ + "zenoh_config_package": zenoh_config_package, + "zenoh_router_config_filename": zenoh_router_config_filename, + }.items(), + ) + ] + ) + return [ - SetEnvironmentVariable('ROS_DOMAIN_ID', ros_domain_id), + zenoh_router, + SetEnvironmentVariable( + "ZENOH_SESSION_CONFIG_URI", + PathJoinSubstitution( + [ + FindPackageShare(zenoh_config_package), + zenoh_session_config_filename, + ] + ) + ), mock_dispenser_node, mock_product_detector_node, mock_gripper_node, @@ -244,30 +271,14 @@ def launch_setup(context, *args, **kwargs): 'launch', tf_publisher_launch_file, ]) + ], + launch_arguments=[ + ] ) ] ), - GroupAction( - [ - IncludeLaunchDescription( - [ - PathJoinSubstitution([ - FindPackageShare('nexus_demos'), - 'launch', - 'zenoh_bridge.launch.py' - ]) - ], - launch_arguments={ - 'zenoh_config_package': zenoh_config_package, - 'zenoh_config_filename': zenoh_config_filename, - 'ros_domain_id': ros_domain_id.perform(context), - }.items() - ) - ], - condition=IfCondition(use_zenoh_bridge), - ), - activate_node_service("motion_planner_server", ros_domain_id.perform(context)), + activate_node_service("motion_planner_server"), ] @@ -292,11 +303,6 @@ def generate_launch_description(): default_value="1", description="Maximum number of parallel jobs that this workcell is allowed to handle.", ), - DeclareLaunchArgument( - "ros_domain_id", - default_value="0", - description="ROS_DOMAIN_ID environment variable", - ), DeclareLaunchArgument( "headless", default_value="true", @@ -367,20 +373,20 @@ def generate_launch_description(): default_value="", description="The IP address of the real robot when use_fake_hardware is False.", ), - DeclareLaunchArgument( - "use_zenoh_bridge", - default_value="true", - description="Set true to launch the Zenoh DDS Bridge", - ), DeclareLaunchArgument( name="zenoh_config_package", default_value="nexus_demos", - description="Package containing Zenoh DDS bridge configurations", + description="Package containing Zenoh configurations", + ), + DeclareLaunchArgument( + name="zenoh_router_config_filename", + default_value="config/zenoh/workcell_1_router_config.json5", + description="RMW Zenoh router configuration filepath", ), DeclareLaunchArgument( - name="zenoh_config_filename", - default_value="config/zenoh/workcell_1.json5", - description="Zenoh DDS bridge configuration filepath", + name="zenoh_session_config_filename", + default_value="config/zenoh/workcell_1_session_config.json5", + description="RMW Zenoh session configuration filepath", ), DeclareLaunchArgument( "remap_task_types", diff --git a/nexus_demos/launch/workcell_1_tf.launch.py b/nexus_demos/launch/workcell_1_tf.launch.py index 95abd1f4..1e163a47 100644 --- a/nexus_demos/launch/workcell_1_tf.launch.py +++ b/nexus_demos/launch/workcell_1_tf.launch.py @@ -13,16 +13,18 @@ # limitations under the License. -import rclpy -import rclpy.node from launch_ros.actions import Node from launch import LaunchDescription -from launch.actions import GroupAction +from launch.actions import DeclareLaunchArgument, OpaqueFunction, SetEnvironmentVariable +from launch_ros.substitutions import FindPackageShare +from launch.substitutions import LaunchConfiguration, PathJoinSubstitution -def generate_launch_description(): +def launch_setup(context, *args, **kwargs): """Publish static TFs. """ + zenoh_config_package = LaunchConfiguration("zenoh_config_package") + zenoh_session_config_filename = LaunchConfiguration("zenoh_session_config_filename") # Static TF of robot base_link to world static_tf_base_link = Node( @@ -151,16 +153,36 @@ def generate_launch_description(): ], ) - return LaunchDescription( - [ - GroupAction( - actions=[ - static_tf_base_link, - static_tf_home, - static_tf_pickup_pose, - static_tf_pallet, - static_tf_dropoff, + return [ + SetEnvironmentVariable( + "ZENOH_SESSION_CONFIG_URI", + PathJoinSubstitution( + [ + FindPackageShare(zenoh_config_package), + zenoh_session_config_filename, ] ) + ), + static_tf_base_link, + static_tf_home, + static_tf_pickup_pose, + static_tf_pallet, + static_tf_dropoff, + ] + +def generate_launch_description(): + return LaunchDescription( + [ + DeclareLaunchArgument( + name="zenoh_config_package", + default_value="nexus_demos", + description="Package containing Zenoh configurations", + ), + DeclareLaunchArgument( + name="zenoh_session_config_filename", + default_value="config/zenoh/workcell_1_session_config.json5", + description="RMW Zenoh session configuration filepath", + ), + OpaqueFunction(function=launch_setup) ] ) diff --git a/nexus_demos/launch/workcell_2_tf.launch.py b/nexus_demos/launch/workcell_2_tf.launch.py index 361721b7..ebe88ad2 100644 --- a/nexus_demos/launch/workcell_2_tf.launch.py +++ b/nexus_demos/launch/workcell_2_tf.launch.py @@ -13,16 +13,18 @@ # limitations under the License. -import rclpy -import rclpy.node from launch_ros.actions import Node from launch import LaunchDescription -from launch.actions import GroupAction +from launch.actions import DeclareLaunchArgument, OpaqueFunction, SetEnvironmentVariable +from launch_ros.substitutions import FindPackageShare +from launch.substitutions import LaunchConfiguration, PathJoinSubstitution -def generate_launch_description(): +def launch_setup(context, *args, **kwargs): """Publish static TFs. """ + zenoh_config_package = LaunchConfiguration("zenoh_config_package") + zenoh_session_config_filename = LaunchConfiguration("zenoh_session_config_filename") # Static TF of robot base_link to world static_tf_base_link = Node( @@ -153,16 +155,36 @@ def generate_launch_description(): ], ) - return LaunchDescription( - [ - GroupAction( - actions=[ - static_tf_base_link, - static_tf_home, - static_tf_dropoff_goal_pose, - static_tf_sku_detection_camera, - static_tf_pallet, + return [ + SetEnvironmentVariable( + "ZENOH_SESSION_CONFIG_URI", + PathJoinSubstitution( + [ + FindPackageShare(zenoh_config_package), + zenoh_session_config_filename, ] ) + ), + static_tf_base_link, + static_tf_home, + static_tf_dropoff_goal_pose, + static_tf_sku_detection_camera, + static_tf_pallet, + ] + +def generate_launch_description(): + return LaunchDescription( + [ + DeclareLaunchArgument( + name="zenoh_config_package", + default_value="nexus_demos", + description="Package containing Zenoh configurations", + ), + DeclareLaunchArgument( + name="zenoh_session_config_filename", + default_value="config/zenoh/workcell_1_session_config.json5", + description="RMW Zenoh session configuration filepath", + ), + OpaqueFunction(function=launch_setup) ] ) diff --git a/nexus_demos/launch/zenoh_bridge.launch.py b/nexus_demos/launch/zenoh_bridge.launch.py deleted file mode 100644 index 8dad1272..00000000 --- a/nexus_demos/launch/zenoh_bridge.launch.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright 2022 Johnson & Johnson -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from launch import LaunchDescription -from launch.actions import ( - DeclareLaunchArgument, - ExecuteProcess, - OpaqueFunction, -) -from launch.conditions import IfCondition -from launch.substitutions import ( - FindExecutable, - LaunchConfiguration, - PathJoinSubstitution, -) -from launch_ros.substitutions import ( - ExecutableInPackage, - FindPackageShare, -) - - -def launch_setup(context, *args, **kwargs): - - zenoh_config_package = LaunchConfiguration("zenoh_config_package") - zenoh_config_filename = LaunchConfiguration("zenoh_config_filename") - dds_localhost_only = LaunchConfiguration("dds_localhost_only") - log_level = LaunchConfiguration("log_level") - ros_domain_id = LaunchConfiguration("ros_domain_id") - - cmd = [ - ExecutableInPackage( - executable="zenoh_bridge_ros2dds", - package="nexus_zenoh_bridge_dds_vendor", - ), - "--config", - PathJoinSubstitution([ - FindPackageShare(zenoh_config_package), - zenoh_config_filename - ]), - "--domain", - ros_domain_id, - ] - - if IfCondition(dds_localhost_only).evaluate(context): - cmd.append("--dds-localhost-only") - - zenoh_bridge_exec = ExecuteProcess( - cmd=cmd, - shell=True, - additional_env={"RUST_LOG": log_level.perform(context)}, - ) - - return [zenoh_bridge_exec] - -def generate_launch_description(): - declared_arguments = [] - - declared_arguments.append( - DeclareLaunchArgument( - name="ros_domain_id", - default_value="0", - description="ROS_DOMAIN_ID environment variable", - ) - ) - - declared_arguments.append( - DeclareLaunchArgument( - name="log_level", - default_value="error", - description="Log level of zenoh DDS Bridge", - ) - ) - - declared_arguments.append( - DeclareLaunchArgument( - name="dds_localhost_only", - default_value="false", - description="If true, the DDS discovery and traffic " - "will occur only on the localhost interface (127.0.0.1)", - ) - ) - - declared_arguments.append( - DeclareLaunchArgument( - name="zenoh_config_package", - default_value="nexus_demos", - description="Package containing Zenoh DDS bridge configurations", - ) - ) - - declared_arguments.append( - DeclareLaunchArgument( - name="zenoh_config_filename", - default_value="config/zenoh/system_orchestrator.json5", - description="Zenoh DDS bridge configuration filepath", - ) - ) - - return LaunchDescription( - declared_arguments + [OpaqueFunction(function=launch_setup)] - ) diff --git a/nexus_demos/launch/zenoh_router.launch.py b/nexus_demos/launch/zenoh_router.launch.py new file mode 100644 index 00000000..c44ed3bb --- /dev/null +++ b/nexus_demos/launch/zenoh_router.launch.py @@ -0,0 +1,77 @@ +# Copyright 2025 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from launch import LaunchDescription +from launch.actions import ( + DeclareLaunchArgument, + ExecuteProcess, + OpaqueFunction, +) +from launch.substitutions import ( + LaunchConfiguration, + PathJoinSubstitution, +) +from launch_ros.substitutions import ( + ExecutableInPackage, + FindPackageShare, +) + + +def launch_setup(context, *args, **kwargs): + log_level = LaunchConfiguration("log_level") + zenoh_config_package = LaunchConfiguration("zenoh_config_package") + zenoh_router_config_filename = LaunchConfiguration("zenoh_router_config_filename") + + cmd = [ + ExecutableInPackage( + executable="rmw_zenohd", + package="rmw_zenoh_cpp", + ) + ] + + zenoh_router_exec = ExecuteProcess( + cmd=cmd, + shell=True, + additional_env={ + "ZENOH_ROUTER_CONFIG_URI": PathJoinSubstitution([ + FindPackageShare(zenoh_config_package), + zenoh_router_config_filename + ]), + "RUST_LOG": log_level.perform(context) + }, + ) + + return [zenoh_router_exec] + +def generate_launch_description(): + declared_arguments = [ + DeclareLaunchArgument( + name="log_level", + default_value="zenoh=debug", + description="Log level of zenoh router", + ), + DeclareLaunchArgument( + name="zenoh_config_package", + default_value="nexus_demos", + description="Package containing RWM Zenoh configurations", + ), + DeclareLaunchArgument( + name="zenoh_router_config_filename", + default_value="config/zenoh/system_orchestrator_router_config.json5", + description="RMW Zenoh configuration filepath", + ), + ] + return LaunchDescription( + declared_arguments + [OpaqueFunction(function=launch_setup)] + ) diff --git a/nexus_demos/package.xml b/nexus_demos/package.xml index ec0cd3b0..9893648b 100644 --- a/nexus_demos/package.xml +++ b/nexus_demos/package.xml @@ -8,7 +8,6 @@ Apache License 2.0 nexus_endpoints - nexus_network_configuration ament_cmake @@ -27,7 +26,7 @@ tf2_ros yaml-cpp yaml_cpp_vendor - rmw_cyclonedds_cpp + rmw_zenoh_cpp abb_bringup abb_irb1300_support @@ -44,7 +43,6 @@ nexus_rviz_plugins nexus_system_orchestrator nexus_workcell_orchestrator - nexus_zenoh_bridge_dds_vendor rmf_building_map_tools diff --git a/nexus_demos/test_invalid_place_on_conveyor.py b/nexus_demos/test_invalid_place_on_conveyor.py index 4a42a34a..c7a6e503 100644 --- a/nexus_demos/test_invalid_place_on_conveyor.py +++ b/nexus_demos/test_invalid_place_on_conveyor.py @@ -30,15 +30,6 @@ class InvalidPlaceOnConveyorTest(NexusTestCase): @RosTestCase.timeout(60) async def asyncSetUp(self): - # todo(YV): Find a better fix to the problem below. - # zenoh-bridge was bumped to 0.72 as part of the upgrade to - # ROS 2 Iron. However with this upgrade, the bridge does not clearly - # terminate when a SIGINT is received leaving behind zombie bridge - # processes from previous test cases. As a result, workcell registration - # fails for this testcase due to multiple bridges remaining active. - # Hence we explicitly kill any zenoh processes before launching the test. - subprocess.Popen('pkill -9 -f zenoh', shell=True) - self.proc = managed_process( ("ros2", "launch", "nexus_demos", "launch.py", "workcell_1_remap_task_types:='invalid_place_on_conveyor: [place_on_conveyor]'"), ) diff --git a/nexus_demos/test_parallel_duplicated_wo.py b/nexus_demos/test_parallel_duplicated_wo.py index 1943b5c3..c26e50c6 100644 --- a/nexus_demos/test_parallel_duplicated_wo.py +++ b/nexus_demos/test_parallel_duplicated_wo.py @@ -28,15 +28,6 @@ class ParallelWoTest(NexusTestCase): @RosTestCase.timeout(60) async def asyncSetUp(self): - # todo(YV): Find a better fix to the problem below. - # zenoh-bridge was bumped to 0.72 as part of the upgrade to - # ROS 2 Iron. However with this upgrade, the bridge does not clearly - # terminate when a SIGINT is received leaving behind zombie bridge - # processes from previous test cases. As a result, workcell registration - # fails for this testcase due to multiple bridges remaining active. - # Hence we explicitly kill any zenoh processes before launching the test. - subprocess.Popen('pkill -9 -f zenoh', shell=True) - self.proc = managed_process( ("ros2", "launch", "nexus_demos", "launch.py"), ) diff --git a/nexus_demos/test_parallel_pick_and_place_rmf.py.disabled b/nexus_demos/test_parallel_pick_and_place_rmf.py.disabled index b98a54a1..b2c0cb4a 100644 --- a/nexus_demos/test_parallel_pick_and_place_rmf.py.disabled +++ b/nexus_demos/test_parallel_pick_and_place_rmf.py.disabled @@ -30,15 +30,6 @@ import subprocess class ParallelPickAndPlaceRmfTest(NexusTestCase): @RosTestCase.timeout(60) async def asyncSetUp(self): - # todo(YV): Find a better fix to the problem below. - # zenoh-bridge was bumped to 0.72 as part of the upgrade to - # ROS 2 Iron. However with this upgrade, the bridge does not clearly - # terminate when a SIGINT is received leaving behind zombie bridge - # processes from previous test cases. As a result, workcell registration - # fails for this testcase due to multiple bridges remaining active. - # Hence we explicitly kill any zenoh processes before launching the test. - subprocess.Popen('pkill -9 -f zenoh', shell=True) - self.proc = managed_process( ( "ros2", diff --git a/nexus_demos/test_parallel_wo.py b/nexus_demos/test_parallel_wo.py index dd8de44f..9cafb6e8 100644 --- a/nexus_demos/test_parallel_wo.py +++ b/nexus_demos/test_parallel_wo.py @@ -28,15 +28,6 @@ class ParallelWoTest(NexusTestCase): @RosTestCase.timeout(60) async def asyncSetUp(self): - # todo(YV): Find a better fix to the problem below. - # zenoh-bridge was bumped to 0.72 as part of the upgrade to - # ROS 2 Iron. However with this upgrade, the bridge does not clearly - # terminate when a SIGINT is received leaving behind zombie bridge - # processes from previous test cases. As a result, workcell registration - # fails for this testcase due to multiple bridges remaining active. - # Hence we explicitly kill any zenoh processes before launching the test. - subprocess.Popen('pkill -9 -f zenoh', shell=True) - self.proc = managed_process( ("ros2", "launch", "nexus_demos", "launch.py"), ) diff --git a/nexus_demos/test_pick_and_place.py b/nexus_demos/test_pick_and_place.py index 11d135e1..5a0fe084 100644 --- a/nexus_demos/test_pick_and_place.py +++ b/nexus_demos/test_pick_and_place.py @@ -30,15 +30,6 @@ class PickAndPlaceTest(NexusTestCase): @RosTestCase.timeout(60) async def asyncSetUp(self): - # todo(YV): Find a better fix to the problem below. - # zenoh-bridge was bumped to 0.72 as part of the upgrade to - # ROS 2 Iron. However with this upgrade, the bridge does not clearly - # terminate when a SIGINT is received leaving behind zombie bridge - # processes from previous test cases. As a result, workcell registration - # fails for this testcase due to multiple bridges remaining active. - # Hence we explicitly kill any zenoh processes before launching the test. - subprocess.Popen('pkill -9 -f zenoh', shell=True) - self.proc = managed_process( ("ros2", "launch", "nexus_demos", "launch.py"), ) diff --git a/nexus_demos/test_pick_and_place_rmf.py b/nexus_demos/test_pick_and_place_rmf.py index dbc2e100..4ddbdc0a 100644 --- a/nexus_demos/test_pick_and_place_rmf.py +++ b/nexus_demos/test_pick_and_place_rmf.py @@ -30,15 +30,6 @@ class PickAndPlaceRMFTest(NexusTestCase): @RosTestCase.timeout(60) async def asyncSetUp(self): - # todo(YV): Find a better fix to the problem below. - # zenoh-bridge was bumped to 0.72 as part of the upgrade to - # ROS 2 Iron. However with this upgrade, the bridge does not clearly - # terminate when a SIGINT is received leaving behind zombie bridge - # processes from previous test cases. As a result, workcell registration - # fails for this testcase due to multiple bridges remaining active. - # Hence we explicitly kill any zenoh processes before launching the test. - subprocess.Popen('pkill -9 -f zenoh', shell=True) - self.proc = managed_process( ( "ros2", diff --git a/nexus_network_configuration/CHANGELOG.rst b/nexus_network_configuration/CHANGELOG.rst deleted file mode 100644 index 67f2ff14..00000000 --- a/nexus_network_configuration/CHANGELOG.rst +++ /dev/null @@ -1,10 +0,0 @@ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Changelog for package nexus_network_configuration -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -0.1.1 (2023-11-20) ------------------- - -0.1.0 (2023-11-06) ------------------- -* Provides scripts to generate Zenoh DDS Bridge configurations. diff --git a/nexus_network_configuration/README.md b/nexus_network_configuration/README.md deleted file mode 100644 index bcddfd30..00000000 --- a/nexus_network_configuration/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Network Config Generator - -This package simplifies the Zenoh DDS Bridge setup for multiple NEXUS orchestrators through the generation of Zenoh bridge configurations from the following 2 files: -1. NEXUS Network Configuration: This YAML file describes the properties of different machines such as ROS Domain ID, Zenoh bridge TCP connection endpoints etc. Refer to [Quick Start](#quick-start) for an example -2. REDF Configuration: This YAML file describes the ROS Endpoints of the NEXUS orchestrators - -# First-time setup for deploying a LOCALHOST only environment - -1. Make sure that CycloneDDS is installed and made the default middleware, and that multicast on loopback interface is enabled for localhost-only communication. -``` -./scripts/set_up_network.sh -``` - -2. Create a NEXUS Network configuration. An example is provided in the `nexus_poc` package at [nexus_network_config.yaml](../nexus_poc/config/zenoh/nexus_network_config.yaml) Here we have 1 system orchestrator connected to 3 workcell orchestrators, all on different domain IDs from 14 to 17. -You can refer to [nexus_network_schema.json](schemas/nexus_network_schema.json) for the config schema. - -```yaml -# Mode of Zenoh bridge, specified as either 'client' or 'peer'. -# Use 'client' when there is an available Zenoh router, otherwise -# use 'peer' for a distributed system. -mode: peer -# When true, discovery info is forwarded to the remote plugins/bridges -forward_discovery: false -# When true, activates a REST API used to administer Zenoh Bridge configurations -enable_rest_api: true - -system_orchestrators: - - ros_namespace: system_orchestrator # ROS Namespace of the endpoints - # ROS Domain ID - domain_id: 14 - # Listening TCP Address of Zenoh bridge - tcp_listen: "localhost:7447" - # HTTP Port for the REST API - rest_api_http_port: 8000 - -workcell_orchestrators: - - ros_namespace: workcell_1 - domain_id: 15 - tcp_connect: "localhost:7447" - rest_api_http_port: 8001 - - - ros_namespace: workcell_2 - domain_id: 16 - tcp_connect: "localhost:7447" - rest_api_http_port: 8002 - - - ros_namespace: workcell_3 - domain_id: 17 - tcp_connect: "localhost:7447" - rest_api_http_port: 8003 -``` - -3. Generate zenoh bridge configurations from NEXUS Network config and REDF config. The `ZENOH_CONFIGS_OUTPUT_DIRECTORY` will be where your zenoh bridge configurations will be saved. -```bash -ros2 run nexus_network_configuration nexus_network_configuration -n -r -o -``` diff --git a/nexus_network_configuration/nexus_network_configuration/__init__.py b/nexus_network_configuration/nexus_network_configuration/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/nexus_network_configuration/nexus_network_configuration/nexus_network_configuration.py b/nexus_network_configuration/nexus_network_configuration/nexus_network_configuration.py deleted file mode 100644 index 7eb10649..00000000 --- a/nexus_network_configuration/nexus_network_configuration/nexus_network_configuration.py +++ /dev/null @@ -1,339 +0,0 @@ -# Copyright 2022 Johnson & Johnson -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import argparse -import json -import re -import os -from jsonschema import validate, ValidationError -import sys -import yaml -from ament_index_python.packages import get_package_share_directory - - -class NEXUSConfigGenerator: - """NEXUSConfigGenerator generates Zenoh DDS Bridge configurations.""" - - def __init__( - self, nexus_net_cfg_filepath, - redf_cfg_filepath, schema_filepath): - """ - Construct NEXUSConfigGenerator. - - Parameters - ---------- - nexus_net_cfg_filepath : str - NEXUS network configuration filepath - redf_cfg_filepath : str - REDF configuration filepath - schema_filepath : str - NEXUS Network configuration schema filepath - - """ - self.nexus_net_cfg = self.parse_file(nexus_net_cfg_filepath) - self.redf_cfg = self.parse_file(redf_cfg_filepath) - - self.nexus_net_cfg_schema = self.parse_file(schema_filepath) - - # Set global variables - self.enable_rest_api = self.nexus_net_cfg["enable_rest_api"] - self.bridge_mode = self.nexus_net_cfg["mode"] - - self.zenoh_cfg_file_extension = "json5" - - def parse_file(self, filepath): - """ - Parse a configuration file (YAML/JSON) into a python dictionary. - - Parameters - ---------- - filepath : str - filepath to configuration to parse - - Returns - ------- - dict - Dictionary parsed from given file - - """ - if filepath is None: - return {} - - try: - file_name, file_extension = os.path.splitext(filepath) - dict_obj = {} - if file_extension == ".json": - with open(filepath, "r", encoding="utf-8") as reader: - dict_obj = json.load(reader) - elif file_extension == ".yaml": - with open(filepath, "rt", encoding="utf-8") as reader: - dict_obj = yaml.safe_load(reader.read()) - else: - print( - f"ERROR: File {file_name} is neither of extension '.json' \ - 'nor '.yaml'") - return None - return dict_obj - except Exception as e: - print(f"Caught exception when parsing file: {filepath}. What: {e}") - sys.exit(1) - - def validate_nexus_net_cfg_schema(self): - """ - Check if NEXUS Network configuration follows the pre-defined schema. - - Returns - ------- - bool - If true, NEXUS Network configuration follows the schema. - - """ - try: - validate(self.nexus_net_cfg, schema=self.nexus_net_cfg_schema) - return True - except ValidationError as validation_err: - print("NEXUS Network configuration schema validation failed") - print(f"Validation error: {validation_err}") - - return False - - def zenoh_cfg( - self, - orchestrator, - allowed_endpoints, - tcp_connect, - tcp_listen - ): - """ - Configure and return a Zenoh bridge from given parameters. - - Parameters - ---------- - orchestrator : dict - Orchestrator dictionary with configuration specific to it - allowed_endpoints : str - ROS endpoints that will be allowed through the Zenoh bridge - tcp_connect : str - TCP Addresses of Zenoh bridges to connect to - tcp_listen : str - TCP Addresses of Zenoh bridges to listen to - - Returns - ------- - dict - Python dictionary of Zenoh bridge configuration - - """ - # Prepend "tcp/" to connect and listen endpoints to denote TCP Protocol - tcp_connect_endpoints = \ - [("tcp/" + addr) for addr in tcp_connect] - tcp_listen_endpoints = \ - [("tcp/" + addr) for addr in tcp_listen] - - zenoh_dict = { - "plugins": { - "ros2dds": { - "domain": orchestrator["domain_id"], - "namespace": orchestrator["namespace"], - "allow": orchestrator["allow"], - "queries_timeout": orchestrator["queries_timeout"], - }, - }, - "mode": self.bridge_mode, - "connect": {"endpoints": tcp_connect_endpoints}, - "listen": {"endpoints": tcp_listen_endpoints}, - } - - if self.enable_rest_api: - zenoh_dict["plugins"]["rest"] = { - "http_port": orchestrator["rest_api_http_port"] - } - - return zenoh_dict - - def generate_zenoh_config(self, output_dir): - """ - Generate Zenoh bridge configs and output to directory 'output_dir'. - - Parameters - ---------- - output_dir : str - Output directory for Zenoh configurations - - """ - nexus_net_cfg = self.nexus_net_cfg - - orchestrators_zenoh_cfg = [] - - allowed_endpoints = self.generate_allowed_endpoints( - self.redf_cfg) - - # Generate system orchestrator zenoh dictionary - for orchestrator in nexus_net_cfg["system_orchestrators"]: - - zenoh_cfg = self.zenoh_cfg( - orchestrator=orchestrator, - allowed_endpoints=allowed_endpoints, - tcp_connect=[], - tcp_listen=orchestrator["tcp_listen"] - ) - - orchestrators_zenoh_cfg.append(zenoh_cfg) - - # Generate workcell orchestrators zenoh dictionary - for orchestrator in nexus_net_cfg["workcell_orchestrators"]: - - zenoh_cfg = self.zenoh_cfg( - orchestrator=orchestrator, - allowed_endpoints=allowed_endpoints, - tcp_connect=orchestrator["tcp_connect"], - tcp_listen=[] - ) - - orchestrators_zenoh_cfg.append(zenoh_cfg) - - # Write system orchestrator config to file - if not os.path.exists(output_dir): - os.makedirs(output_dir) - for zenoh_cfg in orchestrators_zenoh_cfg: - write_filepath = os.path.join( - output_dir, - zenoh_cfg["plugins"]["ros2dds"]["namespace"] - + "." - + self.zenoh_cfg_file_extension, - ) - del zenoh_cfg["plugins"]["ros2dds"]["namespace"] - self.write_to_json(write_filepath, zenoh_cfg) - print(f"Generated Zenoh configuration at {write_filepath}") - - def generate_allowed_endpoints(self, - redf_cfg - ): - """ - Generate allowed endpoints string from REDF configuration. - - This string is used to only allow selected ROS endpoints through the - Zenoh bridges. - - Parameters - ---------- - redf_cfg : dict - REDF Configuration dictionary - - Returns - ------- - str - Allowed endpoints string - - """ - allowed_endpoints = [] - - for endpoint in redf_cfg.get('endpoints', []): - if endpoint.get("topic"): - allowed_endpoints.append(endpoint["topic"]) - elif endpoint.get("action_name"): - allowed_endpoints.append(endpoint["action_name"]) - elif endpoint.get("service_name"): - allowed_endpoints.append(endpoint["service_name"]) - - allowed_endpoints_str = "" - for endpoint_str in allowed_endpoints: - # Searches for namespace defined in curly braces in REDF endpoints. - namespace_search = re.compile(r'\{\w*\}') - namespace_found = namespace_search.search(endpoint_str) - if namespace_found: - endpoint_str = namespace_search.sub(".*", endpoint_str) - allowed_endpoints_str += endpoint_str + "|" - - # Remove the last "|" character - allowed_endpoints_str = allowed_endpoints_str[:-1] - - return allowed_endpoints_str - - def write_to_json(self, filepath, cfg_dict): - """ - Write python dictionary to to a JSON File. - - Parameters - ---------- - filepath : str - Filepath to write to - cfg_dict : dict - Zenoh configuration - - """ - with open(filepath, "wt", encoding="utf-8") as writer: - json.dump(cfg_dict, writer, indent=2, default=str) - - -def main(argv=sys.argv): - """Entrypoint.""" - - parser = argparse.ArgumentParser( - description="Generate Zenoh configurations from a NEXUS Network \ - and REDF Configuration", - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - ) - - parser.add_argument( - '-n', - '--nexus_net_cfg', - required=True, - type=str, - help="The NEXUS Network configuration filepath") - - parser.add_argument( - '-r', - '--redf_cfg', - required=False, - type=str, - help="The REDF configuration filepath") - - parser.add_argument( - "-o", - "--output", - required=True, - type=str, - help="Output directory for Zenoh bridge configurations", - ) - - args = parser.parse_args(argv[1:]) - - # Get REDF yaml filepath - redf_cfg_filepath = args.redf_cfg - # Get Network configuration yaml filepath - nexus_net_cfg_filepath = args.nexus_net_cfg - # Get schema filepath - schema_filepath = os.path.join( - get_package_share_directory("nexus_network_configuration"), - "schemas", - "nexus_network_schema.json" - ) - - nexus_config_gen = NEXUSConfigGenerator( - nexus_net_cfg_filepath, redf_cfg_filepath, schema_filepath - ) - - if not nexus_config_gen.validate_nexus_net_cfg_schema(): - print( - "NEXUS Network configuration does not follow the schema. Please \ - check again before generating the Zenoh configs" - ) - return - - nexus_config_gen.generate_zenoh_config(args.output) - - -if __name__ == "__main__": - main(sys.argv) diff --git a/nexus_network_configuration/package.xml b/nexus_network_configuration/package.xml deleted file mode 100644 index a7414ac1..00000000 --- a/nexus_network_configuration/package.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - nexus_network_configuration - 0.1.1 - This package generates zenoh bridge configurations for the NEXUS Network - john - Apache License 2.0 - - python3-jsonschema - python3-yaml - - - ament_python - - diff --git a/nexus_network_configuration/resource/nexus_network_configuration b/nexus_network_configuration/resource/nexus_network_configuration deleted file mode 100644 index e69de29b..00000000 diff --git a/nexus_network_configuration/schemas/nexus_network_schema.json b/nexus_network_configuration/schemas/nexus_network_schema.json deleted file mode 100644 index c6cfc8d5..00000000 --- a/nexus_network_configuration/schemas/nexus_network_schema.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://raw.githubusercontent.com/osrf/nexus/main/nexus_network_configuration/schemas/nexus_network_schema.json", - "title": "NEXUS Network Configuration", - "description": "A network configuration of the NEXUS Orchestrators", - "type": "object", - "required": [ - "mode", - "enable_rest_api", - "system_orchestrators", - "workcell_orchestrators" - ], - "properties": { - "mode":{ - "description": "Mode of Zenoh bridge, specified as either 'client' or 'peer'. Use 'client' when there is an available Zenoh router, otherwise use 'peer' for a distributed system.", - "type": "string", - "enum": ["peer", "client"] - }, - "enable_rest_api":{ - "description": "When true, activates a REST API used to administer Zenoh Bridge configurations", - "type": "boolean" - }, - "system_orchestrators": { - "type": "array", - "items": { "$ref": "#/$defs/system_orchestrator_unit"} - }, - "workcell_orchestrators": { - "type": "array", - "items": { "$ref": "#/$defs/workcell_orchestrator_unit"} - } - }, - - "$defs": { - "queries_timeout": { - "description": "Timeout parameters for Zenoh queries", - "type": "object", - "properties": { - "default": { - "type": "number" - } - } - }, - "allow": { - "description": "Additional endpoints to allow, these could be endpoints not defined in REDF but are necessary for lifecycle transitions", - "type": "object", - "properties": { - "subscribers": { - "type": "array" - }, - "publishers": { - "type": "array" - }, - "service_servers": { - "type": "array" - }, - "service_clients": { - "type": "array" - }, - "action_servers": { - "type": "array" - }, - "action_clients": { - "type": "array" - } - } - }, - "system_orchestrator_unit": { - "type": "object", - "required": ["namespace", "domain_id", "tcp_listen"], - "properties": { - "namespace":{ - "$ref": "#/$defs/namespace" - }, - "domain_id":{ - "$ref": "#/$defs/domain_id" - }, - "tcp_listen":{ - "$ref": "#/$defs/tcp_listen" - }, - "rest_api_http_port":{ - "$ref": "#/$defs/rest_api_http_port" - }, - "allow":{ - "$ref": "#/$defs/allow" - }, - "queries_timeout":{ - "$ref": "#/$defs/queries_timeout" - } - } - }, - "workcell_orchestrator_unit": { - "type": "object", - "required": ["namespace", "domain_id", "tcp_connect"], - "properties": { - "namespace":{ - "$ref": "#/$defs/namespace" - }, - "domain_id":{ - "$ref": "#/$defs/domain_id" - }, - "tcp_connect":{ - "$ref": "#/$defs/tcp_connect" - }, - "rest_api_http_port":{ - "$ref": "#/$defs/rest_api_http_port" - }, - "allow":{ - "$ref": "#/$defs/allow" - }, - "queries_timeout":{ - "$ref": "#/$defs/queries_timeout" - } - } - }, - "namespace":{ - "description": "ROS Namespace of the endpoints", - "type": "string" - }, - "domain_id":{ - "description": "ROS Domain ID", - "type": "integer", - "minimum": 0, - "maximum": 232 - }, - "tcp_connect":{ - "description": "TCP Address of system orchestrator Zenoh bridge", - "type": "array" - }, - "tcp_listen":{ - "description": "TCP Address of system orchestrator Zenoh bridge", - "type": "array" - }, - "rest_api_http_port":{ - "description": "HTTP Port for the REST API", - "type": "integer", - "minimum": 0, - "maximum": 65535 - } - } -} diff --git a/nexus_network_configuration/scripts/set_up_network.sh b/nexus_network_configuration/scripts/set_up_network.sh deleted file mode 100755 index 76e64a6d..00000000 --- a/nexus_network_configuration/scripts/set_up_network.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash - -# This script does the following: -# 1. Install CycloneDDS (Iron dist.) if not already installed -# 2. Change RMW_IMPLEMENTATION to CycloneDDS -# 3. Enable multicast on loopback interface - -RMW_PACKAGE="ros-jazzy-rmw-cyclonedds-cpp" - -PKG_OK=$(dpkg-query -W --showformat='${Status}\n' $RMW_PACKAGE|grep "install ok installed") - -echo "Checking if $REQUIRED_PKG is installed: $PKG_OK" - -if [ "$PKG_OK" = "" ]; then - while true; do - read -p "$RMW_PACKAGE not installed. Would you like to install it? (y/n)" yn - case $yn in - [Yy]* ) sudo apt-get --yes install $RMW_PACKAGE; break;; - [Nn]* ) exit;; - * ) echo "Please answer yes or no.";; - esac - done -fi - -while true; do - read -p "Restart ROS Daemon and set RMW_IMPLEMENTATION to 'rmw_cyclonedds_cpp'? (y/n)" yn - case $yn in - [Yy]* ) - ros2 daemon stop; - echo "Stopped ROS2 Daemon" - export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp; - ros2 daemon start; - echo "Started ROS2 Daemon" - break;; - [Nn]* ) break;; - * ) echo "Please answer yes or no.";; - esac -done - -while true; do - read -p "Add route to enable multicast on loopback interface? (y/n)" yn - case $yn in - [Yy]* ) - # Enable multicast on loopback interface - sudo ifconfig lo multicast; - sudo route add -net 224.0.0.0 netmask 240.0.0.0 dev lo; - echo "Enabled multicast on loopback interface" - break;; - [Nn]* ) break;; - * ) echo "Please answer yes or no.";; - esac -done - -while true; do - read -p "Set ROS_LOCALHOST_ONLY to 1? (y/n)" yn - case $yn in - [Yy]* ) - # Export environment variables for configuring LOCALHOST only communication - export ROS_LOCALHOST_ONLY=1; - echo "ROS_LOCALHOST_ONLY set to 1" - break;; - [Nn]* ) exit;; - * ) echo "Please answer yes or no.";; - esac -done diff --git a/nexus_network_configuration/setup.cfg b/nexus_network_configuration/setup.cfg deleted file mode 100644 index c96afa11..00000000 --- a/nexus_network_configuration/setup.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[develop] -script_dir=$base/lib/nexus_network_configuration -[install] -install_scripts=$base/lib/nexus_network_configuration diff --git a/nexus_network_configuration/setup.py b/nexus_network_configuration/setup.py deleted file mode 100644 index c8dd9efa..00000000 --- a/nexus_network_configuration/setup.py +++ /dev/null @@ -1,32 +0,0 @@ -import os -from glob import glob -from setuptools import setup - -package_name = 'nexus_network_configuration' - -setup( - name=package_name, - version='0.1.1', - packages=[package_name], - data_files=[ - ('share/ament_index/resource_index/packages', - [os.path.join('resource', package_name)]), - (os.path.join('share', package_name), ['package.xml']), - (os.path.join('share', package_name, 'schemas'), - glob('schemas/*.json')), - ], - install_requires=['setuptools'], - zip_safe=True, - maintainer='john', - maintainer_email='johntan@openrobotics.org', - description='This package generates zenoh bridge configurations for the \ - NEXUS Network', - license='Apache License 2.0', - tests_require=['pytest'], - entry_points={ - 'console_scripts': [ - 'nexus_network_configuration = \ - nexus_network_configuration.nexus_network_configuration:main' - ], - }, -) diff --git a/nexus_system_orchestrator/CMakeLists.txt b/nexus_system_orchestrator/CMakeLists.txt index fb152bf5..c7d2f993 100644 --- a/nexus_system_orchestrator/CMakeLists.txt +++ b/nexus_system_orchestrator/CMakeLists.txt @@ -49,6 +49,7 @@ target_compile_features(${PROJECT_NAME}_plugin PRIVATE cxx_std_17) rclcpp_components_register_node(${PROJECT_NAME}_plugin PLUGIN "nexus::system_orchestrator::SystemOrchestrator" EXECUTABLE nexus_system_orchestrator + EXECUTOR EventsExecutor ) install(TARGETS ${PROJECT_NAME}_plugin DESTINATION lib) diff --git a/nexus_transporter/CMakeLists.txt b/nexus_transporter/CMakeLists.txt index 2750ce92..22f5a3c7 100644 --- a/nexus_transporter/CMakeLists.txt +++ b/nexus_transporter/CMakeLists.txt @@ -106,7 +106,7 @@ target_compile_features(nexus_transporter_component INTERFACE cxx_std_17) rclcpp_components_register_node(nexus_transporter_component PLUGIN "nexus_transporter::TransporterNode" EXECUTABLE nexus_transporter_node - EXECUTOR SingleThreadedExecutor) + EXECUTOR EventsExecutor) #=============================================================================== # Builtin transporter plugins diff --git a/nexus_workcell_orchestrator/CMakeLists.txt b/nexus_workcell_orchestrator/CMakeLists.txt index 0f687425..8e030c22 100644 --- a/nexus_workcell_orchestrator/CMakeLists.txt +++ b/nexus_workcell_orchestrator/CMakeLists.txt @@ -70,6 +70,7 @@ target_compile_definitions(${PROJECT_NAME}_plugin PUBLIC "COMPOSITION_BUILDING_D rclcpp_components_register_node(${PROJECT_NAME}_plugin PLUGIN "nexus::workcell_orchestrator::WorkcellOrchestrator" EXECUTABLE nexus_workcell_orchestrator + EXECUTOR EventsExecutor ) install(TARGETS ${PROJECT_NAME}_plugin DESTINATION lib) diff --git a/nexus_zenoh_bridge_dds_vendor/CHANGELOG.rst b/nexus_zenoh_bridge_dds_vendor/CHANGELOG.rst deleted file mode 100644 index 0808a004..00000000 --- a/nexus_zenoh_bridge_dds_vendor/CHANGELOG.rst +++ /dev/null @@ -1,10 +0,0 @@ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Changelog for package nexus_zenoh_bridge_dds_vendor -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -0.1.1 (2023-11-20) ------------------- - -0.1.0 (2023-11-06) ------------------- -* A package to vendor the Zenoh DDS bridge. diff --git a/nexus_zenoh_bridge_dds_vendor/CMakeLists.txt b/nexus_zenoh_bridge_dds_vendor/CMakeLists.txt deleted file mode 100644 index 13e508cc..00000000 --- a/nexus_zenoh_bridge_dds_vendor/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -project(nexus_zenoh_bridge_dds_vendor) - -# Default to C++17 -if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 17) - set(CMAKE_CXX_STANDARD_REQUIRED ON) -endif() - -find_package(ament_cmake REQUIRED) -find_package(ament_cmake_vendor_package REQUIRED) - -ament_vendor(zeno_bridge_dds_vendor - VCS_URL https://github.com/eclipse-zenoh/zenoh-plugin-ros2dds.git - VCS_VERSION 1.0.3 -) - -# TODO(sloretz) make a nice way to get this path from ament_vendor -set(INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/zeno_bridge_dds_vendor-prefix/install") -install( - DIRECTORY "${INSTALL_DIR}/lib/zenoh_bridge_ros2dds/" - DESTINATION "lib/${PROJECT_NAME}" - USE_SOURCE_PERMISSIONS -) - -ament_package() diff --git a/nexus_zenoh_bridge_dds_vendor/package.xml b/nexus_zenoh_bridge_dds_vendor/package.xml deleted file mode 100644 index 79bb1bcc..00000000 --- a/nexus_zenoh_bridge_dds_vendor/package.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - nexus_zenoh_bridge_dds_vendor - 0.1.1 - Newer version of zenoh_bridge_dds for NEXUS - Teo Koon Peng - Apache License 2.0 - - ament_cmake - ament_cmake_vendor_package - - cargo - clang - - - ament_cmake - -