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