ZeroMQController.cpp
Go to the documentation of this file.
1#include "ZeroMQController.h"
2
3#include <Eigen/Dense>
4
7#include <ArmarXCore/interface/observers/ObserverInterface.h>
8#include <ArmarXCore/interface/observers/VariantBase.h>
10
11// for ARMARX_RT_LOGF_WARN
12#include <chrono>
13#include <cstring>
14#include <mutex>
15#include <string>
16
17#include <Ice/Current.h>
18#include <Ice/Object.h>
19
20#include <VirtualRobot/VirtualRobot.h>
21
25#include <RobotAPI/interface/units/RobotUnit/NJointController.h>
26#include <RobotAPI/interface/visualization/DebugDrawerInterface.h>
28
33
34#include <nlohmann/json.hpp>
35#include <zmq.h>
36#include <zmq.hpp>
37
39{
40 namespace base = armarx::control::njoint_controller::task_space;
41
42 armarx::NJointControllerRegistration<
47
53
59
65
67 base::NJointTSMixImpVelColController>>
71
72 // Implement getClassName() method for templated NJointZeroMQTaskspaceController
73 template <>
74 std::string
81
82 template <>
83 std::string
90
91 template <>
92 std::string
99
100 template <>
101 std::string
103 class base::NJointTSImpColController>::
104 getClassName(const Ice::Current&) const
105 {
108 }
109
110 template <>
111 std::string
113 class base::NJointTSMixImpVelColController>::
114 getClassName(const Ice::Current&) const
115 {
118 }
119
120 template <typename NJointTaskspaceController>
123 const armarx::NJointControllerConfigPtr& config,
124 const VirtualRobot::RobotPtr& robot) :
125 NJointTaskspaceController{robotUnit, config, robot},
126 context(1),
127 socket(context, ZMQ_PULL),
128 publishSocket_(context, ZMQ_PUSH)
129 {
130 init();
131 }
132
133 // template <>
134 // NJointZeroMQTaskspaceController<
135 // class base::NJointTaskspaceCollisionAvoidanceImpedanceController>::
136 // NJointZeroMQTaskspaceController(const armarx::RobotUnitPtr& robotUnit,
137 // const armarx::NJointControllerConfigPtr& config,
138 // const VirtualRobot::RobotPtr& robot) :
139 // base::NJointTaskspaceImpedanceController{robotUnit, config, robot},
140 // base::NJointTaskspaceCollisionAvoidanceImpedanceController{robotUnit, config, robot},
141 // context(1),
142 // socket(context, ZMQ_PULL),
143 // publishSocket_(context, ZMQ_PUSH)
144 // {
145 // init();
146 // }
147
148 // template <>
149 // NJointZeroMQTaskspaceController<
150 // class base::NJointTaskspaceCollisionAvoidanceMixedImpedanceVelocityController>::
151 // NJointZeroMQTaskspaceController(const armarx::RobotUnitPtr& robotUnit,
152 // const armarx::NJointControllerConfigPtr& config,
153 // const VirtualRobot::RobotPtr& robot) :
154 // base::NJointTaskspaceMixedImpedanceVelocityController{robotUnit, config, robot},
155 // base::NJointTaskspaceCollisionAvoidanceMixedImpedanceVelocityController{robotUnit,
156 // config,
157 // robot},
158 // context(1),
159 // socket(context, ZMQ_PULL),
160 // publishSocket_(context, ZMQ_PUSH)
161 // {
162 // init();
163 // }
164
165 // Method(s) of NJointZeroMQTaskspaceController in general
166 template <typename NJointTaskspaceController>
167 void
169 {
170 // Bind to ZeroMQ socket
171 int port = 5556; // TODO(@rietsch): Make configurable
172 std::string ip_address = "*";
173 std::string bind_address = "tcp://" + ip_address + ":" + std::to_string(port);
174 try
175 {
176 socket.bind(bind_address);
177 ARMARX_INFO << "Controller bound to " << bind_address << " through ZeroMQ";
178 }
179 catch (const zmq::error_t& e)
180 {
181 ARMARX_INFO << "ZeroMQ bind error: " << e.what();
182 return;
183 }
184
185 time_last_packet = std::chrono::steady_clock::now();
186 }
187
188 template <typename NJointTaskspaceController>
189 void
190 NJointZeroMQTaskspaceController<NJointTaskspaceController>::setPublishIPAddress(
191 const std::string& ipAddress)
192 {
193 ARMARX_INFO << "Trying to set publisher socket";
194 int publishPort = 5557; // TODO(@rietsch): Make configurable
195 std::string pub_bind_address = "tcp://" + ipAddress + ":" + std::to_string(publishPort);
196 try
197 {
198 std::unique_lock<std::mutex> lock(publishSocketMutex_);
199 // publishSocket_.bind(pub_bind_address);
200 publishSocket_.connect(pub_bind_address);
201 publishSocketBound_.store(true);
202 publishSocket_.set(zmq::sockopt::linger, 0); // Important for socket to be non-blocking on deletion
203 ARMARX_INFO << "Controller publish socket bound to " << pub_bind_address
204 << " through ZeroMQ";
205 }
206 catch (const zmq::error_t& e)
207 {
208 ARMARX_WARNING << "ZeroMQ publish bind error: " << e.what();
209 }
210 }
211
212 template <typename NJointTaskspaceController>
213 void
215 {
216 zmq::message_t config_msg;
217 auto received = socket.recv(config_msg, zmq::recv_flags::dontwait); // Try receive message, don't wait
218 if (received)
219 {
220 // Measure time passed since last packet was received
221 auto currentTime = std::chrono::steady_clock::now();
222 std::chrono::duration<double> elapsed_seconds = currentTime - time_last_packet;
223 frequency_ms = elapsed_seconds.count() * 1000;
224 time_last_packet = currentTime;
225
226 // Parse config message into nlohmann::json
227 std::string config_json_str(static_cast<char*>(config_msg.data()), config_msg.size());
228 nlohmann::json config_json = nlohmann::json::parse(config_json_str);
229
230 // Calculate latency (might be imprecise due to clocks being not being synchronized?)
231 if (config_json.contains("timestamp") && config_json["timestamp"].is_number())
232 {
233 auto arrival_time = std::chrono::duration<double>(
234 std::chrono::system_clock::now().time_since_epoch())
235 .count();
236 double sender_time = config_json["timestamp"];
237 latency_ms = (arrival_time - sender_time) * 1000; // Convert to milliseconds
238 config_json.erase(
239 "timestamp"); // Important! Remove any data that is not part of the config
240 }
241
242 // If the sender send "publish_ip", the controller will send out robot data to that address
243 if (config_json.contains("publish_ip") && !publishSocketBound_.load())
244 {
245 setPublishIPAddress(config_json["publish_ip"].get<std::string>());
246 config_json.erase("publish_ip");
247 }
248
249 // Read into config data structure
251 typename NJointTaskspaceController::ConfigDict receivedConfig;
252 receivedConfig.read(reader, config_json);
253 auto prevCfg = this->userConfig;
254 for (auto& pair : this->limb)
255 {
256 auto limbName = pair.first;
257 auto& limb = pair.second;
258
259 auto limbConfig = prevCfg.limbs.at(limbName);
260 auto limbConfigNew = receivedConfig.limbs.at(limbName);
261 limbConfig.desiredPose = limbConfigNew.desiredPose;
262 limb->bufferConfigUserToNonRt.getWriteBuffer() =
263 limbConfig; // Overwrite user config
264 limb->bufferConfigUserToNonRt.commitWrite();
265 }
266
267 // Update hands
268 if (this->hands)
269 {
270 this->hands->updateConfig(receivedConfig.hands);
271 }
272 }
273
274 auto [rtTargetSafe, rtSafe] = this->additionalTaskUpdateStatus();
276 if (not rtTargetSafe)
277 {
279 }
280 }
281
282 template <typename NJointTaskspaceController>
283 void
285 const SensorAndControl&,
287 const DebugObserverInterfacePrx& debugObs)
288 {
289 // Log metrics
290 StringVariantBaseMap datafields;
291 datafields["frequency_ms"] = new Variant(frequency_ms);
292 datafields["latency_ms"] = new Variant(latency_ms);
293 debugObs->setDebugChannel(getClassName(), datafields);
294
295 // Do limb publish
296 for (auto& pair : this->limb)
297 {
298 if (not pair.second->rtReady.load())
299 {
300 continue;
301 }
302 this->limbPublish(pair.second, debugObs);
303 }
304
305 // If there's no publisher socket, skip rest
306 if (!publishSocketBound_.load())
307 {
308 return;
309 }
310
311 nlohmann::json stateJson;
312 stateJson["timestamp"] =
313 std::chrono::duration<double>(std::chrono::system_clock::now().time_since_epoch())
314 .count();
315
316 auto mat4ToJson = [](const Eigen::Matrix4f& m)
317 {
318 nlohmann::json j = nlohmann::json::array();
319 for (int r = 0; r < 4; ++r)
320 for (int c = 0; c < 4; ++c)
321 j.push_back(m(r, c));
322 return j;
323 };
324
325 // Extract limb data
326 for (auto& pair : this->limb)
327 {
328 if (not pair.second->rtReady.load())
329 continue;
330 auto rtData = pair.second->bufferConfigRtToOnPublish.getUpToDateReadBuffer();
331 auto rtStatus = pair.second->bufferRtStatusToOnPublish.getUpToDateReadBuffer();
332 const auto& nodeSetName = pair.second->kinematicChainName;
333 const auto& jointNames = pair.second->jointNames;
334 auto jointPosition = rtStatus.jointPosition;
335 auto jointVelocity = rtStatus.jointVelocity;
336 auto jointTorque = rtStatus.jointTorque;
337 auto desiredJointTorque = rtStatus.desiredJointTorque;
338 auto desiredJointVelocity = rtStatus.desiredJointVelocity;
339 auto desiredJointPosition = rtStatus.desiredJointPosition;
340 Eigen::Matrix4f currentPose = rtStatus.currentPose;
341 Eigen::Matrix4f desiredPose = rtStatus.desiredPose;
342
343 for (size_t i = 0; i < jointNames.size(); ++i)
344 {
345 stateJson[nodeSetName]["jointPosition"][jointNames[i]] = jointPosition(i);
346 stateJson[nodeSetName]["jointVelocity"][jointNames[i]] = jointVelocity(i);
347 stateJson[nodeSetName]["jointTorque"][jointNames[i]] = jointTorque(i);
348 stateJson[nodeSetName]["desiredJointTorque"][jointNames[i]] = desiredJointTorque(i);
349 stateJson[nodeSetName]["desiredJointVelocity"][jointNames[i]] = desiredJointVelocity(i);
350 stateJson[nodeSetName]["desiredJointPosition"][jointNames[i]] = desiredJointPosition(i);
351 }
352 stateJson[nodeSetName]["currentPose"] = mat4ToJson(currentPose);
353 stateJson[nodeSetName]["desiredPose"] = mat4ToJson(desiredPose);
354 }
355
356 // Extract hand data
357 if (this->hands)
358 {
359 for (auto& [handName, hand] : this->hands->hands)
360 {
361 const auto& jointNames = hand->jointNames;
362 const auto desiredConfig =
363 hand->bufferConfigNonRTToOnPublish.getUpToDateReadBuffer();
364 const auto currentJointPositionMap = hand->ctrl->getJointValuesMap();
365
366 for (size_t i = 0; i < jointNames.size(); ++i)
367 {
368 const auto it = currentJointPositionMap.find(jointNames[i]);
369 stateJson[handName]["jointPosition"][jointNames[i]] =
370 (it != currentJointPositionMap.end()) ? it->second : 0.F;
371 if (desiredConfig.jointPosition.has_value())
372 {
373 stateJson[handName]["desiredJointPosition"][jointNames[i]] =
374 desiredConfig.jointPosition.value()(i);
375 }
376 }
377 }
378 }
379
380 nlohmann::json metadata_json;
381 metadata_json["name"] = "robot_state";
382 metadata_json["type"] = "json";
383 metadata_json["timestamp_processed"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
384 std::string metadata_string = metadata_json.dump();
385
386 // Send data via ZMQ
387 const std::string stateStr = stateJson.dump();
388 zmq::message_t msg(stateStr.size());
389 std::memcpy(msg.data(), stateStr.data(), stateStr.size());
390 zmq::message_t metadata_msg(metadata_string.size());
391 memcpy(metadata_msg.data(), metadata_string.data(), metadata_string.size());
392 try
393 {
394 std::unique_lock<std::mutex> lock(publishSocketMutex_);
395 publishSocket_.send(metadata_msg, zmq::send_flags::sndmore);
396 publishSocket_.send(msg, zmq::send_flags::dontwait);
397 }
398 catch (const zmq::error_t& e)
399 {
400 ARMARX_RT_LOGF_WARN("ZeroMQ joint state publish error: %s", e.what());
401 }
402 }
403
404 template <typename NJointTaskspaceController>
406 {
407 if (publishSocketBound_.load())
408 {
409 std::unique_lock<std::mutex> lock(publishSocketMutex_);
410 publishSocket_.close();
411 publishSocketBound_.store(false);
412 }
413 }
414
415} // namespace armarx::control::njoint_controller::task_space
#define ARMARX_RT_LOGF_WARN(...)
constexpr T c
The Variant class is described here: Variants.
Definition Variant.h:224
NJointTaskspaceController(const RobotUnitPtr &robotUnit, const NJointControllerConfigPtr &config, const VirtualRobot::RobotPtr &)
Definition Base.cpp:138
typename NJointTaskspaceControllerType::ConfigDict ConfigDict
Definition Base.h:64
void limbPublish(ArmPtr &arm, const DebugObserverInterfacePrx &debugObs)
Definition Base.cpp:804
void onPublish(const SensorAndControl &, const DebugDrawerInterfacePrx &, const DebugObserverInterfacePrx &) override
NJointZeroMQTaskspaceController(const armarx::RobotUnitPtr &robotUnit, const armarx::NJointControllerConfigPtr &config, const VirtualRobot::RobotPtr &robot)
std::string getClassName(const Ice::Current &=Ice::emptyCurrent) const final
void onPublishDeactivation(const DebugDrawerInterfacePrx &, const DebugObserverInterfacePrx &) override
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
std::shared_ptr< class Robot > RobotPtr
Definition Bus.h:19
const simox::meta::EnumNames< ControllerType > ControllerTypeNames
Definition type.h:170
armarx::NJointControllerRegistration< NJointSharedMemoryTaskspaceController< base::NJointTSMixImpVelColController > > registrationControllerNJointCollisionAvoidanceTSMixedImpedanceVelocityController(armarx::control::common::ControllerTypeNames.to_name(armarx::control::common::ControllerType::SharedMemoryTSMixImpVelCol))
armarx::NJointControllerRegistration< NJointSharedMemoryTaskspaceController< base::NJointTSMixImpVelController > > registrationControllerNJointTSMixedImpedanceVelocityController(armarx::control::common::ControllerTypeNames.to_name(armarx::control::common::ControllerType::SharedMemoryTSMixImpVel))
armarx::NJointControllerRegistration< NJointSharedMemoryTaskspaceController< base::NJointTSImpController > > registrationControllerNJointTSImpedanceController(armarx::control::common::ControllerTypeNames.to_name(armarx::control::common::ControllerType::SharedMemoryTSImp))
armarx::NJointControllerRegistration< NJointSharedMemoryTaskspaceController< base::NJointTSVelController > > registrationControllerNJointTSVelocityController(armarx::control::common::ControllerTypeNames.to_name(armarx::control::common::ControllerType::SharedMemoryTSVel))
armarx::NJointControllerRegistration< NJointSharedMemoryTaskspaceController< base::NJointTSImpColController > > registrationControllerNJointCollisionAvoidanceTSImpedanceController(armarx::control::common::ControllerTypeNames.to_name(armarx::control::common::ControllerType::SharedMemoryTSImpCol))
::IceInternal::ProxyHandle<::IceProxy::armarx::DebugObserverInterface > DebugObserverInterfacePrx
std::string Variant::get< std::string >() const
Definition Variant.cpp:284
std::map< std::string, VariantBasePtr > StringVariantBaseMap
IceUtil::Handle< class RobotUnit > RobotUnitPtr
Definition FTSensor.h:34
::IceInternal::ProxyHandle<::IceProxy::armarx::DebugDrawerInterface > DebugDrawerInterfacePrx
detail::ControlThreadOutputBufferEntry SensorAndControl