Standing.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2018-2019, CNRS-UM LIRMM
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  * this list of conditions and the following disclaimer in the documentation
13  * and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25  * POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include "Standing.h"
29 
30 #include <mc_rtc/constants.h>
31 
32 #include <ExternalFootstepPlanner/Plan.h>
33 #include <ExternalFootstepPlanner/Request.h>
35 
36 namespace lipm_walking
37 {
38 
39 namespace
40 {
41 constexpr double COM_STIFFNESS = 5.; // standing has CoM set-point task
42 }
43 
44 void states::Standing::configure(const mc_rtc::Configuration & config)
45 {
46  config("autoplay", autoplay_);
47  config("autoplay_plans", autoplay_plans_);
48 }
49 
51 {
52  auto & ctl = controller();
53  ctl.startWalking = autoplay_;
54  ctl.walkingState = WalkingState::Standing;
55  // Reset pendulum state starting from the current CoM state
56  // This is done to ensure that there is no discontinuity when entering the
57  // Standing state from any external state.
58  double lambda = mc_rtc::constants::GRAVITY / ctl.robot().com().z();
59  ctl.pendulum().reset(lambda, ctl.robot().com(), ctl.robot().comVelocity(), ctl.robot().comAcceleration());
60 
61  supportContact_ = ctl.supportContact();
62  targetContact_ = ctl.targetContact();
63 
64  leftFootRatio_ = ctl.leftFootRatio();
65  ctl.isWalking = false;
66  if(supportContact_.surfaceName == "RightFootCenter")
67  {
68  leftFootContact_ = targetContact_;
69  rightFootContact_ = supportContact_;
70  }
71  else if(supportContact_.surfaceName == "LeftFootCenter")
72  {
73  leftFootContact_ = supportContact_;
74  rightFootContact_ = targetContact_;
75  }
76  else
77  {
78  mc_rtc::log::error_and_throw<std::invalid_argument>("Unknown surface name: {}", supportContact_.surfaceName);
79  }
80 
81  if(ctl.isLastDSP())
82  {
83  // When reaching the end of an external plan, do not repeat the previous plan
84  // This prevents walking until another plan is requested and received.
85  if(ctl.plan.name == "external")
86  {
87  ctl.plan.resetContacts(ctl.planInterpolator.getPlan("external").contacts());
88  }
89  ctl.loadFootstepPlan(ctl.plan.name);
90  }
91 
92  ctl.setContacts({{ContactState::Left, leftFootContact_.pose}, {ContactState::Right, rightFootContact_.pose}});
93 
94  if(!ctl.pauseWalking)
95  {
96  ctl.updatePlan(ctl.plan.name);
97  }
98  else
99  {
100  mc_rtc::log::info("[Standing] Walking is paused");
101  }
102  updateTarget(leftFootRatio_);
103 
104  logger().addLogEntry("walking_phase", []() { return 3.; });
105  ctl.stopLogSegment();
106 
107  if(ctl.startWalking && !ctl.pauseWalking) // autoplay
108  {
109  auto plans = std::vector<std::string>{};
110  if(autoplay_plans_.size())
111  { // If defined, use plans from local state configuration
112  plans = autoplay_plans_;
113  }
114  else
115  { // otherwise use global plans
116  plans = ctl.config()("autoplay_plans", std::vector<std::string>{});
117  }
118  if(plans.size() == 0)
119  {
120  ctl.startWalking = false;
121  ctl.config().add("autoplay", false);
122  }
123  else
124  {
125  std::string plan = plans[0];
126  plans.erase(plans.begin());
127  ctl.config().add("autoplay_plans", plans);
128  ctl.updatePlan(plan);
129  }
130  }
131 
132  if(gui())
133  {
134  using namespace mc_rtc::gui;
135  auto & gui_ = *gui();
136  gui_.removeElement({"Walking", "Main"}, "Pause walking");
137  gui_.removeElement({"Walking", "Main"}, "Resume walking");
138  gui_.removeElement({"Walking", "Main"}, "Start walking");
139  gui_.addElement({"Walking", "Main"},
140  ComboInput(
141  "Footstep plan", ctl.planInterpolator.availablePlans(), [&ctl]() { return ctl.plan.name; },
142  [&ctl](const std::string & name) { ctl.updatePlan(name); }));
143  gui_.addElement({"Standing"},
144  NumberInput(
145  "CoM target [0-1]", [this]() { return std::round(leftFootRatio_ * 10.) / 10.; },
146  [this](double leftFootRatio) { updateTarget(leftFootRatio); }),
147  Label("Left foot force [N]",
148  [&ctl]() { return ctl.realRobot().forceSensor("LeftFootForceSensor").force().z(); }),
149  Label("Right foot force [N]",
150  [&ctl]() { return ctl.realRobot().forceSensor("RightFootForceSensor").force().z(); }),
151  Button("Go to left foot", [this]() { updateTarget(1.); }),
152  Button("Go to middle", [this]() { updateTarget(0.5); }),
153  Button("Go to right foot", [this]() { updateTarget(0.); }));
154  gui_.addElement({"Walking", "Main"},
155  Button(!controller().pauseWalking || (supportContact_.id == 0) ? "Start walking" : "Resume walking",
156  [this]() { startWalking(); }));
157  }
158 
159  runState(); // don't wait till next cycle to update reference and tasks
160 }
161 
163 {
164  logger().removeLogEntry("walking_phase");
165 
166  if(gui())
167  {
168  gui()->removeCategory({"Standing"});
169  gui()->removeElement({"Walking", "Main"}, "Footstep plan");
170  gui()->removeElement({"Walking", "Main"}, "Gait");
171  gui()->removeElement({"Walking", "Main"}, "Go to middle");
172  gui()->removeElement({"Walking", "Main"}, "Resume walking");
173  gui()->removeElement({"Walking", "Main"}, "Start walking");
174  }
175 }
176 
178 {
179  checkPlanUpdates();
180 
181  auto & ctl = controller();
182  auto & pendulum = ctl.pendulum();
183 
184  Eigen::Vector3d comTarget = copTarget_ + Eigen::Vector3d{0., 0., ctl.plan.comHeight()};
185  const Eigen::Vector3d & com_i = pendulum.com();
186  const Eigen::Vector3d & comd_i = pendulum.comd();
187  const Eigen::Vector3d & cop_f = copTarget_;
188 
189  double K = COM_STIFFNESS;
190  double D = 2 * std::sqrt(K);
191  Eigen::Vector3d comdd = K * (comTarget - com_i) - D * comd_i;
192  Eigen::Vector3d n = supportContact_.normal();
193  double lambda = n.dot(comdd + mc_rtc::constants::gravity) / n.dot(com_i - cop_f);
194  Eigen::Vector3d zmp = com_i - (mc_rtc::constants::gravity + comdd) / lambda;
195 
196  pendulum.integrateIPM(zmp, lambda, ctl.timeStep);
197  ctl.leftFootRatio(leftFootRatio_);
198  ctl.stabilizer()->target(pendulum.com(), pendulum.comd(), pendulum.comdd(), pendulum.zmp());
199 }
200 
202 {
203  using Foot = mc_plugin::ExternalFootstepPlanner::Foot;
204  auto & ctl = controller();
205 
206  if(ctl.externalFootstepPlanner.planningRequested())
207  {
208  // Request a plan that should be received by the standing state
209  // e.g this means we will wait for the plan here before completing
210  const auto lf_start = utils::SE2d{controller().robot().surfacePose("LeftFootCenter")};
211  const auto rf_start = utils::SE2d{controller().robot().surfacePose("RightFootCenter")};
212 
213  // XXX always starts with Left support foot
214  // Should probably record the last used support foot when entering standing state and start from the other foot
215  // instead to resume walk more naturally
216  auto defaultFoot = Foot::Right;
217  if(supportContact_.surfaceName == "RightFootCenter")
218  {
219  defaultFoot = Foot::Left;
220  }
221  auto lf = controller().robot().surfacePose("LeftFootCenter") * controller().robot().posW().inv();
222  auto rf = controller().robot().surfacePose("RightFootCenter") * controller().robot().posW().inv();
223  Eigen::Vector3d ref = ctl.datastore().call<Eigen::Vector3d>("HybridPlanner::GetVelocity");
224  if(ref.x() > 0.02)
225  {
226  bool rf_front_lf = rf.translation().x() > lf.translation().x() + 0.02;
227  bool lf_front_rf = lf.translation().x() > rf.translation().x() + 0.02;
228  if(rf_front_lf)
229  {
230  defaultFoot = Foot::Right;
231  }
232  if(lf_front_rf)
233  {
234  defaultFoot = Foot::Left;
235  }
236  }
237  else if(ref.x() < -0.02)
238  {
239  bool rf_front_lf = rf.translation().x() > lf.translation().x() + 0.02;
240  bool lf_front_rf = lf.translation().x() > rf.translation().x() + 0.02;
241  if(rf_front_lf)
242  {
243  defaultFoot = Foot::Left;
244  }
245  if(lf_front_rf)
246  {
247  defaultFoot = Foot::Right;
248  }
249  }
250  ctl.externalFootstepPlanner.requestPlan(
251  ExternalPlanner::Standing, defaultFoot, lf_start, rf_start,
252  ctl.externalFootstepPlanner.allowedTimeStanding()); // XXX hardcoded allowed time
253  }
254 
255  if(ctl.externalFootstepPlanner.hasPlan(ExternalPlanner::Standing)
256  || ctl.externalFootstepPlanner.hasPlan(ExternalPlanner::DoubleSupport))
257  {
258  // If we have received a plan requested during this Standing phase or the previous DoubleSupport phase
259  ctl.plan.resetContacts(ctl.externalFootstepPlanner.plan());
260  ctl.updatePlan("external");
261  }
262 }
263 
265 {
266  auto & ctl = controller();
267 
268  if(ctl.plan.name == "external")
269  {
270  handleExternalPlan();
271  }
272  else if(ctl.planInterpolator.checkPlanUpdated())
273  {
274  ctl.loadFootstepPlan(ctl.planInterpolator.customPlanName());
275  }
276 }
277 
278 void states::Standing::updateTarget(double leftFootRatio)
279 {
280  auto & sole = controller().sole();
281  if(!controller().stabilizer()->inDoubleSupport())
282  {
283  mc_rtc::log::error("Cannot update CoM target while in single support");
284  return;
285  }
286  leftFootRatio = clamp(leftFootRatio, 0., 1., "Standing target");
287  sva::PTransformd X_0_lfr =
288  sva::interpolate(rightFootContact_.anklePose(sole), leftFootContact_.anklePose(sole), leftFootRatio);
289  copTarget_ = X_0_lfr.translation();
290  leftFootRatio_ = leftFootRatio;
291 }
292 
294 {
295  auto & ctl = controller();
296 
297  if(!ctl.startWalking || ctl.pauseWalking)
298  {
299  return false;
300  }
301 
302  ctl.mpc().contacts(supportContact_, targetContact_, ctl.nextContact());
303  ctl.mpc().phaseDurations(0., ctl.plan.initDSPDuration(), ctl.singleSupportDuration());
304  if(ctl.updatePreview())
305  {
306  ctl.nextDoubleSupportDuration(ctl.plan.initDSPDuration());
307  ctl.startLogSegment(ctl.plan.name);
308  ctl.isWalking = true;
309  output("DoubleSupport");
310  return true;
311  }
312  return false;
313 }
314 
316 {
317  auto & ctl = controller();
318  if(ctl.isLastSSP())
319  {
320  mc_rtc::log::error("No footstep in contact plan");
321  return;
322  }
323  ctl.startWalking = true;
324  if(ctl.pauseWalking)
325  {
326  gui()->removeElement({"Walking", "Main"}, "Resume walking");
327  ctl.pauseWalking = false;
328  }
329  gui()->addElement({"Walking", "Main"}, mc_rtc::gui::Button("Pause walking", [&ctl]()
330  { ctl.pauseWalkingCallback(/* verbose = */ false); }));
331 }
332 
333 } // namespace lipm_walking
334 
335 EXPORT_SINGLE_STATE("LIPMWalking::Standing", lipm_walking::states::Standing)
double clamp(double v, double vMin, double vMax)
Definition: clamp.h:47
void updateTarget(double leftFootRatio)
Definition: Standing.cpp:278
void configure(const mc_rtc::Configuration &) override
Definition: Standing.cpp:44
bool checkTransitions() override
Definition: Standing.cpp:293