WipingImpController.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
22#include "WipingImpController.h"
23
24#include <map>
25#include <mutex>
26
28#include <RobotAPI/components/units/RobotUnit/RobotUnit.h> // FIXME avoid this
29
31
33{
34 NJointControllerRegistration<NJointWipingImpMPController>
36
39 const NJointControllerConfigPtr& config,
40 const VirtualRobot::RobotPtr& robot) :
42 {
43 ARMARX_IMPORTANT << getClassName() + " construction done";
44 }
45
46 std::string
48 {
49 return "NJointWipingImpMPController";
50 }
51
52 void
53 NJointWipingImpMPController::updateMPConfig(const ::armarx::aron::data::dto::DictPtr& dto,
54 const Ice::Current& iceCurrent)
55 {
56 if (resetMPsV1(dto, controllableNodeSets, nullptr))
57 {
58 for (auto& pair : limb)
59 {
60 pair.second->rtFirstRun.store(true);
61 }
62 }
63 }
64
65 void
66 NJointWipingImpMPController::updateAdaptiveConfig(const ::armarx::aron::data::dto::DictPtr& dto,
67 const Ice::Current& iceCurrent)
68 {
69 adaptionCfg = arondto::WipingAdaptionConfigs::FromAron(dto);
70 for (auto& pair : limb)
71 {
72 auto search = adaptionCfg.cfg.find(pair.first);
73 if (search == adaptionCfg.cfg.end())
74 {
75 ARMARX_ERROR << "Arm " << pair.first << " is not included in the adaption config";
76 }
77 // ARMARX_CHECK_NOT_EQUAL(search, adaptionCfg.cfg.end());
78 }
79 adaptionReady.store(true);
80 }
81
82 void
84 {
86 // for (auto& pair: limb)
87 // {
88 // adaptionCfg.cfg[pair.first] = arondto::WipingAdaptionConfigs();
89 // }
90 for (auto& pair : limb)
91 {
92 adaptionStatus[pair.first] = AdaptionStatus();
93 adaptionStatus[pair.first].lastPosition =
94 common::getPos(pair.second->rtStatus.currentPose).head<2>();
95 }
96 }
97
98 void
100 {
101 if (not adaptionReady.load())
102 {
103 return;
104 }
105 const std::lock_guard<std::recursive_mutex> lock(mtx_mps);
106 auto [rtTargetSafe, ftSafe] = additionalTaskUpdateStatus();
107
108 // when ft guard is enabled and rt is not safe due to ft trigger, then we stop the corresponding mp
109 std::map<std::string, bool> flagMPToStop;
110 // double deltaT = 0.0;
111 for (auto& pair : limb)
112 {
113 flagMPToStop[pair.first] = not limb.at(pair.first)->controller.ftsensor.ftSafe.load();
114 pair.second->nonRTDeltaT =
115 pair.second->rtStatusInNonRT.accumulateTime - pair.second->nonRTAccumulateTime;
116 // deltaT = pair.second->nonRTDeltaT;
117 pair.second->nonRTAccumulateTime = pair.second->rtStatusInNonRT.accumulateTime;
118 }
119 if (hands)
120 {
121 for (auto& pair : hands->hands)
122 {
123 pair.second->nonRTDeltaT =
124 pair.second->rtsInNonRT.accumulateTime - pair.second->nonRTAccumulateTime;
125 pair.second->nonRTAccumulateTime = pair.second->rtsInNonRT.accumulateTime;
126 }
127 }
128
129 std::map<std::string, bool> mpRunning;
130 for (auto& mp : mps)
131 {
132 const auto mpNodeSet = mp.second.mp->getNodeSetName();
133 mpRunning[mp.second.mp->getMPName()] = mp.second.mp->isRunning();
134
135 auto search = flagMPToStop.find(mpNodeSet);
136 if (search != flagMPToStop.end() and flagMPToStop.at(mpNodeSet) and
137 mp.second.mp->isRunning() and
138 (limb.at(mpNodeSet)->controller.ftsensor.isForceGuardEnabled() or
139 limb.at(mpNodeSet)->controller.ftsensor.isTorqueGuardEnabled()))
140 {
141 mp.second.mp->stop();
142 ARMARX_INFO << "Due to force/torque safe guard, MP " << mp.second.mp->getMPName()
143 << " is stopped at " << mp.second.mp->getCanonicalValue();
144 }
145
146 if (mp.second.mp->isFinished())
147 {
148 continue;
149 }
150 if (mp.second.mp->getRole() == "taskspace")
151 {
152 auto& arm = limb.at(mp.second.mp->getNodeSetName());
153 if (mp.second.mp->isFirstRun())
154 {
155 ARMARX_INFO << "checking TSMP initial status ...";
156 /// Here we check if the TSMP start pose is too far away from the current pose, if so, we would
157 /// prefer to reset the mp start from the current pose.
158 const std::vector<double> mpStart = mp.second.mp->getStartVec();
159 const auto mpStartPose = common::dVecToMat4(mpStart);
160 if (mp.second.mp->getStartFromPrevTarget() or
162 arm->rtStatusInNonRT.currentPose,
163 mpStartPose,
164 "current pose",
165 "TSMP initial start pose",
166 arm->nonRtConfig.safeDistanceMMToGoal,
167 arm->nonRtConfig.safeRotAngleDegreeToGoal,
168 mp.second.mp->getMPName() + "mp_set_target"))
169 {
170 if (mp.second.mp->getStartFromPrevTarget())
171 {
172 ARMARX_INFO << "User requested to start the current TS MP from the "
173 "previous target pose";
174 }
175 else
176 {
178 << "deviation from current pose too large, reset MP start pose";
179 }
180 mp.second.mp->validateInitialState(
181 common::mat4ToDVec(arm->rtStatusInNonRT.desiredPose));
182 }
183 ARMARX_INFO << "done";
184 }
185 mp::TSMPInputPtr in = std::dynamic_pointer_cast<mp::TSMPInput>(mp.second.input);
186 in->pose = arm->rtStatusInNonRT.currentPose;
187 in->vel = arm->rtStatusInNonRT.currentTwist;
188 // in->deltaT = arm->rtStatusInNonRT.deltaT;
189 in->deltaT = arm->nonRTDeltaT;
190 }
191 else if (mp.second.mp->getRole() == "nullspace")
192 {
193 auto& arm = limb.at(mp.second.mp->getNodeSetName());
194 mp::JSMPInputPtr in = std::dynamic_pointer_cast<mp::JSMPInput>(mp.second.input);
195 ARMARX_CHECK_EQUAL(arm->rtStatusInNonRT.nDoF,
196 arm->rtStatusInNonRT.jointPosition.size());
197 in->angleRadian = arm->rtStatusInNonRT.jointPosition;
198 in->angularVel = arm->rtStatusInNonRT.qvelFiltered;
199 // in->deltaT = arm->rtStatusInNonRT.deltaT;
200 in->deltaT = arm->nonRTDeltaT;
201 }
202 else if (mp.second.mp->getRole() == "hand")
203 {
204 auto& hand = hands->hands.at(mp.second.mp->getNodeSetName());
205 mp::JSMPInputPtr in = std::dynamic_pointer_cast<mp::JSMPInput>(mp.second.input);
207 << "Fix the following line, see new controller implement in template.h";
208 // in->angleRadian = hand->rtsInNonRT.jointPosition.value();
209 hand->nonRTDeltaT = hand->bufferRTToNonRT.getReadBuffer().accumulateTime -
210 hand->nonRTAccumulateTime;
211 hand->nonRTAccumulateTime = hand->bufferRTToNonRT.getReadBuffer().accumulateTime;
212 // in->deltaT = hand->bufferRTToNonRT.getReadBuffer().deltaT;
213 in->deltaT = hand->nonRTDeltaT;
214 }
215 }
216 if (not rtTargetSafe)
217 {
219
220 // return;
221 // Make sure that you do not call additionalTaskSetTarget before return;
222 // since we overwrite the arm->nonRtConfig in additionalTaskUpdateStatus() with user config, which does not
223 // align with the up-to-date mp status. userConfig is only updated to align with MP status everytime a MP
224 // finishes execution. Only then, user can use this controller as if no mp is there.
225 }
226 ARMARX_INFO << VAROUT(rtTargetSafe) << deactivateSpam(0.5);
227 runMPs(rtTargetSafe);
228
229 std::lock_guard<std::recursive_mutex> adaptLock(adaptionMtx);
230 /// set mp target to nonRT config data structure
231 for (auto& mp : mps)
232 {
233 if (not mpRunning.at(mp.second.mp->getMPName()))
234 {
235 continue;
236 }
237 for (auto& ftGuard : mpConfig.ftGuard)
238 {
239 if (ftGuard.mpName == mp.second.mp->getMPName())
240 {
241 bool const forceGuard =
242 (ftGuard.force.has_value() and
243 ftGuard.force.value() >= mp.second.mp->getCanonicalValue());
244 bool const torqueGuard =
245 (ftGuard.torque.has_value() and
246 ftGuard.torque.value() >= mp.second.mp->getCanonicalValue());
247
248 auto& arm = limb.at(mp.second.mp->getNodeSetName());
249 bool const resetForce =
250 not arm->controller.ftsensor.isForceGuardEnabled() and forceGuard;
251 bool const resetTorque =
252 not arm->controller.ftsensor.isTorqueGuardEnabled() and torqueGuard;
253 if (resetForce or resetTorque)
254 {
255 ARMARX_INFO << "Triggering force torque safety guard for "
256 << arm->kinematicChainName << " at can value "
257 << mp.second.mp->getCanonicalValue();
258 }
259
260 arm->nonRtConfig.ftConfig.enableSafeGuardForce = forceGuard;
261 arm->nonRtConfig.ftConfig.enableSafeGuardTorque = torqueGuard;
262 arm->controller.ftsensor.enableSafeGuard(resetForce, resetTorque);
263 }
264 }
265
266 if (mp.second.mp->getRole() == "taskspace")
267 {
268 auto& nodeSetName = mp.second.mp->getNodeSetName();
269 auto& cfg = adaptionCfg.cfg.at(nodeSetName);
270 auto& s = adaptionStatus.at(nodeSetName);
271 auto& arm = limb.at(nodeSetName);
272 mp::TSMPOutputPtr out = std::dynamic_pointer_cast<mp::TSMPOutput>(mp.second.output);
273
274 /// Note that here we only report warning but still set target to RT, we rely on MP phase stop and
275 /// RT safety mechanism to stop or pause the action if needed
277 arm->rtStatusInNonRT.currentPose,
278 arm->nonRtConfig.desiredPose,
279 "current pose",
280 "desiredPose for RT",
281 arm->nonRtConfig.safeDistanceMMToGoal,
282 arm->nonRtConfig.safeRotAngleDegreeToGoal,
283 mp.second.mp->getMPName() + "mp_set_target");
284 arm->nonRtConfig.desiredPose = out->pose;
285 arm->nonRtConfig.desiredTwist = out->vel;
286
287 /// Note: use force to generate acceleration target and aggregate to the target velocity
288 float desiredVelZ =
289 cfg.kpf * (cfg.targetForce - arm->rtStatusInNonRT.currentForceTorque(2));
290 arm->nonRtConfig.desiredTwist(2) -= desiredVelZ;
291 arm->nonRtConfig.desiredTwist.tail<3>().setZero();
292
293 // float dT = static_cast<float>(arm->rtStatusInNonRT.deltaT);
294 float dT = static_cast<float>(arm->nonRTDeltaT);
295
296 /// check phase stop
297 auto& adaptKImp = arm->nonRtConfig.kpImpedance;
298 if (not cfg.originalKpImp.has_value())
299 {
300 cfg.originalKpImp = adaptKImp;
301 }
302 Eigen::Vector6f originalKpImp = cfg.originalKpImp.value();
303 if (tooFar)
304 {
305 Eigen::Vector2f currentForceXY =
306 arm->rtStatusInNonRT.currentForceTorque.head<2>();
307 Eigen::Vector2f dragForce =
308 currentForceXY - cfg.deadZoneForce * currentForceXY / currentForceXY.norm();
309 adaptKImp.head<2>() =
310 (adaptKImp.head<2>().array() - dT * cfg.adaptForceCoeff * dragForce.norm())
311 .max(0.0f);
312 // lastDiff = diff;
313 }
314 else
315 {
316 adaptKImp.head<2>() = (adaptKImp.head<2>().array() + dT * cfg.adaptCoeff)
317 .min(originalKpImp.head<2>().array());
318 }
319 adaptKImp(2) = originalKpImp(2);
320
321 /// Note: integrate the target pose
322 arm->nonRtConfig.desiredPose.block<3, 1>(0, 3) +=
323 dT * arm->nonRtConfig.desiredTwist.head<3>();
324
325 /// Note: checking the duration that arm is tooFar to indicate a new location for intended wiping
326 if (tooFar)
327 {
328 Eigen::Vector2f currentXY = arm->rtStatusInNonRT.currentPose.block<2, 1>(0, 3);
329 if ((s.lastPosition - currentXY).norm() < cfg.changePositionTolerance)
330 {
331 s.changeTimer += dT;
332 }
333 else
334 {
335 s.lastPosition = currentXY;
336 s.changeTimer = 0;
337 }
338
339 if (s.changeTimer > cfg.changeTimerThreshold)
340 {
341 arm->nonRtConfig.desiredPose.block<2, 1>(0, 3) = currentXY;
342 // isPhaseStop = false; /// Check if desiredPose above in report deviation is correct
343 s.changeTimer = 0;
344 }
345 }
346 else
347 {
348 s.changeTimer = 0;
349 }
350 }
351 else if (mp.second.mp->getRole() == "nullspace")
352 {
353 auto& arm = limb.at(mp.second.mp->getNodeSetName());
354 mp::JSMPOutputPtr out = std::dynamic_pointer_cast<mp::JSMPOutput>(mp.second.output);
355 ARMARX_CHECK_EQUAL(arm->rtStatusInNonRT.nDoF, out->angleRadian.size());
356 arm->nonRtConfig.desiredNullspaceJointAngles.value() = out->angleRadian;
357 }
358 else if (mp.second.mp->getRole() == "hand")
359 {
360 mp::JSMPOutputPtr out = std::dynamic_pointer_cast<mp::JSMPOutput>(mp.second.output);
361 auto& hand = hands->hands.at(mp.second.mp->getNodeSetName());
362 mp::JSMPInputPtr in = std::dynamic_pointer_cast<mp::JSMPInput>(mp.second.input);
363 ARMARX_CHECK_EQUAL(hand->targetNonRT.jointPosition.value().size(),
364 out->angleRadian.size());
365 hand->targetNonRT.jointPosition.value() = out->angleRadian;
366 }
367 if (mpRunning.at(mp.second.mp->getMPName()) and mp.second.mp->isFinished())
368 {
369 ARMARX_INFO << "reset buffer for nonRtConfig of " << mp.second.mp->getMPName();
370 for (auto& pair : limb)
371 {
372 auto& arm = pair.second;
373 arm->bufferConfigUserToNonRt.reinitAllBuffers(arm->nonRtConfig);
374 }
375 for (auto& pair : limb)
376 {
377 userConfig.limbs.at(pair.first) = pair.second->nonRtConfig;
378 }
379 if (hands)
380 {
381 hands->reinitBuffer(userConfig.hands);
382 }
383 }
384 }
386 }
387} // namespace armarx::control::njoint_mp_controller::task_space
#define VAROUT(x)
SpamFilterDataPtr deactivateSpam(float deactivationDurationSec=10.0f, const std::string &identifier="", bool deactivate=true) const
disables the logging for the current line for the given amount of seconds.
Definition Logging.cpp:99
bool resetMPsV1(const ::armarx::aron::data::dto::DictPtr &dto, const std::map< std::string, VirtualRobot::RobotNodeSetPtr > &rnsMap, const std::shared_ptr< common::coordination::Coordination > &coordinator)
Definition MPPool.cpp:604
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
std::map< std::string, VirtualRobot::RobotNodeSetPtr > controllableNodeSets
NJointTaskspaceImpedanceController(const RobotUnitPtr &robotUnit, const NJointControllerConfigPtr &config, const VirtualRobot::RobotPtr &)
void rtPreActivateController() override
This function is called before the controller is activated.
NJointWipingImpMPController(const RobotUnitPtr &robotUnit, const NJointControllerConfigPtr &config, const VirtualRobot::RobotPtr &)
std::string getClassName(const Ice::Current &=Ice::emptyCurrent) const override
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
void rtPreActivateController() override
This function is called before the controller is activated.
#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
Matrix< float, 6, 1 > Vector6f
std::shared_ptr< class Robot > RobotPtr
Definition Bus.h:19
This file is part of ArmarX.
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
Eigen::Block< Eigen::Matrix4f, 3, 1 > getPos(Eigen::Matrix4f &matrix)
Definition utils.cpp:301
NJointControllerRegistration< NJointWipingImpMPController > registrationControllerNJointWipingImpMPController("NJointWipingImpMPController")
IceUtil::Handle< class RobotUnit > RobotUnitPtr
Definition FTSensor.h:34