diff --git a/.github/workflows/ros-ci.yml b/.github/workflows/ros-ci.yml index 37fa96f..60a338e 100644 --- a/.github/workflows/ros-ci.yml +++ b/.github/workflows/ros-ci.yml @@ -57,9 +57,9 @@ jobs: echo "--- Updating rosdep definitions ---" rosdep update echo "--- Installing system dependencies for ROS 2 ${{ matrix.ros_distribution }} ---" - rosdep install --from-paths ros_ws/src --ignore-src -y -r --rosdistro ${{ matrix.ros_distribution }} + rosdep install --from-paths ros_ws/src --ignore-src -y -r --rosdistro ${{ matrix.ros_distribution }} --skip-keys dynamixel_hardware_interface echo "--- Performing rosdep check for ROS 2 ${{ matrix.ros_distribution }} ---" - if rosdep check --from-paths ros_ws/src --ignore-src --rosdistro ${{ matrix.ros_distribution }}; then + if rosdep check --from-paths ros_ws/src --ignore-src --rosdistro ${{ matrix.ros_distribution }} --skip-keys dynamixel_hardware_interface; then echo "--- rosdep check passed ---" else echo "--- rosdep check failed: Missing system dependencies or unresolvable keys. ---" @@ -71,8 +71,9 @@ jobs: with: target-ros2-distro: ${{ matrix.ros_distribution }} - vcs-repo-file-url: "" + vcs-repo-file-url: "https://raw.githubusercontent.com/ROBOTIS-GIT/dynamixel_hardware_interface_demos/${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}/dynamixel_hardware_interface_demos_ci.repos" package-name: | dynamixel_hardware_interface_demos dynamixel_hardware_interface_example dynamixel_hardware_interface_example_1 + dynamixel_hardware_interface_example_2 diff --git a/README.md b/README.md index 290d9d2..6e0ac14 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # dynamixel_hardware_interface_demos -This repository collects example packages and resources for working with Dynamixel hardware using the ros2_control framework. +This repository collects example packages and resources for working with Dynamixel hardware using the ros2_control framework. ## Overview This repository is intended to help users get started with Dynamixel hardware integration in ROS 2. It provides example configurations, launch files, and scripts to demonstrate how to use the `dynamixel_hardware_interface` with ros2_control and controller_manager. @@ -11,6 +11,8 @@ This repository is intended to help users get started with Dynamixel hardware in - Example package with configuration files, launch files, and scripts for setting up and running a Dynamixel-based robot system using ros2_control. - [dynamixel_hardware_interface_example_1](dynamixel_hardware_interface_example_1/README.md) - Example package demonstrating a dual Dynamixel system (two buses) with separate ros2_control configurations and a dedicated launch file for running both systems together. +- [dynamixel_hardware_interface_example_2](dynamixel_hardware_interface_example_2/README.md) + - Example package demonstrating a system with an Dynamixel Protocol compatible IMU sensor using the dynamixel_hardware_interface. ## Getting Started diff --git a/docker/Dockerfile b/docker/Dockerfile index 82861bc..4d7f0de 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -11,7 +11,8 @@ RUN mkdir -p ${COLCON_WS}/src && \ git clone -b jazzy https://github.com/ROBOTIS-GIT/dynamixel_hardware_interface.git && \ git clone -b jazzy https://github.com/ROBOTIS-GIT/dynamixel_interfaces.git && \ git clone -b jazzy https://github.com/ROBOTIS-GIT/DynamixelSDK.git && \ - git clone -b jazzy https://github.com/ROBOTIS-GIT/dynamixel_hardware_interface_demos.git + git clone -b jazzy https://github.com/ROBOTIS-GIT/dynamixel_hardware_interface_demos.git && \ + git clone -b jazzy https://github.com/ros2/rmw_zenoh.git RUN cd ${COLCON_WS} && \ apt-get update && \ diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index aeb29b5..66201fd 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -14,11 +14,15 @@ services: rttime: -1 memlock: 8428281856 network_mode: host + ipc: host + pid: host environment: - DISPLAY=${DISPLAY} - QT_X11_NO_MITSHM=1 + # - RMW_IMPLEMENTATION=rmw_zenoh_cpp volumes: - /dev:/dev + - /dev/shm:/dev/shm - ./workspace:/workspace - /tmp/.X11-unix:/tmp/.X11-unix:rw - /tmp/.docker.xauth:/tmp/.docker.xauth:rw diff --git a/dynamixel_hardware_interface_demos/CHANGELOG.rst b/dynamixel_hardware_interface_demos/CHANGELOG.rst index 6af1f08..92f3c5a 100644 --- a/dynamixel_hardware_interface_demos/CHANGELOG.rst +++ b/dynamixel_hardware_interface_demos/CHANGELOG.rst @@ -2,6 +2,11 @@ Changelog for package dynamixel_hardware_interface_demos ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +0.0.4 (2025-12-15) +------------------ +* Introduced dynamixel_hardware_interface_example_2 package for using the dynamixel_hardware_interface with imu sensor +* Contributors: Woojin Wie + 0.0.3 (2025-09-12) ------------------ * Updated package.xml to include dynamixel_hardware_interface_example_1 diff --git a/dynamixel_hardware_interface_demos/package.xml b/dynamixel_hardware_interface_demos/package.xml index c61a049..1bfb59f 100644 --- a/dynamixel_hardware_interface_demos/package.xml +++ b/dynamixel_hardware_interface_demos/package.xml @@ -15,6 +15,7 @@ ament_cmake dynamixel_hardware_interface_example dynamixel_hardware_interface_example_1 + dynamixel_hardware_interface_example_2 ament_cmake diff --git a/dynamixel_hardware_interface_demos_ci.repos b/dynamixel_hardware_interface_demos_ci.repos index e69de29..c09f3bd 100644 --- a/dynamixel_hardware_interface_demos_ci.repos +++ b/dynamixel_hardware_interface_demos_ci.repos @@ -0,0 +1,5 @@ +repositories: + utils/dynamixel_hardware_interface: + type: git + url: https://github.com/ROBOTIS-GIT/dynamixel_hardware_interface.git + version: main diff --git a/dynamixel_hardware_interface_example/CHANGELOG.rst b/dynamixel_hardware_interface_example/CHANGELOG.rst index 274a180..7d3f676 100644 --- a/dynamixel_hardware_interface_example/CHANGELOG.rst +++ b/dynamixel_hardware_interface_example/CHANGELOG.rst @@ -2,6 +2,10 @@ Changelog for package dynamixel_hardware_interface_example ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +0.0.4 (2025-12-15) +------------------ +* None + 0.0.3 (2025-09-12) ------------------ * Introduced xacro arguments for port_name and baud_rate diff --git a/dynamixel_hardware_interface_example/README.md b/dynamixel_hardware_interface_example/README.md index 57a1e51..d22e595 100644 --- a/dynamixel_hardware_interface_example/README.md +++ b/dynamixel_hardware_interface_example/README.md @@ -35,11 +35,11 @@ You can generate custom XACRO files for your robot using the provided script and ```bash ros2 launch dynamixel_hardware_interface_example generate_xacros.launch.py \ num_joints:=4 \ - baudrate:=4000000 \ + baud_rate:=4000000 \ port_name:=/dev/ttyUSB0 ``` - `num_joints`: Number of joints/motors in your robot -- `baudrate`: Baudrate for the Dynamixel port +- `baud_rate`: Baudrate for the Dynamixel port - `port_name`: Serial port for the Dynamixel device This will generate new XACRO files in the `config/` directory. @@ -48,7 +48,9 @@ This will generate new XACRO files in the `config/` directory. After generating the XACRO files, launch the hardware interface and controller manager: ```bash -ros2 launch dynamixel_hardware_interface_example hardware.launch.py +ros2 launch dynamixel_hardware_interface_example hardware.launch.py \ + baud_rate:=4000000 \ + port_name:=/dev/ttyUSB0 ``` ## Configuration Files diff --git a/dynamixel_hardware_interface_example/dynamixel_hardware_interface_example/generate_xacros.py b/dynamixel_hardware_interface_example/dynamixel_hardware_interface_example/generate_xacros.py index 8196943..c67acc1 100755 --- a/dynamixel_hardware_interface_example/dynamixel_hardware_interface_example/generate_xacros.py +++ b/dynamixel_hardware_interface_example/dynamixel_hardware_interface_example/generate_xacros.py @@ -22,7 +22,7 @@ # Generate the .ros2_control.xacro file -def generate_ros2_control_xacro(num_joints, filename, baudrate, port_name, command_interface): +def generate_ros2_control_xacro(num_joints, filename, baud_rate, port_name, command_interface): # Determine the appropriate interface names based on command_interface if command_interface == 'position': joint_command_interface = 'position' @@ -101,13 +101,13 @@ def generate_ros2_control_xacro(num_joints, filename, baudrate, port_name, comma # Generate the .urdf.xacro file -def generate_urdf_xacro(num_joints, filename, baudrate, port_name): +def generate_urdf_xacro(num_joints, filename, baud_rate, port_name): with open(filename, 'w') as f: f.write('\n') f.write('\n') f.write(' \n') # Provide xacro args for baud_rate and port_name with defaults from CLI - f.write(f' \n') + f.write(f' \n') f.write(f' \n') f.write(' \n') f.write('\n') @@ -134,7 +134,7 @@ def generate_urdf_xacro(num_joints, filename, baudrate, port_name): def main(): if len(sys.argv) < 2: print('Usage: python generate_dynamixel_xacros.py ' - '[output_dir] [baudrate] [port_name] [command_interface]') + '[output_dir] [baud_rate] [port_name] [command_interface]') sys.exit(1) try: num_joints = int(sys.argv[1]) @@ -142,13 +142,13 @@ def main(): print('First argument must be an integer (number of joints)') sys.exit(1) config_dir = os.path.join(os.path.dirname(__file__), 'config') - baudrate = '4000000' + baud_rate = '4000000' port_name = '/dev/ttyUSB0' command_interface = 'position' # Default value if len(sys.argv) >= 3: config_dir = sys.argv[2] if len(sys.argv) >= 4: - baudrate = sys.argv[3] + baud_rate = sys.argv[3] if len(sys.argv) >= 5: port_name = sys.argv[4] if len(sys.argv) >= 6: @@ -156,11 +156,11 @@ def main(): os.makedirs(config_dir, exist_ok=True) ros2_control_path = os.path.join(config_dir, 'dynamixel_system.ros2_control.xacro') urdf_xacro_path = os.path.join(config_dir, 'dynamixel_system.urdf.xacro') - generate_ros2_control_xacro(num_joints, ros2_control_path, baudrate, + generate_ros2_control_xacro(num_joints, ros2_control_path, baud_rate, port_name, command_interface) - generate_urdf_xacro(num_joints, urdf_xacro_path, baudrate, port_name) + generate_urdf_xacro(num_joints, urdf_xacro_path, baud_rate, port_name) print(f'Generated xacro files for {num_joints} joints in {config_dir}. ' - f'Baudrate: {baudrate}, Port: {port_name}, Command Interface: {command_interface}') + f'baud_rate: {baud_rate}, Port: {port_name}, Command Interface: {command_interface}') if __name__ == '__main__': diff --git a/dynamixel_hardware_interface_example/launch/generate_xacros.launch.py b/dynamixel_hardware_interface_example/launch/generate_xacros.launch.py index 8af0afe..2d6660c 100644 --- a/dynamixel_hardware_interface_example/launch/generate_xacros.launch.py +++ b/dynamixel_hardware_interface_example/launch/generate_xacros.launch.py @@ -42,7 +42,7 @@ def generate_launch_description(): description='Directory to save generated xacro files.' ), DeclareLaunchArgument( - 'baudrate', + 'baud_rate', default_value='4000000', description='Baudrate for the Dynamixel port.' ), @@ -60,7 +60,7 @@ def generate_launch_description(): num_joints = LaunchConfiguration('num_joints') output_dir = LaunchConfiguration('output_dir') - baudrate = LaunchConfiguration('baudrate') + baud_rate = LaunchConfiguration('baud_rate') port_name = LaunchConfiguration('port_name') command_interface = LaunchConfiguration('command_interface') @@ -69,7 +69,7 @@ def generate_launch_description(): executable='generate_xacros', name='generate_xacros', output='screen', - arguments=[num_joints, output_dir, baudrate, port_name, command_interface], + arguments=[num_joints, output_dir, baud_rate, port_name, command_interface], ) return LaunchDescription(declared_arguments + [generate_xacros_node]) diff --git a/dynamixel_hardware_interface_example/setup.py b/dynamixel_hardware_interface_example/setup.py index b3854af..45dc979 100644 --- a/dynamixel_hardware_interface_example/setup.py +++ b/dynamixel_hardware_interface_example/setup.py @@ -8,7 +8,7 @@ setup( name=package_name, - version='0.0.3', + version='0.0.4', packages=find_packages(exclude=['test']), data_files=[ ('share/ament_index/resource_index/packages', ['resource/' + package_name]), diff --git a/dynamixel_hardware_interface_example_1/CHANGELOG.rst b/dynamixel_hardware_interface_example_1/CHANGELOG.rst index 694365a..bfeb773 100644 --- a/dynamixel_hardware_interface_example_1/CHANGELOG.rst +++ b/dynamixel_hardware_interface_example_1/CHANGELOG.rst @@ -2,6 +2,10 @@ Changelog for package dynamixel_hardware_interface_example_1 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +0.0.4 (2025-12-15) +------------------ +* None + 0.0.3 (2025-09-12) ------------------ * Introduced dynamixel_hardware_interface_example_1 package for dual ros2_control diff --git a/dynamixel_hardware_interface_example_1/setup.py b/dynamixel_hardware_interface_example_1/setup.py index a2c21fc..01a9056 100644 --- a/dynamixel_hardware_interface_example_1/setup.py +++ b/dynamixel_hardware_interface_example_1/setup.py @@ -8,7 +8,7 @@ setup( name=package_name, - version='0.0.3', + version='0.0.4', packages=find_packages(exclude=['test']), data_files=[ ('share/ament_index/resource_index/packages', ['resource/' + package_name]), diff --git a/dynamixel_hardware_interface_example_2/CHANGELOG.rst b/dynamixel_hardware_interface_example_2/CHANGELOG.rst new file mode 100644 index 0000000..e5c418a --- /dev/null +++ b/dynamixel_hardware_interface_example_2/CHANGELOG.rst @@ -0,0 +1,8 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package dynamixel_hardware_interface_example_2 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +0.0.4 (2025-12-15) +------------------ +* Added example package for using the dynamixel_hardware_interface with imu sensor +* Contributors: Woojin Wie diff --git a/dynamixel_hardware_interface_example_2/README.md b/dynamixel_hardware_interface_example_2/README.md new file mode 100644 index 0000000..0afe755 --- /dev/null +++ b/dynamixel_hardware_interface_example_2/README.md @@ -0,0 +1,19 @@ +# Dynamixel Hardware Interface Example 2 (IMU ros2_control) + +This example shows a single `ros2_control` system that uses `dynamixel_hardware_interface` to read an IMU (ID 1) and publish its data through an `imu_sensor_broadcaster`. The robot description is built from `config/dynamixel_system.urdf.xacro`, which wraps the ros2_control configuration defined in `config/dynamixel_system.ros2_control.xacro`. + +## Usage + +Build and source your workspace, then launch: + +```bash +ros2 launch dynamixel_hardware_interface_example_2 hardware.launch.py \ + port_name:=/dev/ttyUSB0 \ + baud_rate:=4000000 +``` + +### Launch arguments +- `port_name` (default `/dev/ttyUSB0`): Serial port connected to the Dynamixel IMU. +- `baud_rate` (default `4000000`): Baud rate for the port. +- `description_file` (default `dynamixel_system.urdf.xacro`): Xacro to generate the robot description. +- `prefix` (default empty): Optional prefix for joint/frame names. diff --git a/dynamixel_hardware_interface_example_2/config/dynamixel_system.ros2_control.xacro b/dynamixel_hardware_interface_example_2/config/dynamixel_system.ros2_control.xacro new file mode 100644 index 0000000..f60738c --- /dev/null +++ b/dynamixel_hardware_interface_example_2/config/dynamixel_system.ros2_control.xacro @@ -0,0 +1,51 @@ + + + + + + dynamixel_hardware_interface/DynamixelHardware + ${port_name} + ${baud_rate} + /param/dxl_model + 0 + 0 + true + 1000 + dynamixel_hardware_interface/dxl_state + dynamixel_hardware_interface/get_dxl_data + dynamixel_hardware_interface/set_dxl_data + dynamixel_hardware_interface/reboot_dxl + dynamixel_hardware_interface/set_dxl_torque + + + + + + + + + + + + + + + + + sensor + 1 + + + + + + + + + + + 0 + + + + diff --git a/dynamixel_hardware_interface_example_2/config/dynamixel_system.urdf.xacro b/dynamixel_hardware_interface_example_2/config/dynamixel_system.urdf.xacro new file mode 100644 index 0000000..15f360f --- /dev/null +++ b/dynamixel_hardware_interface_example_2/config/dynamixel_system.urdf.xacro @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/dynamixel_hardware_interface_example_2/config/ros2_controllers.yaml b/dynamixel_hardware_interface_example_2/config/ros2_controllers.yaml new file mode 100644 index 0000000..63570f5 --- /dev/null +++ b/dynamixel_hardware_interface_example_2/config/ros2_controllers.yaml @@ -0,0 +1,16 @@ +/**: + controller_manager: + ros__parameters: + update_rate: 500 # Hz + + joint_state_broadcaster: + type: joint_state_broadcaster/JointStateBroadcaster + + imu_sensor_broadcaster: + type: imu_sensor_broadcaster/IMUSensorBroadcaster + +/**: + imu_sensor_broadcaster: + ros__parameters: + sensor_name: "imu" + frame_id: imu_link \ No newline at end of file diff --git a/dynamixel_hardware_interface_example_2/launch/hardware.launch.py b/dynamixel_hardware_interface_example_2/launch/hardware.launch.py new file mode 100644 index 0000000..be4ea0b --- /dev/null +++ b/dynamixel_hardware_interface_example_2/launch/hardware.launch.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# +# Copyright 2025 ROBOTIS CO., LTD. +# +# 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. +# +# Author: Woojin Wie + + +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.substitutions import Command, FindExecutable, LaunchConfiguration, PathJoinSubstitution + +from launch_ros.actions import Node +from launch_ros.substitutions import FindPackageShare + + +def generate_launch_description(): + # Declare arguments + declared_arguments = [] + + declared_arguments.append( + DeclareLaunchArgument( + 'prefix', + default_value='""', + description='Prefix of the joint names' + ) + ) + + declared_arguments.append( + DeclareLaunchArgument( + 'description_file', + default_value='dynamixel_system.urdf.xacro', + description='URDF/XACRO description file with the robot.', + ) + ) + + declared_arguments.append( + DeclareLaunchArgument( + 'port_name', + default_value='/dev/ttyUSB0', + description='Port name for the Dynamixel device.', + ) + ) + + declared_arguments.append( + DeclareLaunchArgument( + 'baud_rate', + default_value='4000000', + description='Baudrate for the Dynamixel port.', + ) + ) + + description_file = LaunchConfiguration('description_file') + prefix = LaunchConfiguration('prefix') + port_name = LaunchConfiguration('port_name') + baud_rate = LaunchConfiguration('baud_rate') + + robot_controllers = PathJoinSubstitution( + [ + FindPackageShare('dynamixel_hardware_interface_example_2'), + 'config', + 'ros2_controllers.yaml', + ] + ) + + control_node = Node( + package='controller_manager', + executable='ros2_control_node', + parameters=[robot_controllers], + output='both', + ) + + robot_description_content = Command( + [ + PathJoinSubstitution([FindExecutable(name='xacro')]), + ' ', + PathJoinSubstitution( + [ + FindPackageShare('dynamixel_hardware_interface_example_2'), + 'config', + description_file, + ] + ), + ' ', + 'prefix:=', + prefix, + ' ', + 'port_name:=', + port_name, + ' ', + 'baud_rate:=', + baud_rate, + ] + ) + + robot_description = {'robot_description': robot_description_content} + + robot_controller_spawner = Node( + package='controller_manager', + executable='spawner', + arguments=[ + 'joint_state_broadcaster', + 'imu_sensor_broadcaster', + ], + parameters=[robot_description], + ) + + robot_state_publisher_node = Node( + package='robot_state_publisher', + executable='robot_state_publisher', + output='both', + parameters=[robot_description], + ) + + nodes = [ + control_node, + robot_controller_spawner, + robot_state_publisher_node, + ] + + return LaunchDescription(declared_arguments + nodes) diff --git a/dynamixel_hardware_interface_example_2/package.xml b/dynamixel_hardware_interface_example_2/package.xml new file mode 100644 index 0000000..1b0748e --- /dev/null +++ b/dynamixel_hardware_interface_example_2/package.xml @@ -0,0 +1,26 @@ + + + + dynamixel_hardware_interface_example_2 + 0.0.4 + + Example package with configuration and launch files for using the dynamixel_hardware_interface. + + Pyo + Apache 2.0 + Woojin Wie + + joint_state_publisher + controller_manager + dynamixel_hardware_interface + robot_state_publisher + xacro + joint_state_broadcaster + imu_sensor_broadcaster + rviz2 + rviz_imu_plugin + + + ament_python + + diff --git a/dynamixel_hardware_interface_example_2/resource/dynamixel_hardware_interface_example_2 b/dynamixel_hardware_interface_example_2/resource/dynamixel_hardware_interface_example_2 new file mode 100644 index 0000000..e69de29 diff --git a/dynamixel_hardware_interface_example_2/setup.cfg b/dynamixel_hardware_interface_example_2/setup.cfg new file mode 100644 index 0000000..5e4bbfb --- /dev/null +++ b/dynamixel_hardware_interface_example_2/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/dynamixel_hardware_interface_example_2 +[install] +install_scripts=$base/lib/dynamixel_hardware_interface_example_2 diff --git a/dynamixel_hardware_interface_example_2/setup.py b/dynamixel_hardware_interface_example_2/setup.py new file mode 100644 index 0000000..ce5c8d8 --- /dev/null +++ b/dynamixel_hardware_interface_example_2/setup.py @@ -0,0 +1,29 @@ +from glob import glob +import os + +from setuptools import find_packages +from setuptools import setup + +package_name = 'dynamixel_hardware_interface_example_2' + +setup( + name=package_name, + version='0.0.3', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + (os.path.join('share', package_name, 'launch'), glob('launch/*.py')), + (os.path.join('share', package_name, 'config'), glob('config/*')), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='Pyo', + maintainer_email='pyo@robotis.com', + description='Dynamixel Hardware Interface Example (imu sensor) ROS 2 package.', + license='Apache 2.0', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [], + }, +)