def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("finger_type")
    args = parser.parse_args(sys.argv[1:2])

    finger_type = finger_types_data.check_finger_type(args.finger_type)
    num_fingers = finger_types_data.get_number_of_fingers(finger_type)

    parser = argparse.ArgumentParser(description=__doc__)
    if num_fingers == 1:
        parser.add_argument("data_file")
    elif num_fingers == 3:
        parser.add_argument("data_file_0")
        parser.add_argument("data_file_120")
        parser.add_argument("data_file_240")
    args = parser.parse_args(sys.argv[2:])

    if num_fingers == 3:
        main_trifinger(
            finger_type,
            args.data_file_0,
            args.data_file_120,
            args.data_file_240,
        )
    else:
        main_finger(finger_type, args.data_file)
def test_get_number_of_fingers():
    for name in ftd.finger_types_data.keys():
        assert (ftd.get_number_of_fingers(name) ==
                ftd.finger_types_data[name].number_of_fingers)

    with pytest.raises(ValueError):
        ftd.check_finger_type("invalid")
Beispiel #3
0
    def __init__(
        self,
        finger_type,
        time_step=0.004,
        enable_visualization=False,
        sim_joint_friction=0.0,
    ):
        """
        Constructor, initializes the physical world we will work in.

        Args:
            finger_type (string): Name of the finger type.  Use
                :meth:`get_valid_finger_types` to get a list of all supported
                types.
            time_step (float): Time (in seconds) between two simulation steps.
                Don't set this to be larger than 1/60.  The gains etc. are set
                according to a time_step of 0.004 s.
            enable_visualization (bool): Set this to 'True' for a GUI interface
                to the simulation.
            sim_joint_friction (float or float array): Set this to non-zero
                to apply negative forces on applied torques to simulate joint
                friction
        """
        self.finger_type = finger_types_data.check_finger_type(finger_type)
        self.number_of_fingers = finger_types_data.get_number_of_fingers(
            self.finger_type)

        self.time_step_s = time_step

        #: The kp gains for the pd control of the finger(s). Note, this depends
        #: on the simulation step size and has been set for a simulation rate
        #: of 250 Hz.
        self.position_gains = np.array([10.0, 10.0, 10.0] *
                                       self.number_of_fingers)

        #: The kd gains for the pd control of the finger(s). Note, this depends
        #: on the simulation step size and has been set for a simulation rate
        #: of 250 Hz.
        self.velocity_gains = np.array([0.1, 0.3, 0.001] *
                                       self.number_of_fingers)

        #: The kd gains used for damping the joint motor velocities during the
        #: safety torque check on the joint motors.
        self.safety_kd = np.array([0.08, 0.08, 0.04] * self.number_of_fingers)

        #: The maximum allowable torque that can be applied to each motor.
        self.max_motor_torque = 0.396

        self._t = -1

        self.__create_link_lists()
        self.__set_urdf_path()
        self._pybullet_client_id = self.__connect_to_pybullet(
            enable_visualization)
        self.__setup_pybullet_simulation()

        self.kinematics = pinocchio_utils.Kinematics(self.finger_urdf_path,
                                                     self.tip_link_names)
        self.sim_joint_friction = sim_joint_friction
Beispiel #4
0
def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--finger-type",
        required=True,
        choices=finger_types_data.get_valid_finger_types(),
        help="Specify valid finger type.",
    )
    parser.add_argument(
        "--real-time-mode",
        "-r",
        action="store_true",
        help="""Run simulation in real time.  If not set,
                        the simulation runs as fast as possible.
                        """,
    )
    args = parser.parse_args()

    # select the correct types/functions based on which robot is used
    num_fingers = finger_types_data.get_number_of_fingers(args.finger_type)
    if num_fingers == 1:
        finger_types = robot_interfaces.finger
        create_backend = (
            robot_fingers.pybullet_drivers.create_single_finger_backend)
    elif num_fingers == 3:
        finger_types = robot_interfaces.trifinger
        create_backend = (
            robot_fingers.pybullet_drivers.create_trifinger_backend)

    robot_data = finger_types.SingleProcessData()

    # Create backend with the simulation as driver.
    # Simply replace this line by creating a backend for the real robot to run
    # the same code on the real robot.
    backend = create_backend(robot_data,
                             real_time_mode=args.real_time_mode,
                             visualize=True)

    frontend = finger_types.Frontend(robot_data)

    backend.initialize()

    # Simple example application that moves the finger to random positions.
    while True:
        action = finger_types.Action(position=get_random_position(num_fingers))
        for _ in range(300):
            t = frontend.append_desired_action(action)
            frontend.wait_until_timeindex(t)

        # print current position from time to time
        current_position = frontend.get_observation(t).position
        print("Position: %s" % current_position)
Beispiel #5
0
    def __init__(
        self,
        finger_type: str,
        time_step: float = 0.001,
        enable_visualization: bool = False,
        robot_position_offset: typing.Sequence[float] = (0, 0, 0),
    ):
        """
        Args:
            finger_type: Name of the finger type.  Use
                :meth:`~finger_types_data.get_valid_finger_types` to get a list
                of all supported types.
            time_step: Time (in seconds) between two simulation steps. Don't
                set this to be larger than 1/60.  The gains etc. are set
                according to a time_step of 0.001 s.
            enable_visualization: Set this to 'True' for a GUI interface to the
                simulation.
            robot_position_offset: (x, y, z)-Position offset with which the
                robot is placed in the world.  Use this, for example, to change
                the height of the fingers above the table.
        """
        self.finger_type = finger_types_data.check_finger_type(finger_type)
        self.number_of_fingers = finger_types_data.get_number_of_fingers(
            self.finger_type
        )

        self.time_step_s = time_step

        self.__set_default_pd_gains()

        #: The kd gains used for damping the joint motor velocities during the
        #: safety torque check on the joint motors.
        self.safety_kd = np.array([0.08, 0.08, 0.04] * self.number_of_fingers)

        #: The maximum allowable torque that can be applied to each motor.
        self.max_motor_torque = 0.396

        self._t = -1

        self.__create_link_lists()
        self.__set_urdf_path()
        self._pybullet_client_id = self.__connect_to_pybullet(
            enable_visualization
        )
        self.__setup_pybullet_simulation(robot_position_offset)

        self.kinematics = pinocchio_utils.Kinematics(
            self.finger_urdf_path, self.tip_link_names
        )
    def __init__(
        self,
        finger_type,
        time_step=0.004,
        enable_visualization=False,
    ):
        """Constructor, initializes the physical world we will work in.

        Args:
            finger_type : See :attr:`finger_type`
            time_step : See :attr:`time_step`
            enable_visualization : See :attr:`enable_visualization`
        """
        self.finger_type = finger_types_data.check_finger_type(finger_type)
        self.number_of_fingers = finger_types_data.get_number_of_fingers(
            self.finger_type)

        self.time_step_s = time_step

        #: The kp gains for the pd control of the finger(s). Note, this depends
        #: on the simulation step size and has been set for a simulation rate
        #: of 250 Hz.
        self.position_gains = np.array([10.0, 10.0, 10.0] *
                                       self.number_of_fingers)

        #: The kd gains for the pd control of the finger(s). Note, this depends
        #: on the simulation step size and has been set for a simulation rate
        #: of 250 Hz.
        self.velocity_gains = np.array([0.1, 0.3, 0.001] *
                                       self.number_of_fingers)

        #: The kd gains used for damping the joint motor velocities during the
        #: safety torque check on the joint motors.
        self.safety_kd = np.array([0.08, 0.08, 0.04] * self.number_of_fingers)

        #: The maximum allowable torque that can be applied to each motor.
        self.max_motor_torque = 0.36

        self._t = -1

        self.__create_link_lists()
        self.__set_urdf_path()
        self.__connect_to_pybullet(enable_visualization)
        self.__setup_pybullet_simulation()

        self.pinocchio_utils = pinocchio_utils.PinocchioUtils(
            self.finger_urdf_path, self.tip_link_names)
    def _run_position_test(self, finger_type, goal_positions):
        """Run position test for single or tri-finger.

        Moves the robot in a sequence of goal positions in joint-space.  After
        each step, it is checked if the robot reached the goal with some
        tolerance.

        Args:
            finger_type:  Which robot to use.
            goal_positions:  A list of joint goal positions.
        """
        # select the correct types/functions based on which robot is used
        num_fingers = finger_types_data.get_number_of_fingers(finger_type)
        if num_fingers == 1:
            finger_types = robot_interfaces.finger
            create_backend = (
                robot_fingers.pybullet_drivers.create_single_finger_backend
            )
        elif num_fingers == 3:
            finger_types = robot_interfaces.trifinger
            create_backend = (
                robot_fingers.pybullet_drivers.create_trifinger_backend
            )

        robot_data = finger_types.SingleProcessData()

        backend = create_backend(
            robot_data, real_time_mode=False, visualize=False
        )

        frontend = finger_types.Frontend(robot_data)
        backend.initialize()

        # Simple example application that moves the finger to random positions.
        for goal in goal_positions:
            action = finger_types.Action(position=goal)
            for _ in range(300):
                t = frontend.append_desired_action(action)
                frontend.wait_until_timeindex(t)

            # check if desired position is reached
            current_position = frontend.get_observation(t).position
            np.testing.assert_array_almost_equal(
                goal, current_position, decimal=1
            )
def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--finger-type",
        choices=finger_types_data.get_valid_finger_types(),
        required=True,
        help="""Pass a valid finger type.""",
    )
    parser.add_argument(
        "--real-time-mode",
        "-r",
        action="store_true",
        help="""Run simulation in real time.  If not set, the simulation runs
            as fast as possible.
        """,
    )
    parser.add_argument(
        "--first-action-timeout",
        "-t",
        type=float,
        default=math.inf,
        help="""Timeout (in seconds) for reception of first action after
            starting the backend.  If not set, the timeout is disabled.
        """,
    )
    parser.add_argument(
        "--max-number-of-actions",
        "-a",
        type=int,
        default=0,
        help="""Maximum numbers of actions that are processed.  After this the
            backend shuts down automatically.
        """,
    )
    parser.add_argument(
        "--robot-logfile",
        "-l",
        type=str,
        help="""Path to a file to which the robot data log is written.  If not
            specified, no log is generated.
        """,
    )
    parser.add_argument(
        "--camera-logfile",
        type=str,
        help="""Path to a file to which the camera data log is written.  If not
            specified, no log is generated.
        """,
    )
    parser.add_argument(
        "--ready-indicator",
        type=str,
        metavar="READY_INDICATOR_FILE",
        help="""Path to a file that will be created once the backend is ready
            and will be deleted again when it stops (before storing the logs).
        """,
    )
    parser.add_argument(
        "--add-cube",
        action="store_true",
        help="""Spawn a cube and run the object tracker backend.""",
    )
    parser.add_argument(
        "--cameras",
        action="store_true",
        help="""Run camera backend using rendered images.""",
    )
    parser.add_argument(
        "--visualize",
        "-v",
        action="store_true",
        help="Run pyBullet's GUI for visualization.",
    )
    args = parser.parse_args()

    # configure the logging module
    log_handler = logging.StreamHandler(sys.stdout)
    logging.basicConfig(
        format="[SIM_TRIFINGER_BACKEND %(levelname)s %(asctime)s] %(message)s",
        level=logging.DEBUG,
        handlers=[log_handler],
    )

    # select the correct types/functions based on which robot is used
    num_fingers = finger_types_data.get_number_of_fingers(args.finger_type)
    if num_fingers == 1:
        shared_memory_id = "finger"
        finger_types = robot_interfaces.finger
        create_backend = drivers.create_single_finger_backend
    elif num_fingers == 3:
        shared_memory_id = "trifinger"
        finger_types = robot_interfaces.trifinger
        create_backend = drivers.create_trifinger_backend

    # If max_number_of_actions is set, choose the history size of the time
    # series such that the whole episode fits in (+1 for the status message
    # containing the "limit exceeded" error).
    if args.max_number_of_actions:
        history_size = args.max_number_of_actions + 1
    else:
        history_size = 1000

    robot_data = finger_types.MultiProcessData(
        shared_memory_id, True, history_size=history_size
    )

    robot_logger = finger_types.Logger(robot_data)

    backend = create_backend(
        robot_data,
        args.real_time_mode,
        args.visualize,
        args.first_action_timeout,
        args.max_number_of_actions,
    )
    backend.initialize()

    #
    # Camera and Object Tracker Interface
    # Important:  These objects need to be created _after_ the simulation is
    # initialized (i.e. after the SimFinger instance is created).
    #
    if args.cameras and not args.add_cube:
        # If cameras are enabled but not the object, use the normal
        # PyBulletTriCameraDriver.
        from trifinger_cameras import tricamera

        camera_data = tricamera.MultiProcessData("tricamera", True, 10)
        camera_driver = tricamera.PyBulletTriCameraDriver()
        camera_backend = tricamera.Backend(camera_driver, camera_data)  # noqa

    elif args.add_cube:
        # If the cube is enabled, use the PyBulletTriCameraObjectTrackerDriver.
        # In case the cameras are not requested, disable rendering of the
        # images to save time.
        import trifinger_object_tracking.py_tricamera_types as tricamera

        # spawn a cube in the centre of the arena
        cube = collision_objects.Block(position=[0.0, 0.0, 0.035])
        render_images = args.cameras

        camera_data = tricamera.MultiProcessData("tricamera", True, 10)
        camera_driver = tricamera.PyBulletTriCameraObjectTrackerDriver(
            cube, robot_data, render_images
        )
        camera_backend = tricamera.Backend(camera_driver, camera_data)  # noqa

    #
    # Camera/Object Logger
    #
    camera_logger = None
    if args.camera_logfile:
        try:
            camera_data
        except NameError:
            logging.critical("Cannot create camera log camera is not running.")
            return

        # TODO
        camera_fps = 10
        robot_rate_hz = 1000
        buffer_length_factor = 1.5
        episode_length_s = args.max_number_of_actions / robot_rate_hz
        # Compute camera log size based on number of robot actions plus some
        # safety buffer
        log_size = int(camera_fps * episode_length_s * buffer_length_factor)

        logging.info("Initialize camera logger with buffer size %d", log_size)
        camera_logger = tricamera.Logger(camera_data, log_size)

    # if specified, create the "ready indicator" file to indicate that the
    # backend is ready
    if args.ready_indicator:
        pathlib.Path(args.ready_indicator).touch()

    backend.wait_until_first_action()

    if camera_logger:
        camera_logger.start()
        logging.info("Start camera logging")

    backend.wait_until_terminated()

    # delete the ready indicator file to indicate that the backend has shut
    # down
    if args.ready_indicator:
        pathlib.Path(args.ready_indicator).unlink()

    if camera_logger:
        logging.info(
            "Save recorded camera data to file %s", args.camera_logfile
        )
        camera_logger.stop_and_save(args.camera_logfile)

    if camera_data:
        # stop the camera backend
        logging.info("Stop camera backend")
        camera_backend.shutdown()

    if args.robot_logfile:
        logging.info("Save robot data to file %s", args.robot_logfile)
        if args.max_number_of_actions:
            end_index = args.max_number_of_actions
        else:
            end_index = -1

        robot_logger.write_current_buffer_binary(
            args.robot_logfile, start_index=0, end_index=end_index
        )

    # cleanup stuff before the simulation (backend) is terminated
    if args.add_cube:
        del cube

    logging.info("Done.")
Beispiel #9
0
    def __init__(
        self,
        finger_type,
        finger_config_suffix,
        enable_visualization=False,
    ):
        """
        Constructor, initializes the physical world we will work in.

        Args:
            finger_type (string): Name of the finger type.  In order to get
                a list of the valid finger types, call
                :meth:`.finger_types_data.get_valid_finger_types`.
            finger_config_suffix (int): ID of the finger that is used. Has to
                be one of [0, 120, 240]. This is only if a single finger is to
                be used on any of the robots, and is ignored otherwise.
            enable_visualization (bool, optional): Set to 'True' to run a GUI
                for visualization.  This uses pyBullet but only for
                visualization, i.e. the state of the simulation is constantly
                set to match the one of the real robot.
        """
        # Simulation is only used for visualization, so only run it when needed
        self.simulator = None
        if enable_visualization:
            self.simulator = SimFinger(
                finger_type=finger_type,
                time_step=0.001,  # todo: not sure if this is correct
                enable_visualization=True,
            )

        number_of_fingers = finger_types_data.get_number_of_fingers(
            finger_type)

        if number_of_fingers == 1:
            if finger_type == "fingerone":
                config_file_path = os.path.join(
                    rospkg.RosPack().get_path("robot_fingers"),
                    "config",
                    "finger_%s.yml" % finger_config_suffix,
                )
            elif finger_type == "fingeredu":
                config_file_path = os.path.join(
                    rospkg.RosPack().get_path("robot_fingers"),
                    "config",
                    "fingeredu_%s.yml" % finger_config_suffix,
                )
            finger_data = robot_interfaces.finger.SingleProcessData()
            self.real_finger_backend = (
                robot_fingers.create_real_finger_backend(
                    finger_data, config_file_path))
            self.robot = robot_interfaces.finger.Frontend(finger_data)
            self.Action = robot_interfaces.finger.Action
        elif number_of_fingers == 3:
            if finger_type == "trifingerone":
                config_file_path = os.path.join(
                    rospkg.RosPack().get_path("robot_fingers"),
                    "config",
                    "trifinger.yml",
                )
            elif finger_type == "trifingeredu":
                config_file_path = os.path.join(
                    rospkg.RosPack().get_path("robot_fingers"),
                    "config",
                    "trifingeredu.yml",
                )
            finger_data = robot_interfaces.trifinger.SingleProcessData()
            self.real_finger_backend = robot_fingers.create_trifinger_backend(
                finger_data, config_file_path)
            self.robot = robot_interfaces.trifinger.Frontend(finger_data)
            self.Action = robot_interfaces.trifinger.Action

        self.real_finger_backend.initialize()
Beispiel #10
0
def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--finger-type",
        choices=finger_types_data.get_valid_finger_types(),
        required=True,
        help="""Pass a valid finger type.""",
    )
    parser.add_argument(
        "--real-time-mode",
        "-r",
        action="store_true",
        help="""Run simulation in real time.  If not set, the simulation runs
            as fast as possible.
        """,
    )
    parser.add_argument(
        "--first-action-timeout",
        "-t",
        type=float,
        default=math.inf,
        help="""Timeout (in seconds) for reception of first action after
            starting the backend.  If not set, the timeout is disabled.
        """,
    )
    parser.add_argument(
        "--max-number-of-actions",
        "-a",
        type=int,
        default=0,
        help="""Maximum numbers of actions that are processed.  After this the
            backend shuts down automatically.
        """,
    )
    parser.add_argument(
        "--logfile",
        "-l",
        type=str,
        help="""Path to a file to which the data log is written.  If not
            specified, no log is generated.
        """,
    )
    parser.add_argument(
        "--add-cube",
        action="store_true",
        help="""Spawn a cube and run the object tracker backend.""",
    )
    parser.add_argument(
        "--cameras",
        action="store_true",
        help="""Run camera backend using rendered images.""",
    )
    parser.add_argument(
        "--visualize",
        "-v",
        action="store_true",
        help="Run pyBullet's GUI for visualization.",
    )
    args = parser.parse_args()

    # select the correct types/functions based on which robot is used
    num_fingers = finger_types_data.get_number_of_fingers(args.finger_type)
    if num_fingers == 1:
        shared_memory_id = "finger"
        finger_types = robot_interfaces.finger
        create_backend = drivers.create_single_finger_backend
    elif num_fingers == 3:
        shared_memory_id = "trifinger"
        finger_types = robot_interfaces.trifinger
        create_backend = drivers.create_trifinger_backend

    # If max_number_of_actions is set, choose the history size of the time
    # series such that the whole episode fits in (+1 for the status message
    # containing the "limit exceeded" error).
    if args.max_number_of_actions:
        history_size = args.max_number_of_actions + 1
    else:
        history_size = 1000

    robot_data = finger_types.MultiProcessData(shared_memory_id,
                                               True,
                                               history_size=history_size)

    logger = finger_types.Logger(robot_data)

    backend = create_backend(
        robot_data,
        args.real_time_mode,
        args.visualize,
        args.first_action_timeout,
        args.max_number_of_actions,
    )
    backend.initialize()

    # Object and Object Tracker Interface
    # Important:  These objects need to be created _after_ the simulation is
    # initialized (i.e. after the SimFinger instance is created).
    if args.add_cube:
        # only import when really needed
        import trifinger_object_tracking.py_object_tracker as object_tracker

        # spawn a cube in the arena
        cube = collision_objects.Block()

        # initialize the object tracker interface
        object_tracker_data = object_tracker.Data("object_tracker", True)
        object_tracker_backend = object_tracker.SimulationBackend(  # noqa
            object_tracker_data, cube, args.real_time_mode)

    if args.cameras:
        from trifinger_cameras import tricamera

        camera_data = tricamera.MultiProcessData("tricamera", True, 10)
        camera_driver = tricamera.PyBulletTriCameraDriver()
        camera_backend = tricamera.Backend(camera_driver, camera_data)  # noqa

    backend.wait_until_terminated()

    if args.logfile:
        if args.max_number_of_actions:
            end_index = args.max_number_of_actions
        else:
            end_index = -1
        logger.write_current_buffer(args.logfile,
                                    start_index=0,
                                    end_index=end_index)
Beispiel #11
0
    def __init__(
        self,
        control_rate_s,
        finger_type,
        enable_visualization,
    ):
        """Intializes the constituents of the pushing environment.

        Constructor sets up the finger robot depending on the finger type,
        sets up the observation and action spaces, smoothing for
        reducing jitter on the robot, and provides for a way to synchronize
        robots being trained independently.


        Args:
            control_rate_s (float): the rate (in seconds) at which step method of the env
                will run. The actual robot controller may run at a higher rate,
                so this is used to compute the number of robot control updates
                per environment step.
            finger_type (string): Name of the finger type.  In order to get
                a list of the valid finger types, call
                :meth:`.finger_types_data.get_valid_finger_types`
            enable_visualization (bool): if the simulation env is to be
                visualized
        """

        #: an instance of the simulated robot depending on the desired
        #: robot type
        self.finger = SimFinger(
            finger_type=finger_type,
            enable_visualization=enable_visualization,
        )

        self.num_fingers = finger_types_data.get_number_of_fingers(finger_type)

        #: the number of times the same action is to be applied to
        #: the robot in one step.
        self.steps_per_control = int(
            round(control_rate_s / self.finger.time_step_s))
        assert (abs(control_rate_s -
                    self.steps_per_control * self.finger.time_step_s) <=
                0.000001)

        #: the types of observations that should be a part of the environment's
        #: observed state
        self.observations_keys = [
            "joint_positions",
            "joint_velocities",
            "action_joint_positions",
            "goal_position",
            "object_position",
        ]
        #: the size of each of the observation type that is part of the
        #: observation keys (in the same order)
        self.observations_sizes = [
            3 * self.num_fingers,
            3 * self.num_fingers,
            3 * self.num_fingers,
            3,
            3,
        ]

        # sets up the observation and action spaces for the environment,
        # unscaled spaces have the custom bounds set up for each observation
        # or action type, whereas all the values in the observation and action
        # spaces lie between 1 and -1
        self.spaces = FingerSpaces(
            num_fingers=self.num_fingers,
            observations_keys=self.observations_keys,
            observations_sizes=self.observations_sizes,
            separate_goals=False,
        )

        self.unscaled_observation_space = (
            self.spaces.get_unscaled_observation_space())
        self.unscaled_action_space = self.spaces.get_unscaled_action_space()

        self.observation_space = self.spaces.get_scaled_observation_space()
        self.action_space = self.spaces.get_scaled_action_space()

        #: a logger to enable logging of observations if desired
        self.logger = DataLogger()

        #: the object that has to be pushed
        self.block = collision_objects.Block()

        #: a marker to visualize where the target goal position for the episode
        #: is
        self.goal_marker = visual_objects.Marker(
            number_of_goals=1,
            goal_size=0.0325,
            initial_position=[0.19, 0.08, 0.0425],
        )

        self.reset()
Beispiel #12
0
    def __init__(
        self,
        control_rate_s,
        finger_type,
        enable_visualization,
        smoothing_params,
        use_real_robot=False,
        finger_config_suffix="0",
        synchronize=False,
    ):
        """Intializes the constituents of the reaching environment.

        Constructor sets up the finger robot depending on the finger type, and
        also whether an instance of the simulated or the real robot is to be
        created. Also sets up the observation and action spaces, smoothing for
        reducing jitter on the robot, and provides for a way to synchronize
        robots being trained independently.

        Args:
            control_rate_s (float): the rate (in seconds) at which step method of the env
                will run. The actual robot controller may run at a higher rate,
                so this is used to compute the number of robot control updates
                per environment step.
            finger_type (string): Name of the finger type.  In order to get
                a dictionary of the valid finger types, call
                :meth:`.finger_types_data.get_valid_finger_types`
            enable_visualization (bool): if the simulation env is to be
                visualized
            smoothing_params (dict):
                num_episodes (int): the total number of episodes for which the
                    training is performed
                start_after (float): the fraction of episodes after which the
                    smoothing of applied actions to the motors should start
                final_alpha (float): smoothing coeff that will be reached at
                    the end of the smoothing
                stop_after (float): the fraction of total episodes by which
                    final alpha is to be reached, after which the same final
                    alpha will be used for smoothing in the remainder of
                    the episodes
                is_test (bool, optional): Include this for testing
            use_real_robot (bool): if training is to be performed on
                the real robot ([default] False)
            finger_config_suffix: pass this if only one of
                the three fingers is to be trained. Valid choices include
                [0, 120, 240] ([default] 0)
            synchronize (bool): Set this to True if you want to train
                independently on three fingers in separate processes, but
                have them synchronized. ([default] False)
        """
        #: an instance of a simulated, or a real robot depending on
        #: what is desired.
        if use_real_robot:
            from pybullet_fingers.real_finger import RealFinger

            self.finger = RealFinger(
                finger_type=finger_type,
                finger_config_suffix=finger_config_suffix,
                enable_visualization=enable_visualization,
            )

        else:
            self.finger = SimFinger(
                finger_type=finger_type,
                enable_visualization=enable_visualization,
            )

        self.num_fingers = finger_types_data.get_number_of_fingers(finger_type)

        #: the number of times the same action is to be applied to
        #: the robot.
        self.steps_per_control = int(
            round(control_rate_s / self.finger.time_step_s))
        assert (abs(control_rate_s -
                    self.steps_per_control * self.finger.time_step_s) <=
                0.000001)

        #: the types of observations that should be a part of the environment's
        #: observed state
        self.observations_keys = [
            "joint_positions",
            "joint_velocities",
            "goal_position",
            "action_joint_positions",
        ]

        self.observations_sizes = [
            3 * self.num_fingers,
            3 * self.num_fingers,
            3 * self.num_fingers,
            3 * self.num_fingers,
        ]

        # sets up the observation and action spaces for the environment,
        # unscaled spaces have the custom bounds set up for each observation
        # or action type, whereas all the values in the observation and action
        # spaces lie between 1 and -1
        self.spaces = FingerSpaces(
            num_fingers=self.num_fingers,
            observations_keys=self.observations_keys,
            observations_sizes=self.observations_sizes,
            separate_goals=True,
        )

        self.unscaled_observation_space = (
            self.spaces.get_unscaled_observation_space())
        self.unscaled_action_space = self.spaces.get_unscaled_action_space()

        self.observation_space = self.spaces.get_scaled_observation_space()
        self.action_space = self.spaces.get_scaled_action_space()

        #: a logger to enable logging of observations if desired
        self.logger = DataLogger()

        # sets up smooothing
        if "is_test" in smoothing_params:
            self.smoothing_start_episode = 0
            self.smoothing_alpha = smoothing_params["final_alpha"]
            self.smoothing_increase_step = 0
            self.smoothing_stop_episode = math.inf
        else:
            self.smoothing_stop_episode = int(
                smoothing_params["num_episodes"] *
                smoothing_params["stop_after"])

            self.smoothing_start_episode = int(
                smoothing_params["num_episodes"] *
                smoothing_params["start_after"])
            num_smoothing_increase_steps = (self.smoothing_stop_episode -
                                            self.smoothing_start_episode)
            self.smoothing_alpha = 0
            self.smoothing_increase_step = (smoothing_params["final_alpha"] /
                                            num_smoothing_increase_steps)

        self.smoothed_action = None
        self.episode_count = 0

        #: a marker to visualize where the target goal position for the episode
        #: is to which the tip link(s) of the robot should reach
        self.enable_visualization = enable_visualization
        if self.enable_visualization:
            self.goal_marker = visual_objects.Marker(
                number_of_goals=self.num_fingers)

        # set up synchronization if it's set to true
        self.synchronize = synchronize
        if synchronize:
            now = datetime.datetime.now()
            self.next_start_time = datetime.datetime(now.year, now.month,
                                                     now.day, now.hour,
                                                     now.minute + 1)
        else:
            self.next_start_time = None

        self.seed()
        self.reset()