sick_scan_parse_util.cpp
Go to the documentation of this file.
1 /*
2 * Copyright (C) 2017, Ing.-Buero Dr. Michael Lehning, Hildesheim
3 * Copyright (C) 2017, SICK AG, Waldkirch
4 * All rights reserved.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 *
19 * All rights reserved.
20 *
21 * Redistribution and use in source and binary forms, with or without
22 * modification, are permitted provided that the following conditions are met:
23 *
24 * * Redistributions of source code must retain the above copyright
25 * notice, this list of conditions and the following disclaimer.
26 * * Redistributions in binary form must reproduce the above copyright
27 * notice, this list of conditions and the following disclaimer in the
28 * documentation and/or other materials provided with the distribution.
29 * * Neither the name of Osnabrueck University nor the names of its
30 * contributors may be used to endorse or promote products derived from
31 * this software without specific prior written permission.
32 * * Neither the name of SICK AG nor the names of its
33 * contributors may be used to endorse or promote products derived from
34 * this software without specific prior written permission
35 * * Neither the name of Ing.-Buero Dr. Michael Lehning nor the names of its
36 * contributors may be used to endorse or promote products derived from
37 * this software without specific prior written permission
38 *
39 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
40 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
41 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
42 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
43 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
44 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
45 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
46 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
47 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
48 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
49 * POSSIBILITY OF SUCH DAMAGE.
50  *
51  * Created on: 24.05.2012
52  *
53  * Authors:
54  * Michael Lehning <michael.lehning@lehning.de>
55  *
56  * Utility functions for parsing scanner specific sopas requests and responses
57  *
58  */
59 #include <iomanip>
62 
63 #if 0 // debugging and unittest
64 class SickScanParseUtilUnittest
65 {
66 public:
67  SickScanParseUtilUnittest()
68  {
69  runUnittest();
70  }
71  void runUnittest()
72  {
73  std::string sopas_reply = "sRA LMPscancfg \\x00\\x00\\x03\\x20\\x00\\x01\\x00\\x00\\x09\\xc4\\x00\\x00\\x00\\x00\\x00\\x36\\xee\\x80\\x00\\x00\\x09\\xc4\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x09\\xc4\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x09\\xc4\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00";
75  if (!sick_scan_xd::SickScanParseUtil::SopasToLMPscancfg(sopas_reply, scancfg))
76  std::cerr << "## ERROR SickScanParseUtil::SopasToLMPscancfg failed: " << scancfg.print() << std::endl;
77  else
78  std::cout << "SickScanParseUtil::SopasToLMPscancfg success: " << scancfg.print() << std::endl;
79  std::string sopas_cmd;
81  std::cerr << "## ERROR SickScanParseUtil::LMPscancfgToSopas failed: \"" << sopas_cmd << "\"" << std::endl;
82  else
83  std::cout << "SickScanParseUtil::LMPscancfgToSopas success: \"" << sopas_cmd << "\"" << std::endl;
84  }
85 };
86 static SickScanParseUtilUnittest selftester = SickScanParseUtilUnittest();
87 #endif // debugging and unittest
88 
89 // returns the given angle in rad normalized to -PI ... +PI
90 double sick_scan_xd::normalizeAngleRad(double angle_rad, double angle_min, double angle_max)
91 {
92 while(angle_rad > angle_max)
93  angle_rad -= (2 * M_PI);
94 while(angle_rad < angle_min)
95  angle_rad += (2 * M_PI);
96 return angle_rad;
97 }
98 
99 // Converts a string to a 6D pose x,y,z,roll,pitch,yaw in [m] resp. [rad]
100 std::vector<float> sick_scan_xd::parsePose(const std::string& pose_xyz_rpy_str)
101 {
102  std::istringstream config_stream(pose_xyz_rpy_str);
103  std::string config_arg;
104  std::vector<float> config_values;
105  while (getline(config_stream, config_arg, ','))
106  {
107  // ROS-2 interpretes parameter values configured by "ros2 param set <node> <arg> <value>" as yaml content,
108  // but does not remove yaml escape characters required for negative numbers. Any '\\' is removed here.
109  std::string::size_type n = 0;
110  while ((n = config_arg.find('\\', n)) != std::string::npos)
111  config_arg.replace( n, 1, "");
112  try
113  {
114  float arg_value = std::stof(config_arg);
115  config_values.push_back(arg_value);
116  }
117  catch(const std::exception& e)
118  {
119  ROS_ERROR_STREAM("## ERROR sick_scan_xd::parsePose(): parse error in string \"" << pose_xyz_rpy_str << "\", arg=\"" << config_arg << "\", exception " << e.what());
120  }
121  }
122  return config_values;
123 }
124 
125 template<typename T> static bool convertBin(const std::string& sopas_string, size_t& offset, T& value)
126 {
127  value = 0;
128  if (sopas_string.size() >= offset + 4 * sizeof(value))
129  {
130  for (int byte_cnt = 0; byte_cnt < sizeof(value); byte_cnt++)
131  {
132  std::string hex_str = sopas_string.substr(offset + 4 * byte_cnt + 2, 2);
133  unsigned long cur_byte = std::stoul(hex_str, nullptr, 16);
134  value = (value << 8) | (cur_byte & 0xFF);
135  }
136  offset += 4 * sizeof(value);
137  return true;
138  }
139  else
140  {
141  ROS_WARN_STREAM("## ERROR in SickScanParseUtil::convertBin(\"" << sopas_string << "\", offset=" << offset << ", sizeof(value)=" << sizeof(value) << "): value not readable, reached end of string");
142  }
143  return false;
144 }
145 
146 template<typename T> static std::string convertHex(T value)
147 {
148  std::stringstream s;
149  for (int byte_cnt = (int)sizeof(value) - 1; byte_cnt >= 0; byte_cnt--)
150  {
151  s << "\\x" << std::setfill('0') << std::setw(2) << std::hex << ((value >> 8 * byte_cnt) & 0xFF);
152  }
153  return s.str();
154 }
155 
157 {
158  std::stringstream scancfg_msg;
159  scancfg_msg << "scan_frequency=" << scan_frequency << ", active_sector_cnt=" << active_sector_cnt;
160  for (int sector_cnt = 0; sector_cnt < sector_cfg.size(); sector_cnt++)
161  {
162  scancfg_msg << ", angular_resolution[" << sector_cnt << "]=" << sector_cfg[sector_cnt].angular_resolution
163  << ", start_angle[" << sector_cnt << "]=" << sector_cfg[sector_cnt].start_angle
164  << ", stop_angle[" << sector_cnt << "]=" << sector_cfg[sector_cnt].stop_angle;
165  }
166  return scancfg_msg.str();
167 }
168 
169 /*
170 * @brief Parse the sopas reply to "sRN LMPscancfg" and convert to LMPscancfg
171 *
172 * LRS-36x1 reply to "sRN LMPscancfg" (Cola-B example): "sRA LMPscancfg \x00\x00\x03\x20\x00\x01\x00\x00\x09\xc4\x00\x00\x00\x00\x00\x36\xee\x80\x00\x00\x09\xc4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\xc4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\xc4\x00\x00\x00\x00\x00\x00\x00\x00"
173 */
175 {
177  size_t offset = 0;
178  // Check for LMPscancfg or mLMPsetscancfg reply
179  std::vector<std::string> sopas_reply_ids = { "sRA LMPscancfg " , "sAN mLMPsetscancfg " };
180  if (strncmp(sopas_reply.data(), sopas_reply_ids[0].data(), sopas_reply_ids[0].size()) == 0)
181  {
182  offset = sopas_reply_ids[0].size();
183  }
184  else if (strncmp(sopas_reply.data(), sopas_reply_ids[1].data(), sopas_reply_ids[1].size()) == 0)
185  {
186  // Read 1 byte status code after "sAN mLMPsetscancfg "
187  offset = sopas_reply_ids[1].size();
188  uint8_t status_code = 0;
189  convertBin(sopas_reply, offset, status_code);
190  if (status_code != 0)
191  ROS_WARN_STREAM("SickScanParseUtil::SopasToLMPscancfg(): status code " << (uint32_t)(status_code) << " in reply: \"" << sopas_reply << "\" indicates an ERROR");
192  }
193  else
194  {
195  ROS_WARN_STREAM("## ERROR in SickScanParseUtil::SopasToLMPscancfg: \"" << sopas_reply << "\" not supported");
196  return false;
197  }
198  bool success = convertBin(sopas_reply, offset, scancfg.scan_frequency); // scan frequency in 1/100 Hz
199  success = success && convertBin(sopas_reply, offset, scancfg.active_sector_cnt); // number of active sectors
200  int max_sector_cnt = 4; // scancfg.active_sector_cnt // always 4 sectors are transmitted by sopas
201  scancfg.sector_cfg.reserve(max_sector_cnt);
202  for (int sector_cnt = 0; success == true && sector_cnt < max_sector_cnt && offset < sopas_reply.length(); sector_cnt++)
203  {
205  success = success && convertBin(sopas_reply, offset, scancfg_sector.angular_resolution); // angular resolution in 1/10000 deg
206  success = success && convertBin(sopas_reply, offset, scancfg_sector.start_angle); // start angle in 1/10000 deg
207  success = success && convertBin(sopas_reply, offset, scancfg_sector.stop_angle); // stop angle in 1/10000 deg
208  scancfg.sector_cfg.push_back(scancfg_sector);
209  }
210  if (!success)
211  {
212  ROS_WARN_STREAM("## ERROR in LMPscancfg reply: \"" << sopas_reply << "\"");
213  ROS_WARN_STREAM("## SickScanParseUtil::SopasToLMPscancfg(): convertBin() failed with " << scancfg.print());
215  }
216  else
217  {
218  ROS_INFO_STREAM("LMPscancfg reply: \"" << sopas_reply << "\"");
219  ROS_INFO_STREAM("LMPscancfg: { " << scancfg.print() << " }");
220  }
221  return success;
222 }
223 
224 /*
225 * @brief Convert LMPscancfg to sopas request "sMN mLMPsetscancfg ..."
226 * Example: "\x02sMN mLMPsetscancfg +2000 +1 +7500 +3600000 0 +2500 0 0 +2500 0 0 +2500 0 0\x03"
227 
228 */
230 {
231  sopas_cmd = "";
232  std::stringstream sopas_hex;
233  sopas_hex << "\x02sMN mLMPsetscancfg ";
234  sopas_hex << convertHex(scancfg.scan_frequency);
235  sopas_hex << convertHex(scancfg.active_sector_cnt);
236  for (int sector_cnt = 0; sector_cnt < scancfg.sector_cfg.size(); sector_cnt++)
237  {
238  sopas_hex << convertHex(scancfg.sector_cfg[sector_cnt].angular_resolution);
239  sopas_hex << convertHex(scancfg.sector_cfg[sector_cnt].start_angle);
240  sopas_hex << convertHex(scancfg.sector_cfg[sector_cnt].stop_angle);
241  }
242  sopas_hex << "\x03";
243  sopas_cmd = sopas_hex.str();
244  return true;
245 }
sick_scan_xd::SickScanParseUtil::LMPscancfg::sector_cfg
std::vector< LMPscancfgSector > sector_cfg
Definition: sick_scan_parse_util.h:98
convertHex
static std::string convertHex(T value)
Definition: sick_scan_parse_util.cpp:146
s
XmlRpcServer s
convertBin
static bool convertBin(const std::string &sopas_string, size_t &offset, T &value)
Definition: sick_scan_parse_util.cpp:125
sick_scan_xd::SickScanParseUtil::LMPscancfgSector::stop_angle
int32_t stop_angle
Definition: sick_scan_parse_util.h:90
sick_scan_common.h
sick_scan_xd::SickScanParseUtil::LMPscancfg::active_sector_cnt
int16_t active_sector_cnt
Definition: sick_scan_parse_util.h:97
sick_scan_xd::SickScanParseUtil::LMPscancfgSector::angular_resolution
uint32_t angular_resolution
Definition: sick_scan_parse_util.h:88
sick_scan_xd::SickScanParseUtil::LMPscancfgSector::start_angle
int32_t start_angle
Definition: sick_scan_parse_util.h:89
sick_scan_xd::SickScanParseUtil::LMPscancfg::scan_frequency
uint32_t scan_frequency
Definition: sick_scan_parse_util.h:96
ROS_WARN_STREAM
#define ROS_WARN_STREAM(args)
Definition: sick_scan_logging.h:123
ROS_INFO_STREAM
#define ROS_INFO_STREAM(...)
Definition: sick_scan_ros2_example.cpp:71
sick_scan_xd::SickScanParseUtil::SopasToLMPscancfg
static bool SopasToLMPscancfg(const std::string &sopas_reply, LMPscancfg &scancfg)
Parse the sopas reply to "sRN LMPscancfg" and convert to LMPscancfg.
Definition: sick_scan_parse_util.cpp:174
sick_scan_xd::SickScanParseUtil::LMPscancfgToSopas
static bool LMPscancfgToSopas(const LMPscancfg &scancfg, std::string &sopas_cmd)
Convert LMPscancfg to sopas request "sMN mLMPsetscancfg ...".
Definition: sick_scan_parse_util.cpp:229
sick_scan_parse_util.h
sick_scan_xd::parsePose
std::vector< float > parsePose(const std::string &pose_xyz_rpy_str)
Definition: sick_scan_parse_util.cpp:100
sick_scan_xd::SickScanParseUtil::LMPscancfg
Definition: sick_scan_parse_util.h:93
sick_scan_xd::SickScanParseUtil::LMPscancfgSector
Definition: sick_scan_parse_util.h:85
ROS_ERROR_STREAM
#define ROS_ERROR_STREAM(...)
Definition: sick_scan_ros2_example.cpp:72
sick_scan_xd::SickScanParseUtil::LMPscancfg::print
std::string print() const
Definition: sick_scan_parse_util.cpp:156
sick_scan_xd::normalizeAngleRad
double normalizeAngleRad(double angle_rad, double angle_min, double angle_max)
Definition: sick_scan_parse_util.cpp:90


sick_scan_xd
Author(s): Michael Lehning , Jochen Sprickerhof , Martin Günther
autogenerated on Fri Oct 25 2024 02:47:11