1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// Copyright (c) 2021 Marco Boneberger
// Licensed under the EUPL-1.2-or-later
//! Contains the Experiment struct, which is needed to spawn and connect to robots

use crate::environment::{FrankaEnvironment, FrankaRealEnvironment, FrankaSimEnvironment};
use crate::{FrankaReal, FrankaSim, FrankaSimWithoutClient, Robot, RobotArguments};
use rubullet::image::RgbaImage;
use rubullet::PhysicsClient;
use std::cell::{RefCell, RefMut};
use std::path::PathBuf;
use std::rc::Rc;
use std::time::Duration;

/// choose whether to run your [`Experiment`](`Experiment`) in the simulation or with the real robot
pub enum Mode {
    /// Runs the experiment in the simulation.
    Simulation,
    /// Runs the experiment on the real robot.
    Real,
}
/// A trait which contains methods for handling the simulation. Apart from
/// [`set_franka_urdf_path`](`Self::set_franka_urdf_path`),
/// all methods have a default implementation,
/// so you do not have to implement them if you do not need them.
pub trait SimulationSetup {
    /// Sets how often the simulation should be updated inside the control loop. The default is
    /// 1/240s  = 240 Hz
    fn set_simulation_time_step(&self) -> Duration {
        Duration::from_secs_f64(1. / 240.)
    }
    /// Return the path to the URDF file of the Franka robot.
    fn set_franka_urdf_path(&self) -> PathBuf;
    /// this method runs directly after the simulation is spawned. Use this method for example
    /// to load additional object into the simulation.
    fn setup_simulation(&mut self, _client: Rc<RefCell<PhysicsClient>>) {}
    /// this method is intended to let you setup the camera. It is run directly after setup_simulation.
    fn setup_camera(&mut self, _client: Rc<RefCell<PhysicsClient>>) {}
    /// determine how you want to get the camera image with this method.
    fn get_camera_image(&mut self, _client: Rc<RefCell<PhysicsClient>>) -> RgbaImage {
        RgbaImage::default()
    }
}
/// A trait which contains methods for handling the real robot. All methods have a default implementation,
/// so you do not have to implement them if you do not need them.
pub trait RealSetup {
    /// This method runs directly on calling [`Experiment::new`](`Experiment::new`). You can use
    /// it to run specific code which is only needed when using the real hardware.
    fn setup_real(&mut self) {}
    /// Runs directly after [`setup_real`](`Self::setup_real`) and is intended for setting up the camera.
    fn setup_camera(&mut self) {}
    /// determine how you want to get the camera image with this method.
    fn get_camera_image(&mut self) -> RgbaImage {
        RgbaImage::default()
    }
}
/// Use it to create a new experiment which can either run on the real robot or in the simulation.
///
/// Use [`new`](`Self::new`) to create an experiment and then use [`new_robot`](`Self::new_robot`) to
/// create a new robot.
///
pub struct Experiment<Sim: ?Sized + SimulationSetup, Real: ?Sized + RealSetup> {
    pub environment: FrankaEnvironment,
    pub sim_setup: Box<Sim>,
    pub real_setup: Box<Real>,
}

impl Experiment<dyn SimulationSetup, dyn RealSetup> {
    /// Use it to create a new experiment which can either run on the real robot or in the simulation
    ///
    /// # Arguments
    /// * `mode` - specify whether to run the experiment in simulation or with a real robot
    /// * `sim_setup` - something that implements [`SimulationSetup`](`SimulationSetup`).
    /// * `real_setup` - something that implements [`RealSetup`](`RealSetup`).
    ///
    /// # Example
    /// ```no_run
    /// use franka_interface::experiment::{Experiment, Mode, RealSetup, SimulationSetup};
    /// use franka_interface::types::Vector7;
    /// use franka_interface::RobotArguments;
    /// use std::f64::consts::PI;
    /// use std::path::PathBuf;
    /// struct MySimSetup {}
    /// impl SimulationSetup for MySimSetup {
    ///     fn set_franka_urdf_path(&self) -> PathBuf {
    ///         "path/to/panda.urdf".into()
    ///     }
    /// }
    /// struct MyRealSetup {}
    /// impl RealSetup for MyRealSetup {}
    /// let mut env = Experiment::new(Mode::Simulation, MySimSetup {}, MyRealSetup {});
    /// let mut robot = env.new_robot(RobotArguments {
    ///     hostname: "franka".to_string(),
    ///     base_pose: None,
    ///     initial_config: None,
    /// });
    /// robot.joint_motion(
    ///     0.1,
    ///     Vector7::from_column_slice(&[1., PI / 4., 0., -2. * PI / 4., 0., PI / 2., -PI / 4.]),
    /// );
    /// println!("{:?}", robot.get_state());
    /// ```
    pub fn new(
        mode: Mode,
        mut sim_setup: impl SimulationSetup + 'static,
        mut real_setup: impl RealSetup + 'static,
    ) -> Experiment<dyn SimulationSetup, dyn RealSetup> {
        match mode {
            Mode::Simulation => {
                let environment = Box::new(FrankaSimEnvironment::new(
                    sim_setup.set_simulation_time_step(),
                ));

                sim_setup.setup_simulation(environment.client.clone());
                sim_setup.setup_camera(environment.client.clone());
                let environment = FrankaEnvironment::Simulation(environment);
                Experiment {
                    environment,
                    sim_setup: Box::new(sim_setup),
                    real_setup: Box::new(real_setup),
                }
            }
            Mode::Real => {
                let environment = FrankaEnvironment::Real(Box::new(FrankaRealEnvironment::new()));
                real_setup.setup_real();
                real_setup.setup_camera();
                Experiment {
                    environment,
                    sim_setup: Box::new(sim_setup),
                    real_setup: Box::new(real_setup),
                }
            }
        }
    }
    /// returns the current image from the robot. Make sure you implemented `get_camera_image`
    /// in your [`RealSetup`](`RealSetup`) and [`SimulationSetup`](`SimulationSetup`).
    pub fn get_image(&mut self) -> RgbaImage {
        match &self.environment {
            FrankaEnvironment::Real(_) => self.real_setup.get_camera_image(),
            FrankaEnvironment::Simulation(environment) => {
                self.sim_setup.get_camera_image(environment.client.clone())
            }
        }
    }

    /// spawns/connects to a new robot using the [`RobotArguments`](`crate::RobotArguments`)
    pub fn new_robot(&mut self, config: RobotArguments) -> Robot {
        match &mut self.environment {
            FrankaEnvironment::Real(_environment) => {
                Robot::Real(FrankaReal::new(config.hostname.as_str()))
            }
            FrankaEnvironment::Simulation(environment) => {
                let args = FrankaSimWithoutClient {
                    urdf_path: self.sim_setup.set_franka_urdf_path(),
                    base_pose: config.base_pose,
                    initial_config: config.initial_config,
                };
                Robot::Sim(FrankaSim::new(
                    environment.client.clone(),
                    args,
                    &environment.time_step,
                ))
            }
        }
    }
    /// Query whether the current experiment is run in simulation
    pub fn is_simulation(&self) -> bool {
        match self.environment {
            FrankaEnvironment::Simulation(_) => true,
            FrankaEnvironment::Real(_) => false,
        }
    }
    /// allows accessing the PhysicsClient in Simulation by defining a closure that takes
    /// the PhysicsClient as input.
    /// Nothing will happen if the experiment is not run in the simulation
    /// # Return
    /// * In the simulation it will return Some(T) where T is the return type of the client_cb
    /// * Will return None when not executed with the simulation.
    pub fn use_physics_client<F: FnMut(RefMut<PhysicsClient>) -> T, T>(
        &mut self,
        mut client_cb: F,
    ) -> Option<T> {
        match &self.environment {
            FrankaEnvironment::Simulation(sim) => Some(client_cb(sim.client.borrow_mut())),
            FrankaEnvironment::Real(_) => None,
        }
    }
}