WipingMixImpVelColController.cpp
Go to the documentation of this file.
1/**
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @author Jianfeng Gao ( jianfeng dot gao at kit dot edu )
17 * @date 2024
18 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
19 * GNU General Public License
20 */
21
23
25#include <RobotAPI/components/units/RobotUnit/RobotUnit.h> // FIXME avoid this
26
28
30{
31 NJointControllerRegistration<NJointWipingMixImpVelColMPController>
33 "NJointWipingMixImpVelColMPController");
34
44
45 std::string
47 {
48 return "NJointWipingMixImpVelColMPController";
49 }
50
51 void
53 const ::armarx::aron::data::dto::DictPtr& dto,
54 const Ice::Current& iceCurrent)
55 {
57 {
58 for (auto& pair : limb)
59 {
60 pair.second->rtFirstRun.store(true);
61 }
62 }
63 }
64
65 void
67 const ::armarx::aron::data::dto::DictPtr& dto,
68 const Ice::Current& iceCurrent)
69 {
70 const auto adaptionCfg = arondto::WipingAdaptionConfigs::FromAron(dto);
71
72 bool valid = true;
73
74 for (auto& pair : limb)
75 {
76 auto search = adaptionCfg.cfg.find(pair.first);
77 if (search == adaptionCfg.cfg.end())
78 {
79 ARMARX_ERROR << "Arm " << pair.first << " is not included in the adaption config";
80 valid = false;
81 }
82 }
83
84 if (valid)
85 {
86 bufferAdaptationConfigUserToNonRt.getWriteBuffer() = adaptionCfg;
87 bufferAdaptationConfigUserToNonRt.commitWrite();
88 ARMARX_INFO << "Updated adaptation config";
89
90 adaptionReady.store(true);
91 }
92 else
93 {
94 ARMARX_ERROR << "Failed to update adaption config";
95 }
96 }
97
98 void
100 {
101 for (auto& pair : limb)
102 {
103 pair.second->reInitPreActivate.store(true);
104 }
105
108
109 for (const auto& pair : limb)
110 {
112 status.kpImpedance = pair.second->rtConfig.kpImpedance;
113 status.kpCartesianVel = pair.second->rtConfig.kpCartesianVel;
114 status.desiredPose = pair.second->rtConfig.desiredPose;
115 adaptionStatus[pair.first] = status;
116 }
117 }
118
119 void
121 {
122 if (not adaptionReady.load())
123 {
124 return;
125 }
126 adaptionCfg = bufferAdaptationConfigUserToNonRt.getUpToDateReadBuffer();
127
128 std::lock_guard<std::recursive_mutex> lock(mtx_mps);
129 auto [rtTargetSafe, ftSafe] = additionalTaskUpdateStatus();
130
131 // when ft guard is enabled and rt is not safe due to ft trigger, then we stop the corresponding mp
132 std::map<std::string, bool> flagMPToStop;
133 for (auto& pair : limb)
134 {
135 flagMPToStop[pair.first] = limb.at(pair.first)->controller.ftsensor.ftSafe.load();
136 }
137
138 const double timeScalingFactor =
139 adaptionCfg.timeScalingFactor.has_value()
140 ? fmin(fmax(adaptionCfg.timeScalingFactor.value(), 0.25), 4.)
141 : 1.;
142
143 const double sizeScalingFactor =
144 adaptionCfg.sizeScalingFactor.has_value() ? adaptionCfg.sizeScalingFactor.value() : 1.;
145
146 std::map<std::string, bool> mpRunning;
147 for (auto& _mp : mps)
148 {
149 const auto mpNodeSet = _mp.second.mp->getNodeSetName();
150 auto search = flagMPToStop.find(mpNodeSet);
151 if (_mp.second.mp->isStopRequested())
152 {
153 _mp.second.mp->stop();
154 ARMARX_INFO << "user requested to stop mp execution at canonical value: "
155 << _mp.second.mp->getCanonicalValue();
156 }
157 if (search != flagMPToStop.end() and flagMPToStop.at(mpNodeSet) and
158 _mp.second.mp->isRunning() and
159 (limb.at(mpNodeSet)->controller.ftsensor.isForceGuardEnabled() or
160 limb.at(mpNodeSet)->controller.ftsensor.isTorqueGuardEnabled()))
161 {
162 _mp.second.mp->stop();
163 ARMARX_INFO << "Due to force/torque safe guard, MP " << _mp.second.mp->getMPName()
164 << " is stopped at " << _mp.second.mp->getCanonicalValue();
165 }
166 mpRunning[_mp.second.mp->getMPName()] = _mp.second.mp->isRunning();
167 if (_mp.second.mp->isFinished())
168 {
169 continue;
170 }
171 if (_mp.second.mp->getRole() == "taskspace")
172 {
173 auto& arm = limb.at(_mp.second.mp->getNodeSetName());
174 if (_mp.second.mp->isFirstRun())
175 {
176 ARMARX_INFO << "checking TSMP initial status ...";
177 /// Here we check if the TSMP start pose is too far away from the current pose, if so, we would
178 /// prefer to reset the mp start from the current pose.
179 const std::vector<double> mpStart = _mp.second.mp->getStartVec();
180 const auto mpStartPose = common::dVecToMat4(mpStart);
182 arm->rtStatusInNonRT.currentPose,
183 mpStartPose,
184 "current pose",
185 "TSMP initial start pose",
186 arm->nonRtConfig.safeDistanceMMToGoal,
187 arm->nonRtConfig.safeRotAngleDegreeToGoal,
188 _mp.second.mp->getMPName() + "mp_set_target"))
189 {
191 << "-- deviation from current pose too large, reset MP start pose";
192 _mp.second.mp->validateInitialState(
193 common::mat4ToDVec(arm->rtStatusInNonRT.desiredPose));
194 }
195 ARMARX_INFO << "done";
196 }
197 mp::TSMPInputPtr in = std::dynamic_pointer_cast<mp::TSMPInput>(_mp.second.input);
198 in->pose = arm->rtStatusInNonRT.currentPose;
199 in->vel = arm->rtStatusInNonRT.currentTwist;
200 in->deltaT = arm->rtStatusInNonRT.deltaT * timeScalingFactor;
201 }
202 else if (_mp.second.mp->getRole() == "nullspace")
203 {
204 auto& arm = limb.at(_mp.second.mp->getNodeSetName());
205 mp::JSMPInputPtr in = std::dynamic_pointer_cast<mp::JSMPInput>(_mp.second.input);
206 ARMARX_CHECK_EQUAL(arm->rtStatusInNonRT.nDoF,
207 arm->rtStatusInNonRT.jointPosition.size());
208 in->angleRadian = arm->rtStatusInNonRT.jointPosition;
209 in->angularVel = arm->rtStatusInNonRT.qvelFiltered;
210 in->deltaT = arm->rtStatusInNonRT.deltaT * timeScalingFactor;
211 }
212 else if (_mp.second.mp->getRole() == "hand")
213 {
214 auto& hand = hands->hands.at(_mp.second.mp->getNodeSetName());
215 mp::JSMPInputPtr in = std::dynamic_pointer_cast<mp::JSMPInput>(_mp.second.input);
216 ARMARX_ERROR << "Fix the following line, see template.h";
217 // in->angleRadian =
218 // hand->bufferRTToNonRT.getUpToDateReadBuffer().jointPosition.value();
219 in->deltaT = hand->bufferRTToNonRT.getReadBuffer().deltaT * timeScalingFactor;
220 }
221 }
222 if (not rtTargetSafe)
223 {
225
226 // return;
227 // Make sure that you do not call additionalTaskSetTarget before return;
228 // since we overwrite the arm->nonRtConfig in additionalTaskUpdateStatus() with user config, which does not
229 // align with the up-to-date mp status. userConfig is only updated to align with MP status everytime a MP
230 // finishes execution. Only then, user can use this controller as if no mp is there.
231 }
232 runMPs(rtTargetSafe);
233
234 /// set mp target to nonRT config data structure
235 for (auto& _mp : mps)
236 {
237 if (not mpRunning.at(_mp.second.mp->getMPName()))
238 {
239 continue;
240 }
241 for (auto& ftGuard : mpConfig.ftGuard)
242 {
243 if (ftGuard.mpName == _mp.second.mp->getMPName())
244 {
245 bool const forceGuard =
246 (ftGuard.force.has_value() and
247 ftGuard.force.value() >= _mp.second.mp->getCanonicalValue());
248 bool const torqueGuard =
249 (ftGuard.torque.has_value() and
250 ftGuard.torque.value() >= _mp.second.mp->getCanonicalValue());
251
252 auto& arm = limb.at(_mp.second.mp->getNodeSetName());
253 bool const resetForce =
254 not arm->controller.ftsensor.isForceGuardEnabled() and forceGuard;
255 bool const resetTorque =
256 not arm->controller.ftsensor.isTorqueGuardEnabled() and torqueGuard;
257 if (resetForce or resetTorque)
258 {
259 ARMARX_INFO << "Triggering force torque safety guard for "
260 << arm->kinematicChainName << " at can value "
261 << _mp.second.mp->getCanonicalValue();
262 }
263
264 arm->nonRtConfig.ftConfig.enableSafeGuardForce = forceGuard;
265 arm->nonRtConfig.ftConfig.enableSafeGuardTorque = torqueGuard;
266 arm->controller.ftsensor.enableSafeGuard(resetForce, resetTorque);
267 }
268 }
269
270 if (_mp.second.mp->getRole() == "taskspace")
271 {
272 const auto& nodeSetName = _mp.second.mp->getNodeSetName();
273 auto& cfg = adaptionCfg.cfg.at(nodeSetName);
274 auto& s = adaptionStatus.at(nodeSetName);
275 auto& arm = limb.at(nodeSetName);
277 std::dynamic_pointer_cast<mp::TSMPOutput>(_mp.second.output);
278
279 /// Note that here we only report warning but still set target to RT, we rely on MP phase stop and
280 /// RT safety mechanism to stop or pause the action if needed
281 arm->nonRtConfig.desiredPose = s.desiredPose;
283 arm->rtStatusInNonRT.currentPose,
284 arm->nonRtConfig.desiredPose,
285 "current pose",
286 "desiredPose for RT",
287 arm->nonRtConfig.safeDistanceMMToGoal,
288 arm->nonRtConfig.safeRotAngleDegreeToGoal,
289 _mp.second.mp->getMPName() + "mp_set_target");
290 arm->nonRtConfig.desiredTwist = out->vel;
291
292 /// Note: use force to generate acceleration target and aggregate to the target velocity
293 if (arm->rtStatusInNonRT.rtTargetSafe)
294 {
295 const float desiredVelZ =
296 cfg.kpf * (cfg.targetForce - arm->rtStatusInNonRT.currentForceTorque(2));
297 arm->nonRtConfig.desiredTwist(2) -= desiredVelZ;
298 }
299 arm->nonRtConfig.desiredTwist.tail<3>().setZero();
300
301 const float dT = static_cast<float>(arm->rtStatusInNonRT.deltaT);
302
303 /// check phase stop
304 auto& adaptKImp = s.kpImpedance;
305 auto& adaptKVel = s.kpCartesianVel;
306
307 const Eigen::Vector2f& currentForceXY =
308 arm->rtStatusInNonRT.currentForceTorque.head<2>();
309 if (currentForceXY.norm() > cfg.deadZoneForce)
310 {
311 // Gradually decrease kp until zero
312 const Eigen::Vector2f dragForce =
313 currentForceXY - cfg.deadZoneForce * currentForceXY / currentForceXY.norm();
314 adaptKImp.head<2>().array() -= dT * cfg.adaptForceCoeff * dragForce.norm();
315 adaptKVel.head<2>().array() -= dT * cfg.adaptForceCoeff * dragForce.norm();
316 }
317 else
318 {
319 // Gradually increase kp until original value
320 adaptKImp.head<2>().array() += fabs(dT * cfg.adaptCoeff);
321 adaptKVel.head<2>().array() += fabs(dT * cfg.adaptCoeff);
322 }
323
324 // Clip between 0. and original Kp
325 adaptKImp.head<2>() = adaptKImp.head<2>()
326 .array()
327 .min(arm->nonRtConfig.kpImpedance.head<2>().array())
328 .max(0.0);
329 adaptKVel.head<2>() = adaptKVel.head<2>()
330 .array()
331 .min(arm->nonRtConfig.kpCartesianVel.head<2>().array())
332 .max(0.0);
333
334 arm->nonRtConfig.kpImpedance = adaptKImp;
335 arm->nonRtConfig.kpCartesianVel = adaptKVel;
336
337 // Scaling inverting trajectory
338 arm->nonRtConfig.desiredTwist.head<2>() *= sizeScalingFactor * timeScalingFactor;
339
340 /// Note: integrate the target pose
341 arm->nonRtConfig.desiredPose.block<3, 1>(0, 3) +=
342 dT * arm->nonRtConfig.desiredTwist.head<3>();
343
344 // Optionally restrict the workspace
345 if (cfg.minX.has_value())
346 {
347 arm->nonRtConfig.desiredPose(0, 3) =
348 fmax(arm->nonRtConfig.desiredPose(0, 3), *cfg.minX);
349 }
350 if (cfg.maxX.has_value())
351 {
352 arm->nonRtConfig.desiredPose(0, 3) =
353 fmin(arm->nonRtConfig.desiredPose(0, 3), *cfg.maxX);
354 }
355 if (cfg.minY.has_value())
356 {
357 arm->nonRtConfig.desiredPose(1, 3) =
358 fmax(arm->nonRtConfig.desiredPose(1, 3), *cfg.minY);
359 }
360 if (cfg.maxY.has_value())
361 {
362 arm->nonRtConfig.desiredPose(1, 3) =
363 fmin(arm->nonRtConfig.desiredPose(1, 3), *cfg.maxY);
364 }
365 if (cfg.minZ.has_value())
366 {
367 arm->nonRtConfig.desiredPose(2, 3) =
368 fmax(arm->nonRtConfig.desiredPose(2, 3), *cfg.minZ);
369 }
370
371 if (cfg.desiredUserPosition.has_value() and not cfg.desiredUserPosition->isZero())
372 {
373 if (not(*cfg.desiredUserPosition - s.lastDesiredUserPosition).isZero())
374 {
375 if (s.openDistanceToDesiredUserPosition > 0.)
376 {
377 ARMARX_INFO << "Didn't reach previously defined desired position "
378 << s.lastDesiredUserPosition;
379 }
380 s.lastDesiredUserPosition = *cfg.desiredUserPosition;
381 const Eigen::Vector2f distance =
382 s.lastDesiredUserPosition -
383 arm->nonRtConfig.desiredPose.block<2, 1>(0, 3);
384 s.openDistanceToDesiredUserPosition = distance.norm();
385 if (cfg.distanceToUserPositionScalingFactor.has_value())
386 {
387 s.openDistanceToDesiredUserPosition *=
388 *cfg.distanceToUserPositionScalingFactor;
389 }
390 ARMARX_INFO << "Initialized new desired user position at "
391 << s.lastDesiredUserPosition << " which is "
392 << s.openDistanceToDesiredUserPosition
393 << " mm from current desired position";
394 }
395
396 if (s.openDistanceToDesiredUserPosition > 0.)
397 {
398 Eigen::Vector2f desiredLinearVelocity =
399 s.lastDesiredUserPosition -
400 arm->nonRtConfig.desiredPose.block<2, 1>(0, 3);
401 desiredLinearVelocity = dT * cfg.kpLinear * desiredLinearVelocity /
402 desiredLinearVelocity.norm();
403 s.openDistanceToDesiredUserPosition -= desiredLinearVelocity.norm();
404 arm->nonRtConfig.desiredPose.block<2, 1>(0, 3) += desiredLinearVelocity;
405 if (s.openDistanceToDesiredUserPosition < 0.)
406 {
407 ARMARX_INFO << "Reached desired user position "
408 << s.lastDesiredUserPosition;
409 }
410 }
411 }
412
413 /// Note: checking the duration that arm is tooFar to indicate a new location for intended wiping
414 if (tooFar)
415 {
416 const Eigen::Vector2f currentXY =
417 arm->rtStatusInNonRT.currentPose.block<2, 1>(0, 3);
418 if ((s.lastPosition - currentXY).norm() < cfg.changePositionTolerance)
419 {
420 s.changeTimer += dT;
421 }
422 else
423 {
424 s.lastPosition = currentXY;
425 s.changeTimer = 0;
426 }
427
428 if (s.changeTimer > cfg.changeTimerThreshold)
429 {
430 arm->nonRtConfig.desiredPose.block<2, 1>(0, 3) = currentXY;
431 // isPhaseStop = false; /// Check if desiredPose above in report deviation is correct
432 s.changeTimer = 0;
433 ARMARX_INFO << "Updated desired position to "
434 << arm->nonRtConfig.desiredPose.block<3, 1>(0, 3).transpose();
435 }
436 }
437 else
438 {
439 s.changeTimer = 0;
440 }
441
442 s.desiredPose = arm->nonRtConfig.desiredPose;
443 }
444 else if (_mp.second.mp->getRole() == "nullspace")
445 {
446 auto& arm = limb.at(_mp.second.mp->getNodeSetName());
448 std::dynamic_pointer_cast<mp::JSMPOutput>(_mp.second.output);
449 ARMARX_CHECK_EQUAL(arm->rtStatusInNonRT.nDoF, out->angleRadian.size());
450 arm->nonRtConfig.desiredNullspaceJointAngles.value() = out->angleRadian;
451 }
452 else if (_mp.second.mp->getRole() == "hand")
453 {
455 std::dynamic_pointer_cast<mp::JSMPOutput>(_mp.second.output);
456 auto& hand = hands->hands.at(_mp.second.mp->getNodeSetName());
457 mp::JSMPInputPtr in = std::dynamic_pointer_cast<mp::JSMPInput>(_mp.second.input);
458 ARMARX_CHECK_EQUAL(hand->targetNonRT.jointPosition.value().size(),
459 out->angleRadian.size());
460 hand->targetNonRT.jointPosition.value() = out->angleRadian;
461 }
462 if (mpRunning.at(_mp.second.mp->getMPName()) and _mp.second.mp->isFinished())
463 {
464 ARMARX_INFO << "reset buffer for nonRtConfig of " << _mp.second.mp->getMPName();
465 for (auto& pair : limb)
466 {
467 auto& arm = pair.second;
468 arm->bufferConfigUserToNonRt.reinitAllBuffers(arm->nonRtConfig);
469 }
470 for (auto& pair : limb)
471 {
472 userConfig.limbs.at(pair.first) = pair.second->nonRtConfig;
473 }
474 if (hands)
475 {
476 hands->reinitBuffer(userConfig.hands);
477 }
478 }
479 }
481 }
482} // namespace armarx::control::njoint_mp_controller::task_space
bool resetMPs(const ::armarx::aron::data::dto::DictPtr &dto, const std::map< std::string, VirtualRobot::RobotNodeSetPtr > &rnsMap)
Definition MPPool.cpp:565
std::recursive_mutex mtx_mps
Definition MPPool.h:147
std::map< std::string, MPInputOutput > mps
Definition MPPool.h:146
MPListConfig mpConfig
this variable is only needed when constructing the MP instances, therefore you don't need to use trip...
Definition MPPool.h:153
NJointTaskspaceCollisionAvoidanceMixedImpedanceVelocityController(const RobotUnitPtr &robotUnit, const NJointControllerConfigPtr &config, const VirtualRobot::RobotPtr &robot)
NJointWipingMixImpVelColMPController(const RobotUnitPtr &robotUnit, const NJointControllerConfigPtr &config, const VirtualRobot::RobotPtr &)
void updateAdaptiveConfig(const ::armarx::aron::data::dto::DictPtr &dto, const Ice::Current &iceCurrent=Ice::emptyCurrent) override
void updateMPConfig(const ::armarx::aron::data::dto::DictPtr &dto, const Ice::Current &iceCurrent=Ice::emptyCurrent) override
#define ARMARX_CHECK_EQUAL(lhs, rhs)
This macro evaluates whether lhs is equal (==) rhs and if it turns out to be false it will throw an E...
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:190
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:196
std::shared_ptr< class Robot > RobotPtr
Definition Bus.h:19
std::shared_ptr< TSMPOutput > TSMPOutputPtr
Definition TSMP.h:47
std::shared_ptr< TSMPInput > TSMPInputPtr
Definition TSMP.h:46
std::shared_ptr< JSMPOutput > JSMPOutputPtr
Definition JSMP.h:48
std::shared_ptr< JSMPInput > JSMPInputPtr
Definition JSMP.h:47
bool detectAndReportPoseDeviationWarning(const Eigen::Matrix4f &pose1, const Eigen::Matrix4f &pose2, const std::string &namePose1, const std::string &namePose2, float positionThrMM, float angleThrDeg, const std::string &who)
Definition utils.cpp:773
Eigen::Matrix4f dVecToMat4(const DVec &dvec)
create Eigen:Matrix4f from 7D double vector.
Definition utils.cpp:325
DVec mat4ToDVec(const Eigen::Matrix4f &mat)
convert Eigen:Matrix4f to 7D double vector.
Definition utils.cpp:359
NJointControllerRegistration< NJointWipingMixImpVelColMPController > registrationControllerNJointWipingMixImpVelColMPController("NJointWipingMixImpVelColMPController")
std::vector< std::vector< T > > transpose(const std::vector< std::vector< T > > &src, Thrower thrower)
IceUtil::Handle< class RobotUnit > RobotUnitPtr
Definition FTSensor.h:34
double distance(const Point &a, const Point &b)
Definition point.hpp:95