franka_interface/experiment.rs
1// Copyright (c) 2021 Marco Boneberger
2// Licensed under the EUPL-1.2-or-later
3//! Contains the Experiment struct, which is needed to spawn and connect to robots
4
5use crate::environment::{FrankaEnvironment, FrankaRealEnvironment, FrankaSimEnvironment};
6use crate::{FrankaReal, FrankaSim, FrankaSimWithoutClient, Robot, RobotArguments};
7use rubullet::image::RgbaImage;
8use rubullet::PhysicsClient;
9use std::cell::{RefCell, RefMut};
10use std::path::PathBuf;
11use std::rc::Rc;
12use std::time::Duration;
13
14/// choose whether to run your [`Experiment`](`Experiment`) in the simulation or with the real robot
15pub enum Mode {
16 /// Runs the experiment in the simulation.
17 Simulation,
18 /// Runs the experiment on the real robot.
19 Real,
20}
21/// A trait which contains methods for handling the simulation. Apart from
22/// [`set_franka_urdf_path`](`Self::set_franka_urdf_path`),
23/// all methods have a default implementation,
24/// so you do not have to implement them if you do not need them.
25pub trait SimulationSetup {
26 /// Sets how often the simulation should be updated inside the control loop. The default is
27 /// 1/240s = 240 Hz
28 fn set_simulation_time_step(&self) -> Duration {
29 Duration::from_secs_f64(1. / 240.)
30 }
31 /// Return the path to the URDF file of the Franka robot.
32 fn set_franka_urdf_path(&self) -> PathBuf;
33 /// this method runs directly after the simulation is spawned. Use this method for example
34 /// to load additional object into the simulation.
35 fn setup_simulation(&mut self, _client: Rc<RefCell<PhysicsClient>>) {}
36 /// this method is intended to let you setup the camera. It is run directly after setup_simulation.
37 fn setup_camera(&mut self, _client: Rc<RefCell<PhysicsClient>>) {}
38 /// determine how you want to get the camera image with this method.
39 fn get_camera_image(&mut self, _client: Rc<RefCell<PhysicsClient>>) -> RgbaImage {
40 RgbaImage::default()
41 }
42}
43/// A trait which contains methods for handling the real robot. All methods have a default implementation,
44/// so you do not have to implement them if you do not need them.
45pub trait RealSetup {
46 /// This method runs directly on calling [`Experiment::new`](`Experiment::new`). You can use
47 /// it to run specific code which is only needed when using the real hardware.
48 fn setup_real(&mut self) {}
49 /// Runs directly after [`setup_real`](`Self::setup_real`) and is intended for setting up the camera.
50 fn setup_camera(&mut self) {}
51 /// determine how you want to get the camera image with this method.
52 fn get_camera_image(&mut self) -> RgbaImage {
53 RgbaImage::default()
54 }
55}
56/// Use it to create a new experiment which can either run on the real robot or in the simulation.
57///
58/// Use [`new`](`Self::new`) to create an experiment and then use [`new_robot`](`Self::new_robot`) to
59/// create a new robot.
60///
61pub struct Experiment<Sim: ?Sized + SimulationSetup, Real: ?Sized + RealSetup> {
62 pub environment: FrankaEnvironment,
63 pub sim_setup: Box<Sim>,
64 pub real_setup: Box<Real>,
65}
66
67impl Experiment<dyn SimulationSetup, dyn RealSetup> {
68 /// Use it to create a new experiment which can either run on the real robot or in the simulation
69 ///
70 /// # Arguments
71 /// * `mode` - specify whether to run the experiment in simulation or with a real robot
72 /// * `sim_setup` - something that implements [`SimulationSetup`](`SimulationSetup`).
73 /// * `real_setup` - something that implements [`RealSetup`](`RealSetup`).
74 ///
75 /// # Example
76 /// ```no_run
77 /// use franka_interface::experiment::{Experiment, Mode, RealSetup, SimulationSetup};
78 /// use franka_interface::types::Vector7;
79 /// use franka_interface::RobotArguments;
80 /// use std::f64::consts::PI;
81 /// use std::path::PathBuf;
82 /// struct MySimSetup {}
83 /// impl SimulationSetup for MySimSetup {
84 /// fn set_franka_urdf_path(&self) -> PathBuf {
85 /// "path/to/panda.urdf".into()
86 /// }
87 /// }
88 /// struct MyRealSetup {}
89 /// impl RealSetup for MyRealSetup {}
90 /// let mut env = Experiment::new(Mode::Simulation, MySimSetup {}, MyRealSetup {});
91 /// let mut robot = env.new_robot(RobotArguments {
92 /// hostname: "franka".to_string(),
93 /// base_pose: None,
94 /// initial_config: None,
95 /// });
96 /// robot.joint_motion(
97 /// 0.1,
98 /// Vector7::from_column_slice(&[1., PI / 4., 0., -2. * PI / 4., 0., PI / 2., -PI / 4.]),
99 /// );
100 /// println!("{:?}", robot.get_state());
101 /// ```
102 pub fn new(
103 mode: Mode,
104 mut sim_setup: impl SimulationSetup + 'static,
105 mut real_setup: impl RealSetup + 'static,
106 ) -> Experiment<dyn SimulationSetup, dyn RealSetup> {
107 match mode {
108 Mode::Simulation => {
109 let environment = Box::new(FrankaSimEnvironment::new(
110 sim_setup.set_simulation_time_step(),
111 ));
112
113 sim_setup.setup_simulation(environment.client.clone());
114 sim_setup.setup_camera(environment.client.clone());
115 let environment = FrankaEnvironment::Simulation(environment);
116 Experiment {
117 environment,
118 sim_setup: Box::new(sim_setup),
119 real_setup: Box::new(real_setup),
120 }
121 }
122 Mode::Real => {
123 let environment = FrankaEnvironment::Real(Box::new(FrankaRealEnvironment::new()));
124 real_setup.setup_real();
125 real_setup.setup_camera();
126 Experiment {
127 environment,
128 sim_setup: Box::new(sim_setup),
129 real_setup: Box::new(real_setup),
130 }
131 }
132 }
133 }
134 /// returns the current image from the robot. Make sure you implemented `get_camera_image`
135 /// in your [`RealSetup`](`RealSetup`) and [`SimulationSetup`](`SimulationSetup`).
136 pub fn get_image(&mut self) -> RgbaImage {
137 match &self.environment {
138 FrankaEnvironment::Real(_) => self.real_setup.get_camera_image(),
139 FrankaEnvironment::Simulation(environment) => {
140 self.sim_setup.get_camera_image(environment.client.clone())
141 }
142 }
143 }
144
145 /// spawns/connects to a new robot using the [`RobotArguments`](`crate::RobotArguments`)
146 pub fn new_robot(&mut self, config: RobotArguments) -> Robot {
147 match &mut self.environment {
148 FrankaEnvironment::Real(_environment) => {
149 Robot::Real(FrankaReal::new(config.hostname.as_str()))
150 }
151 FrankaEnvironment::Simulation(environment) => {
152 let args = FrankaSimWithoutClient {
153 urdf_path: self.sim_setup.set_franka_urdf_path(),
154 base_pose: config.base_pose,
155 initial_config: config.initial_config,
156 };
157 Robot::Sim(FrankaSim::new(
158 environment.client.clone(),
159 args,
160 &environment.time_step,
161 ))
162 }
163 }
164 }
165 /// Query whether the current experiment is run in simulation
166 pub fn is_simulation(&self) -> bool {
167 match self.environment {
168 FrankaEnvironment::Simulation(_) => true,
169 FrankaEnvironment::Real(_) => false,
170 }
171 }
172 /// allows accessing the PhysicsClient in Simulation by defining a closure that takes
173 /// the PhysicsClient as input.
174 /// Nothing will happen if the experiment is not run in the simulation
175 /// # Return
176 /// * In the simulation it will return Some(T) where T is the return type of the client_cb
177 /// * Will return None when not executed with the simulation.
178 pub fn use_physics_client<F: FnMut(RefMut<PhysicsClient>) -> T, T>(
179 &mut self,
180 mut client_cb: F,
181 ) -> Option<T> {
182 match &self.environment {
183 FrankaEnvironment::Simulation(sim) => Some(client_cb(sim.client.borrow_mut())),
184 FrankaEnvironment::Real(_) => None,
185 }
186 }
187}