GazeController.cpp
Go to the documentation of this file.
1#include "GazeController.h"
2
3#include <algorithm>
4#include <cstddef>
5#include <map>
6#include <memory>
7#include <string>
8#include <vector>
9
10#include <Ice/Current.h>
11#include <IceUtil/Time.h>
12
13#include <range/v3/range/conversion.hpp>
14#include <range/v3/view/zip.hpp>
15
16#include <VirtualRobot/IK/GazeIK.h>
17#include <VirtualRobot/Nodes/RobotNode.h>
18#include <VirtualRobot/Nodes/RobotNodePrismatic.h>
19#include <VirtualRobot/Robot.h>
20#include <VirtualRobot/RobotNodeSet.h>
21#include <VirtualRobot/VirtualRobot.h>
22
28#include <ArmarXCore/interface/core/ManagedIceObjectDefinitions.h>
29#include <ArmarXCore/interface/observers/ObserverInterface.h>
30#include <ArmarXCore/interface/observers/VariantBase.h>
32
36#include <RobotAPI/components/units/RobotUnit/RobotUnit.h> // FIXME avoid thi>
38#include <RobotAPI/interface/aron/Aron.h>
39#include <RobotAPI/interface/units/RobotUnit/NJointController.h>
40#include <RobotAPI/interface/visualization/DebugDrawerInterface.h>
42
45#include <armarx/view_selection/gaze_controller/gaze_ik/aron/ControllerConfig.aron.generated.h>
50
52{
53
56
58 const NJointControllerConfigPtr& config,
60 {
61 ARMARX_RT_LOGF("Creating gaze controller");
62 // config
63 ConfigPtrT cfg = ConfigPtrT::dynamicCast(config);
65 // ARMARX_CHECK_EXPRESSION(!cfg->nodeSetName.empty());
66
67 ARMARX_CHECK_EXPRESSION(robotUnit);
68
69 const auto configData = arondto::Config::FromAron(cfg->config);
70
71 // robot
72 ARMARX_RT_LOGF("Setting up nodes");
73 {
74 _rtRobot = useSynchronizedRtRobot();
75 ARMARX_CHECK_NOT_NULL(_rtRobot);
76 _rtRobot->setThreadsafe(false);
77
78 _robotAdditionalTask = _rtRobot->clone();
79 _robotAdditionalTask->setThreadsafe(false);
80 RobotState initialState;
81 initialState.globalRobotPose = _rtRobot->getGlobalPose();
82
83 ARMARX_INFO << "Using nodeset `" << configData.params.nodeSetName << "`.";
84 _headNodeSet = _robotAdditionalTask->getRobotNodeSet(configData.params.nodeSetName);
85 ARMARX_CHECK_NOT_NULL(_headNodeSet);
86 ARMARX_INFO << VAROUT(_headNodeSet->size());
87
88 for (const auto& [name, value] : _robotAdditionalTask->getJointValues())
89 {
90 initialState.jointValues.push_back(value);
91
92 const auto node = _rtRobot->getRobotNode(name);
93 ARMARX_CHECK_NOT_NULL(node) << name;
94 _rtRobotNodes.push_back(node);
95
96 _additionalTaskRobotNodeNames.push_back(name);
97 }
98
99 ARMARX_CHECK_EQUAL(initialState.jointValues.size(), _rtRobotNodes.size());
100
101 _robotStateBuffer_rtToAdditionalTask.reinitAllBuffers(initialState);
102 Config initialConfig;
103 fromAron(configData, initialConfig);
104
105 ARMARX_INFO << VAROUT(initialConfig.target.position);
106
107 _configBuffer_updateConfigToAdditionalTask.reinitAllBuffers(initialConfig);
108 ARMARX_CHECK_NOT_NULL(_headNodeSet);
109
110 const auto tcpNode = _headNodeSet->getTCP();
111 _virtualPrismaticJoint =
112 std::dynamic_pointer_cast<VirtualRobot::RobotNodePrismatic>(tcpNode);
113 ARMARX_CHECK_NOT_NULL(_virtualPrismaticJoint);
114
115 ARMARX_INFO << "Setting up GazeIK.";
116 _gazeIK = std::make_unique<VirtualRobot::GazeIK>(_headNodeSet, _virtualPrismaticJoint);
117 _gazeIK->enableJointLimitAvoidance(configData.params.enableJointLimitAvoidance);
118 _gazeIK->setup(configData.params.maxPositionError,
119 configData.params.maxLoops,
120 configData.params.maxGradientDecentSteps);
121 _gazeIK->setVerbose(false);
122
123 // ARMARX_CHECK_NOT_NULL(_rtTorsoNode);
124 // joint positions to be controlled
125
126 ARMARX_INFO << "Requesting control targets";
127 std::vector<float> controlTargets;
128
129 for (const auto& nodeName : _headNodeSet->getNodeNames())
130 {
131 if (nodeName == _virtualPrismaticJoint->getName())
132 {
133 continue;
134 }
135
136 ARMARX_INFO << "Using control target `" << nodeName << "`.";
137 auto& target = _rtCtrlTargets.emplace_back(
139 nodeName, ControlModes::Position1DoF));
140 ARMARX_CHECK_NOT_NULL(target);
141 target->position = 0;
142
143 const auto node = _headNodeSet->getNode(nodeName);
144 ARMARX_CHECK_NOT_NULL(node) << nodeName;
145 // controlTargets.emplace_back(node->getJointValue());
146 controlTargets.emplace_back(0);
147 }
148 reinitTripleBuffer(controlTargets);
149 }
150
151 ARMARX_RT_LOGF("Nodes set up successfully");
152 }
153
155
156 void
157 GazeController::updateConfig(const ::armarx::aron::data::dto::DictPtr& dto,
158 const Ice::Current& /*iceCurrent*/)
159 {
160 ARMARX_INFO << "Controller::updateConfig";
161
162 auto updateConfigDto = arondto::Config::FromAron(dto);
163
164 Config config;
165 fromAron(updateConfigDto, config);
166
167 ARMARX_INFO << VAROUT(config.target.position.frame);
168
169 _statusReporter.setTarget(config.targetId, config.target.name);
170
171 _configBuffer_updateConfigToAdditionalTask.getWriteBuffer() = config;
172 _configBuffer_updateConfigToAdditionalTask.commitWrite();
173 }
174
175 void
177 {
179
180 runTask("GazeControllerAdditionalTask",
181 [&]
182 {
183 CycleUtil c(10);
184 getObjectScheduler()->waitForObjectStateMinimum(eManagedIceObjectStarted);
185 ARMARX_IMPORTANT << "Create a new thread alone PlatformTrajectory controller";
186 while (getState() == eManagedIceObjectStarted)
187 {
188 if (isControllerActive() and rtReady.load())
189 {
190 ARMARX_VERBOSE << "additional task";
191 additionalTask();
192 }
193 c.waitForCycleDuration();
194 }
195 });
196 }
197
198 void
204
205 std::string
210
211 void
212 GazeController::rtRun(const IceUtil::Time& /*sensorValuesTimestamp*/,
213 const IceUtil::Time& /*timeSinceLastIteration*/)
214 {
216
217 // update control devices
218 ARMARX_CHECK_EQUAL(_rtCtrlTargets.size(), rtGetControlStruct().size());
219 for (std::size_t i = 0; i < _rtCtrlTargets.size(); i++)
220 {
221 _rtCtrlTargets.at(i)->position = rtGetControlStruct().at(i);
222 }
223
224 // read data (for non-rt)
225 _robotStateBuffer_rtToAdditionalTask.getWriteBuffer().globalRobotPose =
226 rtGetRobot()->getGlobalPose();
227
228 ARMARX_CHECK_EQUAL(_robotStateBuffer_rtToAdditionalTask.getWriteBuffer().jointValues.size(),
229 _rtRobotNodes.size());
230 for (std::size_t i = 0; i < _rtRobotNodes.size(); i++)
231 {
232 ARMARX_CHECK_NOT_NULL(_rtRobotNodes.at(i)) << i;
233 _robotStateBuffer_rtToAdditionalTask.getWriteBuffer().jointValues.at(i) =
234 _rtRobotNodes.at(i)->getJointValue();
235 }
236
237 _robotStateBuffer_rtToAdditionalTask.commitWrite();
238 }
239
240 void
242 {
243 rtReady.store(true);
244 }
245
246 void
248 {
249 rtReady.store(false);
250 }
251
252 void
255 const DebugObserverInterfacePrx& debugObserver)
256 {
257
258 const float currentPitchAngle = _publishCurrentPitchAngle;
259 const float currentYawAngle = _publishCurrentYawAngle;
260 const float targetPitchAngle = _publishTargetPitchAngle;
261 const float targetYawAngle = _publishTargetYawAngle;
262
263 StringVariantBaseMap datafields;
264 datafields["currentPitchAngle"] = new Variant(currentPitchAngle);
265 datafields["currentYawAngle"] = new Variant(currentYawAngle);
266 datafields["targetPitchAngle"] = new Variant(targetPitchAngle);
267 datafields["targetYawAngle"] = new Variant(targetYawAngle);
268 datafields["angularError"] = new Variant(_publishAngularError.load());
269 datafields["lateralError"] = new Variant(_publishLateralError.load());
270 if (_gazeTarget_rtRunToPublishing.updateReadBuffer())
271 {
272 gaze_targets::GazeTarget target = _gazeTarget_rtRunToPublishing.getReadBuffer();
273 // this->publishTarget(target);
274 }
275 debugObserver->setDebugChannel(getInstanceName(), datafields);
276
277 _statusReporter.report(_statusTopic);
278 }
279
280 void
281 GazeController::additionalTask()
282 {
283 const auto& configBuffer =
284 _configBuffer_updateConfigToAdditionalTask.getUpToDateReadBuffer();
285 const auto& robotStateBuffer = _robotStateBuffer_rtToAdditionalTask.getUpToDateReadBuffer();
286
287 // update robot
288 _robotAdditionalTask->setGlobalPose(robotStateBuffer.globalRobotPose);
289
290 ARMARX_CHECK_EQUAL(_additionalTaskRobotNodeNames.size(),
291 robotStateBuffer.jointValues.size());
292 const std::map<std::string, float> jointPositions =
293 ranges::views::zip(_additionalTaskRobotNodeNames, robotStateBuffer.jointValues) |
294 ranges::to<std::map>();
295
296 _robotAdditionalTask->setJointValues(jointPositions);
297
298 ARMARX_CHECK_NOT_NULL(_gazeIK);
299 ARMARX_CHECK_NOT_EMPTY(configBuffer.target.position.frame);
300
301 const Eigen::Vector3f globalPosition =
302 configBuffer.target.position.toGlobalEigen(_robotAdditionalTask);
303
304 // Measured against the state just written above -- solve() below moves the model onto the
305 // IK solution, after which the model no longer says where the robot is actually looking.
306 const Eigen::Vector3f gazeOrigin = _virtualPrismaticJoint->getGlobalPosition();
307 const GazeResidual residual =
308 computeGazeResidual(_virtualPrismaticJoint->getJointTranslationDirection(),
309 globalPosition - gazeOrigin,
310 (globalPosition - gazeOrigin).norm());
311
312 ARMARX_VERBOSE << deactivateSpam(5) << "Trying to solve IK for target global position "
313 << globalPosition;
314 const bool solutionFound = _gazeIK->solve(globalPosition);
315
316 _publishAngularError = residual.angularError;
317 _publishLateralError = residual.lateralError;
318 _statusReporter.update(configBuffer.targetId, residual, solutionFound);
319
320 // store result
321 if (solutionFound)
322 {
323 for (std::size_t i = 0; i < _headNodeSet->getSize(); i++)
324 {
325 if (auto node = _headNodeSet->getNode(i); node != _headNodeSet->getTCP())
326 {
327 getWriterControlStruct().at(i) = node->getJointValue();
328 }
329
330 ARMARX_DEBUG << _headNodeSet->getNode(i)->getName() << ": "
331 << _headNodeSet->getNode(i)->getJointValue();
332 }
333 }
334 else
335 {
337 << "No IK solution found for Gaze IK to global target "
338 << globalPosition;
339 }
341
342 // store results (onPublish)
343 }
344
346 GazeController::getConfig(const ::Ice::Current&)
347 {
348 ARMARX_ERROR << "NYI";
349 return nullptr;
350 }
351} // namespace armarx::view_selection::gaze_controller::gaze_ik
#define ARMARX_RT_LOGF(...)
#define ARMARX_CHECK_NOT_EMPTY(c)
#define VAROUT(x)
constexpr T c
This util class helps with keeping a cycle time during a control cycle.
Definition CycleUtil.h:41
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
void offeringTopic(const std::string &name)
Registers a topic for retrival after initialization.
TopicProxyType getTopic(const std::string &name)
Returns a proxy of the specified topic.
ArmarXObjectSchedulerPtr getObjectScheduler() const
int getState() const
Retrieve current state of the ManagedIceObject.
bool isControllerActive(const Ice::Current &=Ice::emptyCurrent) const final override
void runTask(const std::string &taskName, Task &&task)
Executes a given task in a separate thread from the Application ThreadPool.
const VirtualRobot::RobotPtr & useSynchronizedRtRobot(bool updateCollisionModel=false)
Requests a VirtualRobot for use in rtRun *.
std::string getInstanceName(const Ice::Current &=Ice::emptyCurrent) const final override
const VirtualRobot::RobotPtr & rtGetRobot()
TODO make protected and use attorneys.
ControlTargetBase * useControlTarget(const std::string &deviceName, const std::string &controlMode)
Declares to calculate the ControlTarget for the given ControlDevice in the given ControlMode when rtR...
const T & getUpToDateReadBuffer() const
The Variant class is described here: Variants.
Definition Variant.h:224
void update(std::int64_t targetId, const GazeResidual &residual, bool targetReachable)
Called from the control side, at whatever rate it computes the residual.
std::string getClassName(const Ice::Current &=Ice::emptyCurrent) const override
::armarx::aron::data::dto::DictPtr getConfig(const ::Ice::Current &=::Ice::emptyCurrent) override
GazeController(RobotUnitPtr robotUnit, const NJointControllerConfigPtr &config, const VirtualRobot::RobotPtr &robot)
void onPublish(const SensorAndControl &, const DebugDrawerInterfacePrx &, const DebugObserverInterfacePrx &) override
void rtPostDeactivateController() override
This function is called after the controller is deactivated.
void rtRun(const IceUtil::Time &sensorValuesTimestamp, const IceUtil::Time &timeSinceLastIteration) override
TODO make protected and use attorneys.
void updateConfig(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.
control::ConfigurableNJointControllerConfigPtr ConfigPtrT
Business Object (BO) class of GazeTarget.
Definition GazeTarget.h:22
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#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:179
#define ARMARX_IMPORTANT
The logging level for always important information, but expected behaviour (in contrast to ARMARX_WAR...
Definition Logging.h:188
#define ARMARX_ERROR
The logging level for unexpected behaviour, that must be fixed.
Definition Logging.h:194
#define ARMARX_DEBUG
The logging level for output that is only interesting while debugging.
Definition Logging.h:182
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:185
std::shared_ptr< class Robot > RobotPtr
Definition Bus.h:19
::IceInternal::Handle< Dict > DictPtr
const simox::meta::EnumNames< ControllerType > ControllerTypeNames
const std::string GazeControllerStatusTopicName
Topic the low-level gaze controllers report their residual on.
Definition constants.h:10
const armarx::NJointControllerRegistration< GazeController > RegistrationControllerGazeController(common::ControllerTypeNames.to_name(common::ControllerType::GazeControllerGazeIK))
void fromAron(const arondto::Config &dto, Config &bo)
GazeResidual computeGazeResidual(const Eigen::Vector3f &currentGazeDirection, const Eigen::Vector3f &targetDirection, const float targetDistance)
The gaze residual, in a form that means the same thing for every controller variant.
::IceInternal::ProxyHandle<::IceProxy::armarx::DebugObserverInterface > DebugObserverInterfacePrx
std::map< std::string, VariantBasePtr > StringVariantBaseMap
IceUtil::Handle< class RobotUnit > RobotUnitPtr
Definition FTSensor.h:34
::IceInternal::ProxyHandle<::IceProxy::armarx::DebugDrawerInterface > DebugDrawerInterfacePrx
detail::ControlThreadOutputBufferEntry SensorAndControl
How far the gaze currently is from a target.
float lateralError
Lateral miss distance at target range [mm].
float angularError
Angle between the gaze ray and the direction to the target [rad].
armarx::view_selection::gaze_controller::Target target
std::int64_t targetId
Identifies this control target; echoed back in the status reports.