ros_scanner_node.h
Go to the documentation of this file.
1 // Copyright (c) 2020-2022 Pilz GmbH & Co. KG
2 //
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU Lesser General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public License
14 // along with this program. If not, see <https://www.gnu.org/licenses/>.
15 
16 #ifndef PSEN_SCAN_V2_ROS_SCANNER_NODE_H
17 #define PSEN_SCAN_V2_ROS_SCANNER_NODE_H
18 
19 #include <stdexcept>
20 #include <string>
21 #include <atomic>
22 #include <chrono>
23 #include <future>
24 #include <algorithm>
25 
26 #include <fmt/format.h>
27 
28 #include <gtest/gtest_prod.h>
29 
30 #include <ros/ros.h>
31 #include <std_msgs/UInt8.h>
32 #include <sensor_msgs/LaserScan.h>
33 
35 
40 
44 namespace psen_scan_v2
45 {
46 using namespace psen_scan_v2_standalone;
47 using namespace std::chrono_literals;
48 
53 template <typename S = ScannerV2>
55 {
56 public:
67  const std::string& topic,
68  const std::string& tf_prefix,
69  const double& x_axis_rotation,
70  const ScannerConfiguration& scanner_config);
71 
76  void run();
78  void terminate();
79 
80 private:
81  void laserScanCallback(const LaserScan& scan);
82  void publishChangedIOStates(const std::vector<psen_scan_v2_standalone::IOState>& io_states);
83 
84 private:
89  std::string tf_prefix_;
92  std::atomic_bool terminate_{ false };
93 
95 
96  friend class RosScannerNodeTests;
97  FRIEND_TEST(RosScannerNodeTests, shouldStartAndStopSuccessfullyIfScannerRespondsToRequests);
98  FRIEND_TEST(RosScannerNodeTests, shouldPublishScansWhenLaserScanCallbackIsInvoked);
99  FRIEND_TEST(RosScannerNodeTests, shouldPublishActiveZonesetWhenLaserScanCallbackIsInvoked);
100  FRIEND_TEST(RosScannerNodeTests, shouldWaitWhenStopRequestResponseIsMissing);
101  FRIEND_TEST(RosScannerNodeTests, shouldProvideScanTopic);
102  FRIEND_TEST(RosScannerNodeTests, shouldProvideActiveZonesetTopic);
103  FRIEND_TEST(RosScannerNodeTests, shouldPublishScanEqualToConversionOfSuppliedLaserScan);
104  FRIEND_TEST(RosScannerNodeTests, shouldThrowExceptionSetInScannerStartFuture);
105  FRIEND_TEST(RosScannerNodeTests, shouldThrowExceptionSetInScannerStopFuture);
106  FRIEND_TEST(RosScannerNodeTests, shouldPublishChangedIOStatesEqualToConversionOfSuppliedStandaloneIOStates);
107  FRIEND_TEST(RosScannerNodeTests, shouldPublishLatchedOnIOStatesTopic);
108  FRIEND_TEST(RosScannerNodeTests, shouldLogChangedIOStates);
109 };
110 
112 
113 template <typename S>
115  const std::string& topic,
116  const std::string& tf_prefix,
117  const double& x_axis_rotation,
118  const ScannerConfiguration& scanner_config)
119  : nh_(nh)
120  , tf_prefix_(tf_prefix)
121  , x_axis_rotation_(x_axis_rotation)
122  , scanner_(scanner_config, std::bind(&ROSScannerNodeT<S>::laserScanCallback, this, std::placeholders::_1))
123 {
124  pub_scan_ = nh_.advertise<sensor_msgs::LaserScan>(topic, 1);
125  pub_zone_ = nh_.advertise<std_msgs::UInt8>("active_zoneset", 1);
126  pub_io_ = nh_.advertise<psen_scan_v2::IOState>("io_state", 6, true /* latched */);
127 }
128 
129 template <typename S>
131 {
132  try
133  {
134  const auto laser_scan_msg = toLaserScanMsg(scan, tf_prefix_, x_axis_rotation_);
136  "ScannerNode",
137  "Publishing laser scan with angle_min={:.1f} angle_max={:.1f} angle_increment={:.1f} degrees. {} angle values.",
138  data_conversion_layer::radianToDegree(laser_scan_msg.angle_min),
139  data_conversion_layer::radianToDegree(laser_scan_msg.angle_max),
140  data_conversion_layer::radianToDegree(laser_scan_msg.angle_increment),
141  laser_scan_msg.ranges.size());
142  pub_scan_.publish(laser_scan_msg);
143 
144  std_msgs::UInt8 active_zoneset;
145  active_zoneset.data = scan.activeZoneset();
146  pub_zone_.publish(active_zoneset);
147 
149  }
150  // LCOV_EXCL_START
151  catch (const std::invalid_argument& e)
152  {
153  ROS_ERROR_STREAM(e.what());
154  }
155  // LCOV_EXCL_STOP
156 }
157 
158 template <typename S>
159 void ROSScannerNodeT<S>::publishChangedIOStates(const std::vector<psen_scan_v2_standalone::IOState>& io_states)
160 {
161  for (const auto& io : io_states)
162  {
163  if (last_io_state_ != io)
164  {
166 
167  PSENSCAN_INFO("RosScannerNode",
168  "IOs changed, new input: {}, new output: {}",
169  formatPinStates(io.changedInputStates(last_io_state_)),
170  formatPinStates(io.changedOutputStates(last_io_state_)));
171  last_io_state_ = io;
172  }
173  }
174 }
175 
176 template <typename S>
178 {
179  terminate_ = true;
180 }
181 
182 template <typename S>
184 {
185  ros::Rate r(10);
186  auto start_future = scanner_.start();
187  const auto start_status = start_future.wait_for(3s);
188  if (start_status == std::future_status::ready)
189  {
190  start_future.get(); // Throws std::runtime_error if start not successful
191  }
192 
193  while (ros::ok() && !terminate_)
194  {
195  r.sleep(); // LCOV_EXCL_LINE can not be reached deterministically
196  }
197  auto stop_future = scanner_.stop();
198 
199  const auto stop_status = stop_future.wait_for(3s);
200  if (stop_status == std::future_status::ready)
201  {
202  stop_future.get(); // Throws std::runtime_error if stop not successful
203  }
204 }
205 
206 } // namespace psen_scan_v2
207 
208 #endif // PSEN_SCAN_V2_ROS_SCANNER_NODE_H
void run(class_loader::ClassLoader *loader)
void laserScanCallback(const LaserScan &scan)
Definition: main.cpp:36
ROS Node that continuously publishes scan data of a single PSENscan laser scanner.
#define PSENSCAN_INFO_ONCE(name,...)
Definition: logging.h:85
XmlRpcServer s
Represents the set of all I/Os of the scanner and their states.
Definition: io_state.h:76
Contains the events needed to define and implement the scanner protocol.
void laserScanCallback(const LaserScan &scan)
std::string formatPinStates(const std::vector< PinState > &pins)
Formats a vector of PinStates.
Definition: io_state.h:59
psen_scan_v2::IOState toIOStateMsg(const psen_scan_v2_standalone::IOState &io_state, const std::string &frame_id)
ROSScannerNodeT ROSScannerNode
Higher level data type storing the configuration details of the scanner like scanner IP...
void publish(const boost::shared_ptr< M > &message) const
void terminate()
Terminates the fetching and publishing of scanner data.
#define PSENSCAN_INFO(name,...)
Definition: logging.h:61
sensor_msgs::LaserScan toLaserScanMsg(const LaserScan &laserscan, const std::string &frame_id, const double x_axis_rotation)
void run()
Continuously fetches data from the scanner and publishes the data into the ROS network.
ROSScannerNodeT(ros::NodeHandle &nh, const std::string &topic, const std::string &tf_prefix, const double &x_axis_rotation, const ScannerConfiguration &scanner_config)
Constructor.
Root namespace in which the software components to communicate with the scanner (firmware-version: 2)...
Definition: udp_client.h:41
ROSCPP_DECL bool ok()
Root namespace for the ROS part.
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
bool sleep()
const IOData & ioStates() const
Definition: laserscan.cpp:190
#define ROS_ERROR_STREAM(args)
static constexpr double radianToDegree(const double &angle_in_rad)
r
void publishChangedIOStates(const std::vector< psen_scan_v2_standalone::IOState > &io_states)
This class represents a single laser scan in the <tf_prefix> target frame.
Definition: laserscan.h:46
psen_scan_v2_standalone::IOState last_io_state_


psen_scan_v2
Author(s): Pilz GmbH + Co. KG
autogenerated on Sat Nov 5 2022 02:13:36