gyroscope_ros_i.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2019, Open Source Robotics Foundation
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  * * Neither the name of the copyright holder nor the names of its
14  * contributors may be used to endorse or promote products derived from
15  * this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <functional>
31 #include <memory>
32 #include <mutex>
33 
34 #include <ros/ros.h>
35 #include <sensor_msgs/Imu.h>
36 #include <std_msgs/Bool.h>
37 #include <std_srvs/Empty.h>
38 
40 
41 namespace phidgets {
42 
44  : nh_(nh), nh_private_(nh_private)
45 {
46  ROS_INFO("Starting Phidgets Gyroscope");
47 
48  int serial_num;
49  if (!nh_private_.getParam("serial", serial_num))
50  {
51  serial_num = -1; // default open any device
52  }
53  int hub_port;
54  if (!nh_private.getParam("hub_port", hub_port))
55  {
56  hub_port = 0; // only used if the device is on a VINT hub_port
57  }
58  if (!nh_private_.getParam("frame_id", frame_id_))
59  {
60  // As specified in http://www.ros.org/reps/rep-0145.html
61  frame_id_ = "imu_link";
62  }
63 
64  double angular_velocity_stdev;
65  if (!nh_private_.getParam("angular_velocity_stdev", angular_velocity_stdev))
66  {
67  // 0.095 deg/s gyroscope white noise sigma, as per manual
68  angular_velocity_stdev = 0.095 * (M_PI / 180.0);
69  }
71  angular_velocity_stdev * angular_velocity_stdev;
72 
73  int time_resync_ms;
74  if (!nh_private_.getParam("time_resynchronization_interval_ms",
75  time_resync_ms))
76  {
77  time_resync_ms = 5000;
78  }
80  static_cast<int64_t>(time_resync_ms) * 1000 * 1000;
81 
82  int data_interval_ms;
83  if (!nh_private.getParam("data_interval_ms", data_interval_ms))
84  {
85  data_interval_ms = 8;
86  }
87  data_interval_ns_ = data_interval_ms * 1000 * 1000;
88 
89  int cb_delta_epsilon_ms;
90  if (!nh_private.getParam("callback_delta_epsilon_ms", cb_delta_epsilon_ms))
91  {
92  cb_delta_epsilon_ms = 1;
93  }
94  cb_delta_epsilon_ns_ = cb_delta_epsilon_ms * 1000 * 1000;
95 
96  if (cb_delta_epsilon_ms >= data_interval_ms)
97  {
98  throw std::runtime_error(
99  "Callback epsilon is larger than the data interval; this can never "
100  "work");
101  }
102 
103  if (!nh_private.getParam("publish_rate", publish_rate_))
104  {
105  publish_rate_ = 0;
106  }
107 
108  if (nh_private.getParam("server_name", server_name_) &&
109  nh_private.getParam("server_ip", server_ip_))
110  {
111  PhidgetNet_addServer(server_name_.c_str(), server_ip_.c_str(), 5661, "",
112  0);
113 
114  ROS_INFO("Using phidget server %s at IP %s", server_name_.c_str(),
115  server_ip_.c_str());
116  }
117 
118  ROS_INFO("Connecting to Phidgets Gyroscope serial %d, hub port %d ...",
119  serial_num, hub_port);
120 
121  // We take the mutex here and don't unlock until the end of the constructor
122  // to prevent a callback from trying to use the publisher before we are
123  // finished setting up.
124  std::lock_guard<std::mutex> lock(gyro_mutex_);
125 
126  try
127  {
128  gyroscope_ = std::make_unique<Gyroscope>(
129  serial_num, hub_port, false,
131  std::placeholders::_1, std::placeholders::_2));
132 
133  ROS_INFO("Connected");
134 
135  gyroscope_->setDataInterval(data_interval_ms);
136 
137  cal_publisher_ = nh_.advertise<std_msgs::Bool>("imu/is_calibrated", 5,
138  true /* latched */);
139 
140  calibrate();
141 
142  gyroscope_pub_ = nh_.advertise<sensor_msgs::Imu>("imu/data_raw", 1);
143 
144  cal_srv_ = nh_.advertiseService("imu/calibrate",
146 
147  } catch (const Phidget22Error &err)
148  {
149  ROS_ERROR("Gyroscope: %s", err.what());
150  throw;
151  }
152 
153  if (publish_rate_ > 0)
154  {
157  }
158 }
159 
161 {
162  ROS_INFO(
163  "Calibrating Gyro, this takes around 2 seconds to finish. "
164  "Make sure that the device is not moved during this time.");
165  gyroscope_->zero();
166  // The API call returns directly, so we "enforce" the recommended 2 sec
167  // here. See: https://github.com/ros-drivers/phidgets_drivers/issues/40
168  ros::Duration(2.).sleep();
169  ROS_INFO("Calibrating Gyro done.");
170 
171  // publish message
172  std_msgs::Bool is_calibrated_msg;
173  is_calibrated_msg.data = true;
174  cal_publisher_.publish(is_calibrated_msg);
175 }
176 
177 bool GyroscopeRosI::calibrateService(std_srvs::Empty::Request &req,
178  std_srvs::Empty::Response &res)
179 {
180  (void)req;
181  (void)res;
182  calibrate();
183  return true;
184 }
185 
187 {
188  std::shared_ptr<sensor_msgs::Imu> msg =
189  std::make_shared<sensor_msgs::Imu>();
190 
191  msg->header.frame_id = frame_id_;
192 
193  for (int i = 0; i < 3; ++i)
194  {
195  for (int j = 0; j < 3; ++j)
196  {
197  if (i == j)
198  {
199  int idx = j * 3 + i;
200  msg->angular_velocity_covariance[idx] =
202  }
203  }
204  }
205 
206  uint64_t gyro_diff_in_ns = last_data_timestamp_ns_ - data_time_zero_ns_;
207  uint64_t time_in_ns = ros_time_zero_.toNSec() + gyro_diff_in_ns;
208 
209  if (time_in_ns < last_ros_stamp_ns_)
210  {
211  ROS_WARN("Time went backwards (%lu < %lu)! Not publishing message.",
212  time_in_ns, last_ros_stamp_ns_);
213  return;
214  }
215 
216  last_ros_stamp_ns_ = time_in_ns;
217 
218  msg->header.stamp = ros::Time().fromNSec(time_in_ns);
219 
220  // set angular velocities
221  msg->angular_velocity.x = last_gyro_x_;
222  msg->angular_velocity.y = last_gyro_y_;
223  msg->angular_velocity.z = last_gyro_z_;
224 
225  gyroscope_pub_.publish(*msg);
226 }
227 
229 {
230  std::lock_guard<std::mutex> lock(gyro_mutex_);
231  if (can_publish_)
232  {
233  publishLatest();
234  }
235 }
236 
237 void GyroscopeRosI::gyroscopeChangeCallback(const double angular_rate[3],
238  double timestamp)
239 {
240  // When publishing the message on the ROS network, we want to publish the
241  // time that the data was acquired in seconds since the Unix epoch. The
242  // data we have to work with is the time that the callback happened (on the
243  // local processor, in Unix epoch seconds), and the timestamp that the
244  // IMU gives us on the callback (from the processor on the IMU, in
245  // milliseconds since some arbitrary starting point).
246  //
247  // At a first approximation, we can apply the timestamp from the device to
248  // Unix epoch seconds by taking a common starting point on the IMU and the
249  // local processor, then applying the delta between this IMU timestamp and
250  // the "zero" IMU timestamp to the local processor starting point.
251  //
252  // There are several complications with the simple scheme above. The first
253  // is finding a proper "zero" point where the IMU timestamp and the local
254  // timestamp line up. Due to potential delays in servicing this process,
255  // along with USB delays, the delta timestamp from the IMU and the time when
256  // this callback gets called can be wildly different. Since we want the
257  // initial zero for both the IMU and the local time to be in the same time
258  // "window", we throw away data at the beginning until we see that the delta
259  // callback and delta timestamp are within reasonable bounds of each other.
260  //
261  // The second complication is that the time on the IMU drifts away from the
262  // time on the local processor. Taking the "zero" time once at the
263  // beginning isn't sufficient, and we have to periodically re-synchronize
264  // the times given the constraints above. Because we still have the
265  // arbitrary delays present as described above, it can take us several
266  // callbacks to successfully synchronize. We continue publishing data using
267  // the old "zero" time until successfully resynchronize, at which point we
268  // switch to the new zero point.
269 
270  std::lock_guard<std::mutex> lock(gyro_mutex_);
271 
272  ros::Time now = ros::Time::now();
273 
274  // At the beginning of time, need to initialize last_cb_time for later use;
275  // last_cb_time is used to figure out the time between callbacks
276  if (last_cb_time_.sec == 0 && last_cb_time_.nsec == 0)
277  {
278  last_cb_time_ = now;
279  return;
280  }
281 
282  ros::Duration time_since_last_cb = now - last_cb_time_;
283  uint64_t this_ts_ns = static_cast<uint64_t>(timestamp * 1000.0 * 1000.0);
284 
286  {
287  // The only time it's safe to sync time between IMU and ROS Node is when
288  // the data that came in is within the data interval that data is
289  // expected. It's possible for data to come late because of USB issues
290  // or swapping, etc and we don't want to sync with data that was
291  // actually published before this time interval, so we wait until we get
292  // data that is within the data interval +/- an epsilon since we will
293  // have taken some time to process and/or a short delay (maybe USB
294  // comms) may have happened
295  if (time_since_last_cb.toNSec() >=
297  time_since_last_cb.toNSec() <=
299  {
300  ros_time_zero_ = now;
301  data_time_zero_ns_ = this_ts_ns;
302  synchronize_timestamps_ = false;
303  can_publish_ = true;
304  } else
305  {
306  ROS_DEBUG(
307  "Data not within acceptable window for synchronization: "
308  "expected between %ld and %ld, saw %ld",
311  time_since_last_cb.toNSec());
312  }
313  }
314 
315  if (can_publish_) // Cannot publish data until IMU/ROS timestamps have been
316  // synchronized at least once
317  {
318  // Save off the values
319  last_gyro_x_ = angular_rate[0] * (M_PI / 180.0);
320  last_gyro_y_ = angular_rate[1] * (M_PI / 180.0);
321  last_gyro_z_ = angular_rate[2] * (M_PI / 180.0);
322 
323  last_data_timestamp_ns_ = this_ts_ns;
324 
325  // Publish if we aren't publishing on a timer
326  if (publish_rate_ <= 0)
327  {
328  publishLatest();
329  }
330  }
331 
332  // Determine if we need to resynchronize - time between IMU and ROS Node can
333  // drift, periodically resync to deal with this issue
334  ros::Duration diff = now - ros_time_zero_;
335  if (time_resync_interval_ns_ > 0 &&
337  {
339  }
340 
341  last_cb_time_ = now;
342 }
343 
344 } // namespace phidgets
phidgets
phidgets::GyroscopeRosI::cb_delta_epsilon_ns_
int64_t cb_delta_epsilon_ns_
Definition: gyroscope_ros_i.h:78
phidgets::GyroscopeRosI::calibrate
void calibrate()
Definition: gyroscope_ros_i.cpp:160
phidgets::GyroscopeRosI::calibrateService
bool calibrateService(std_srvs::Empty::Request &req, std_srvs::Empty::Response &res)
Definition: gyroscope_ros_i.cpp:177
phidgets::GyroscopeRosI::last_data_timestamp_ns_
uint64_t last_data_timestamp_ns_
Definition: gyroscope_ros_i.h:72
phidgets::GyroscopeRosI::publishLatest
void publishLatest()
Definition: gyroscope_ros_i.cpp:186
phidgets::GyroscopeRosI::last_gyro_x_
double last_gyro_x_
Definition: gyroscope_ros_i.h:54
ros::NodeHandle::getParam
bool getParam(const std::string &key, bool &b) const
ros.h
phidgets::GyroscopeRosI::gyroscope_
std::unique_ptr< Gyroscope > gyroscope_
Definition: gyroscope_ros_i.h:50
phidgets::GyroscopeRosI::GyroscopeRosI
GyroscopeRosI(ros::NodeHandle nh, ros::NodeHandle nh_private)
Definition: gyroscope_ros_i.cpp:43
phidgets::GyroscopeRosI::time_resync_interval_ns_
int64_t time_resync_interval_ns_
Definition: gyroscope_ros_i.h:74
phidgets::GyroscopeRosI::data_time_zero_ns_
uint64_t data_time_zero_ns_
Definition: gyroscope_ros_i.h:71
ros::NodeHandle::advertiseService
ServiceServer advertiseService(AdvertiseServiceOptions &ops)
phidgets::GyroscopeRosI::can_publish_
bool can_publish_
Definition: gyroscope_ros_i.h:76
phidgets::GyroscopeRosI::server_name_
std::string server_name_
Definition: gyroscope_ros_i.h:66
phidgets::GyroscopeRosI::last_cb_time_
ros::Time last_cb_time_
Definition: gyroscope_ros_i.h:77
phidgets::GyroscopeRosI::gyro_mutex_
std::mutex gyro_mutex_
Definition: gyroscope_ros_i.h:53
phidgets::GyroscopeRosI::cal_srv_
ros::ServiceServer cal_srv_
Definition: gyroscope_ros_i.h:61
ros::Publisher::publish
void publish(const boost::shared_ptr< M > &message) const
ros::NodeHandle::advertise
Publisher advertise(AdvertiseOptions &ops)
phidgets::GyroscopeRosI::last_gyro_z_
double last_gyro_z_
Definition: gyroscope_ros_i.h:56
phidgets::GyroscopeRosI::last_ros_stamp_ns_
uint64_t last_ros_stamp_ns_
Definition: gyroscope_ros_i.h:73
phidgets::GyroscopeRosI::synchronize_timestamps_
bool synchronize_timestamps_
Definition: gyroscope_ros_i.h:70
phidgets::GyroscopeRosI::data_interval_ns_
int64_t data_interval_ns_
Definition: gyroscope_ros_i.h:75
phidgets::GyroscopeRosI::ros_time_zero_
ros::Time ros_time_zero_
Definition: gyroscope_ros_i.h:69
phidgets::GyroscopeRosI::angular_velocity_variance_
double angular_velocity_variance_
Definition: gyroscope_ros_i.h:52
ROS_DEBUG
#define ROS_DEBUG(...)
phidgets::GyroscopeRosI::nh_private_
ros::NodeHandle nh_private_
Definition: gyroscope_ros_i.h:59
phidgets::GyroscopeRosI::gyroscopeChangeCallback
void gyroscopeChangeCallback(const double angular_rate[3], const double timestamp)
Definition: gyroscope_ros_i.cpp:237
phidgets::Phidget22Error
ROS_WARN
#define ROS_WARN(...)
ros::TimerEvent
TimeBase< Time, Duration >::sec
uint32_t sec
phidgets::GyroscopeRosI::cal_publisher_
ros::Publisher cal_publisher_
Definition: gyroscope_ros_i.h:60
TimeBase< Time, Duration >::nsec
uint32_t nsec
phidgets::GyroscopeRosI::timerCallback
void timerCallback(const ros::TimerEvent &event)
Definition: gyroscope_ros_i.cpp:228
phidgets::Phidget22Error::what
const char * what() const noexcept
phidgets::GyroscopeRosI::publish_rate_
int publish_rate_
Definition: gyroscope_ros_i.h:65
phidgets::GyroscopeRosI::nh_
ros::NodeHandle nh_
Definition: gyroscope_ros_i.h:58
phidgets::GyroscopeRosI::timer_
ros::Timer timer_
Definition: gyroscope_ros_i.h:64
phidgets::GyroscopeRosI::server_ip_
std::string server_ip_
Definition: gyroscope_ros_i.h:67
TimeBase< Time, Duration >::fromNSec
Time & fromNSec(uint64_t t)
ros::Time
DurationBase< Duration >::toNSec
int64_t toNSec() const
ROS_ERROR
#define ROS_ERROR(...)
TimeBase< Time, Duration >::toNSec
uint64_t toNSec() const
ros::Duration::sleep
bool sleep() const
gyroscope_ros_i.h
ROS_INFO
#define ROS_INFO(...)
ros::NodeHandle::createTimer
Timer createTimer(Duration period, const TimerCallback &callback, bool oneshot=false, bool autostart=true) const
ros::Duration
phidgets::GyroscopeRosI::frame_id_
std::string frame_id_
Definition: gyroscope_ros_i.h:51
ros::NodeHandle
ros::Time::now
static Time now()
phidgets::GyroscopeRosI::last_gyro_y_
double last_gyro_y_
Definition: gyroscope_ros_i.h:55
phidgets::GyroscopeRosI::gyroscope_pub_
ros::Publisher gyroscope_pub_
Definition: gyroscope_ros_i.h:62


phidgets_gyroscope
Author(s): Chris Lalancette
autogenerated on Sun May 11 2025 02:20:32