rc_hand_eye_calibration_client.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2018-2020 Roboception GmbH
3  *
4  * Author: Felix Endres
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution.
15  *
16  * 3. Neither the name of the copyright holder nor the names of its contributors
17  * may be used to endorse or promote products derived from this software without
18  * specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
34 
35 #include "json_conversions.h"
36 
37 #include <ifaddrs.h>
38 #include <arpa/inet.h>
39 #include <unistd.h>
40 
42 
44 {
46  : nh_(nh), rest_helper_(host, "rc_hand_eye_calibration", 2000)
47 {
50  initTimers();
52 }
53 
54 template <typename Request, typename Response>
55 bool HandEyeCalibClient::callService(const std::string& name, const Request& req, Response& res)
56 {
57  try
58  {
59  json j_req = req;
60  const auto j_res = rest_helper_.servicePutRequest(name, j_req);
61  res = j_res;
62  return true;
63  }
64  catch (const std::exception& ex)
65  {
66  ROS_ERROR("%s", ex.what());
67  return false;
68  }
69 }
70 
72 {
73  if (calib_publish_period_ > 0.0 && // only need the timer at all if there's a valid intervall
74  // every calibration request broadcasts: no need for extra publishing if request is done more often
76  {
77  ROS_INFO("Broadcasting calibration every %.3f seconds on /tf", calib_publish_period_);
78  // the two booleans at the end: *one-shot* is kept default (false), *autostart* is set to false,
79  // because we start publishing only when the first calibration result was received
81  &HandEyeCalibClient::sendCachedCalibration, this, false, false);
82  }
83 
84  if (calib_request_period_ >= 0.0) // negative is documented to turn all auto-requesting off
85  {
86  if (calib_request_period_ == 0.0) // special meaning as documented in README.md
87  {
88  ROS_INFO("Requesting (and broadcasting) calibration from rc_visard once");
89  }
90  else
91  {
92  ROS_INFO("Requesting (and broadcasting) calibration every %.3f seconds from rc_visard", calib_request_period_);
95  }
96  // request once immediately at startup
98  }
99 }
100 
101 bool HandEyeCalibClient::saveSrv(TriggerRequest& req, TriggerResponse& res)
102 {
103  callService("save_calibration", req, res);
104  return true;
105 }
106 
107 bool HandEyeCalibClient::resetSrv(TriggerRequest& req, TriggerResponse& res)
108 {
109  callService("reset_calibration", req, res);
110  return true;
111 }
112 
113 bool HandEyeCalibClient::removeSrv(TriggerRequest& req, TriggerResponse& res)
114 {
115  callService("remove_calibration", req, res);
116 
117  if (res.success)
118  {
119  calib_publish_timer_.stop(); // does nothing if already stopped
120  ROS_INFO("Calibration has been removed, stopped /tf broadcasting.");
121  }
122  else
123  {
124  ROS_WARN("Failed to remove calibration: %s", res.message.c_str());
125  }
126 
127  return true;
128 }
129 
130 bool HandEyeCalibClient::setSlotSrv(SetCalibrationPoseRequest& req, SetCalibrationPoseResponse& res)
131 {
132  callService("set_pose", req, res);
133  return true;
134 }
135 
136 void HandEyeCalibClient::calibResponseHandler(CalibrationResponse& response)
137 {
138  if (response.success)
139  {
140  ROS_INFO("Calibration request successful. Broadcasting new calibration.");
141  updateCalibrationCache(response);
143  if (calib_publish_timer_.isValid()) // don't start it if it is invalid
144  {
145  calib_publish_timer_.start(); // does nothing if already started.
146  }
147  }
148  else
149  {
150  ROS_WARN_STREAM("Could not get calibration: " << response.message);
151  }
152 }
153 
154 bool HandEyeCalibClient::calibSrv(CalibrationRequest& req, CalibrationResponse& res)
155 {
156  callService("calibrate", req, res);
158  return true;
159 }
160 
161 bool HandEyeCalibClient::getCalibResultSrv(CalibrationRequest& req, CalibrationResponse& res)
162 {
163  callService("get_calibration", req, res);
165  return true;
166 }
167 
168 bool HandEyeCalibClient::setCalibSrv(SetCalibrationRequest& req, SetCalibrationResponse& res)
169 {
170  if (image_version_ >= std::make_tuple(20ul, 4ul, 0ul))
171  {
172  callService("set_calibration", req, res);
173  }
174  else
175  {
176  res.message = "set_calibration is not supported in this firmware version";
177  res.success = false;
178  ROS_WARN_STREAM(res.message);
179  }
180  return true;
181 }
182 
184 {
185  CalibrationRequest req;
186  CalibrationResponse res;
187  getCalibResultSrv(req, res);
188 }
189 
190 void HandEyeCalibClient::updateCalibrationCache(const CalibrationResponse& response)
191 {
192  // Select frame_id based on the type of calibration hand-eye (on-robot-cam) or base-eye (external cam)
193  current_calibration_.header.frame_id = response.robot_mounted ? endeff_frame_id_ : base_frame_id_;
194  current_calibration_.child_frame_id = camera_frame_id_;
195  current_calibration_.transform.translation.x = response.pose.position.x;
196  current_calibration_.transform.translation.y = response.pose.position.y;
197  current_calibration_.transform.translation.z = response.pose.position.z;
198  current_calibration_.transform.rotation = response.pose.orientation;
199 }
200 
202 {
203  if (calib_publish_period_ <= 0.0) // if there's no period use static tf
204  {
205  // Timestamp doesn't (or at least shouldn't) matter for static transforms
206  // Time::now makes it easy to see when it was updated though
207  current_calibration_.header.stamp = ros::Time::now();
209  }
210  else // periodic sending
211  {
212  // Pre-date, so the transformation can be directly used by clients until the next one is sent.
213  // I.e., don't cause lag when looking up transformations because one has to wait for
214  // the current calibration.
217  }
218 }
219 
221 {
222  using CW = HandEyeCalibClient;
223  // Save pose and image/grid pair for later calibration
224  srv_set_slot_ = nh_.advertiseService("set_pose", &CW::setSlotSrv, this);
225  // Save calibration to disk
226  srv_save_ = nh_.advertiseService("save_calibration", &CW::saveSrv, this);
227  // Compute and return calibration
228  srv_calibrate_ = nh_.advertiseService("calibrate", &CW::calibSrv, this);
229  // Get result (but don't compute)
230  srv_get_result_ = nh_.advertiseService("get_calibration", &CW::getCalibResultSrv, this);
231  // Delete all slots
232  srv_reset_ = nh_.advertiseService("reset_calibration", &CW::resetSrv, this);
233  // remove calibration
234  srv_remove_ = nh_.advertiseService("remove_calibration", &CW::removeSrv, this);
235  srv_set_calibration_ = nh_.advertiseService("set_calibration", &CW::setCalibSrv, this);
236 }
237 
238 void paramsToCfg(const json& params, hand_eye_calibrationConfig& cfg)
239 {
240  for (const auto& param : params)
241  {
242  const auto& name = param["name"];
243  if (name == "grid_width")
244  cfg.grid_width = param["value"];
245  else if (name == "grid_height")
246  cfg.grid_height = param["value"];
247  else if (name == "robot_mounted")
248  cfg.robot_mounted = (bool)param["value"];
249  else if (name == "tcp_rotation_axis")
250  cfg.tcp_rotation_axis = (int)param["value"];
251  else if (name == "tcp_offset")
252  cfg.tcp_offset = param["value"];
253  }
254 }
255 
257 {
258  hand_eye_calibrationConfig cfg;
259 
260  // first get the current values from sensor
261  const auto j_params = rest_helper_.getParameters();
262 
263  paramsToCfg(j_params, cfg);
264 
265  // second, try to get ROS parameters:
266  // if parameter is not set in parameter server, we default to current sensor configuration
267  nh_.param("grid_width", cfg.grid_width, cfg.grid_width);
268  nh_.param("grid_height", cfg.grid_height, cfg.grid_height);
269  nh_.param("robot_mounted", cfg.robot_mounted, cfg.robot_mounted);
270  nh_.param("tcp_rotation_axis", cfg.tcp_rotation_axis, cfg.tcp_rotation_axis);
271  nh_.param("tcp_offset", cfg.tcp_offset, cfg.tcp_offset);
272 
273  // see if those parameters are available otherwise use default (set in class header)
274  nh_.param("rc_visard_frame_id", camera_frame_id_, camera_frame_id_);
275  nh_.param("end_effector_frame_id", endeff_frame_id_, endeff_frame_id_);
276  nh_.param("base_frame_id", base_frame_id_, base_frame_id_);
277  nh_.param("calibration_publication_period", calib_publish_period_, calib_publish_period_);
278  nh_.param("calibration_request_period", calib_request_period_, calib_request_period_);
279 
280  // set parameters on parameter server so that dynamic reconfigure picks them up
281  nh_.setParam("grid_width", cfg.grid_width);
282  nh_.setParam("grid_height", cfg.grid_height);
283  nh_.setParam("robot_mounted", cfg.robot_mounted);
284  nh_.setParam("tcp_rotation_axis", cfg.tcp_rotation_axis);
285  nh_.setParam("tcp_offset", cfg.tcp_offset);
286 
287  // instantiate dynamic reconfigure server that will initially read those values
288  using RCFSRV = dynamic_reconfigure::Server<hand_eye_calibrationConfig>;
289  server_ = std::unique_ptr<RCFSRV>(new RCFSRV(nh_));
290  server_->setCallback(boost::bind(&HandEyeCalibClient::dynamicReconfigureCb, this, _1, _2));
291 }
292 
293 void HandEyeCalibClient::dynamicReconfigureCb(hand_eye_calibrationConfig& config, uint32_t)
294 {
295  ROS_DEBUG("Reconfigure Request: (%f x %f) %s, 4DOF: %d, offset: %f", config.grid_width, config.grid_height,
296  config.robot_mounted ? "True" : "False", config.tcp_rotation_axis, config.tcp_offset);
297 
298  // fill json request from dynamic reconfigure request
299  json j_params, j_param;
300  j_param["name"] = "grid_width";
301  j_param["value"] = config.grid_width;
302  j_params.push_back(j_param);
303  j_param["name"] = "grid_height";
304  j_param["value"] = config.grid_height;
305  j_params.push_back(j_param);
306  j_param["name"] = "robot_mounted";
307  j_param["value"] = config.robot_mounted;
308  j_params.push_back(j_param);
309  j_param["name"] = "tcp_rotation_axis";
310  j_param["value"] = config.tcp_rotation_axis;
311  j_params.push_back(j_param);
312  j_param["name"] = "tcp_offset";
313  j_param["value"] = config.tcp_offset;
314  j_params.push_back(j_param);
315 
316  json j_params_new = rest_helper_.setParameters(j_params);
317  // set config with new params so they are updated if needed
318  paramsToCfg(j_params_new, config);
319 }
320 
321 } // namespace rc_hand_eye_calibration_client
bool param(const std::string &param_name, T &param_val, const T &default_val)
bool calibSrv(rc_hand_eye_calibration_client::CalibrationRequest &req, rc_hand_eye_calibration_client::CalibrationResponse &res)
Perform the calibration and return its result.
bool removeSrv(rc_hand_eye_calibration_client::TriggerRequest &req, rc_hand_eye_calibration_client::TriggerResponse &res)
Remove calibration so sensor reports as uncalibrated.
bool getCalibResultSrv(rc_hand_eye_calibration_client::CalibrationRequest &req, rc_hand_eye_calibration_client::CalibrationResponse &res)
Service call to get the result. Also returned directly via calibrate.
void advertiseServices()
Advertises the services in the namespace of nh_.
bool setCalibSrv(rc_hand_eye_calibration_client::SetCalibrationRequest &req, rc_hand_eye_calibration_client::SetCalibrationResponse &res)
Service call to set the calibration.
void sendCachedCalibration(const ros::SteadyTimerEvent &=ros::SteadyTimerEvent())
void updateCalibrationCache(const rc_hand_eye_calibration_client::CalibrationResponse &res)
Copy resp into current_calibration_.
ServiceServer advertiseService(const std::string &service, bool(T::*srv_func)(MReq &, MRes &), T *obj)
std::string camera_frame_id_
Default Frame Ids to be used for the tf broadcast.
#define ROS_WARN(...)
void calibResponseHandler(CalibrationResponse &res)
update and broadcast the internally cached calibration result.
std::unique_ptr< dynamic_reconfigure::Server< rc_hand_eye_calibration_client::hand_eye_calibrationConfig > > server_
std::tuple< size_t, size_t, size_t > getImageVersion()
SteadyTimer createSteadyTimer(WallDuration period, void(T::*callback)(const SteadyTimerEvent &), T *obj, bool oneshot=false, bool autostart=true) const
tf2_ros::TransformBroadcaster dynamic_tf2_broadcaster_
To be used for periodic sending.
#define ROS_INFO(...)
bool param(const std::string &param_name, T &param_val, const T &default_val) const
tf2_ros::StaticTransformBroadcaster static_tf2_broadcaster_
To be used for on-change sending only.
std::string endeff_frame_id_
Which one is used depends on the value of "robot_mounted" in the calibration response.
#define ROS_WARN_STREAM(args)
void sendTransform(const geometry_msgs::TransformStamped &transform)
void initTimers()
Initialize timers to periodically publish and/or request the calibration Depends on parameters: calib...
void requestCalibration(const ros::SteadyTimerEvent &)
Wraps the above getCalibResultSrv.
bool callService(const std::string &name, const Request &req, Response &res)
json setParameters(const json &js_params)
double calib_publish_period_
Whether to send the transformation periodically instead of as a static transformation Zero or negativ...
static Time now()
nlohmann::json json
bool setSlotSrv(rc_hand_eye_calibration_client::SetCalibrationPoseRequest &req, rc_hand_eye_calibration_client::SetCalibrationPoseResponse &res)
Save given pose and the pose of the grid indexed by the given request.slot.
void paramsToCfg(const json &params, hand_eye_calibrationConfig &cfg)
void sendTransform(const geometry_msgs::TransformStamped &transform)
#define ROS_ERROR(...)
void setParam(const std::string &key, const XmlRpc::XmlRpcValue &v) const
bool saveSrv(rc_hand_eye_calibration_client::TriggerRequest &req, rc_hand_eye_calibration_client::TriggerResponse &res)
store hand eye calibration on sensor
double calib_request_period_
If non-negative: Periodically update the calibration by requesting it.
void dynamicReconfigureCb(rc_hand_eye_calibration_client::hand_eye_calibrationConfig &config, uint32_t)
bool resetSrv(rc_hand_eye_calibration_client::TriggerRequest &req, rc_hand_eye_calibration_client::TriggerResponse &res)
Delete all stored data, e.g., to start over.
#define ROS_DEBUG(...)
json servicePutRequest(const std::string &service_name)
Definition: rest_helper.cpp:92


rc_hand_eye_calibration_client
Author(s): Christian Emmerich
autogenerated on Sat Feb 13 2021 03:41:55