test_server_ldmrs_msg.cpp
Go to the documentation of this file.
1 /*
2  * @brief sick_scan2 test_server_lidar_msg implements the ldmrs specific messages,
3  * i.e. message receiving and message creation to simulate ldmrs devices.
4  *
5  * Note: sick_scan2 test_server_lidar_msg does not implement the functions of lidar sensors,
6  * it just implements a simple message handling to test the sick_scan2 ros drivers.
7  *
8  * Copyright (C) 2020 Ing.-Buero Dr. Michael Lehning, Hildesheim
9  * Copyright (C) 2020 SICK AG, Waldkirch
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  *
23  * All rights reserved.
24  *
25  * Redistribution and use in source and binary forms, with or without
26  * modification, are permitted provided that the following conditions are met:
27  *
28  * * Redistributions of source code must retain the above copyright
29  * notice, this list of conditions and the following disclaimer.
30  * * Redistributions in binary form must reproduce the above copyright
31  * notice, this list of conditions and the following disclaimer in the
32  * documentation and/or other materials provided with the distribution.
33  * * Neither the name of SICK AG nor the names of its
34  * contributors may be used to endorse or promote products derived from
35  * this software without specific prior written permission
36  * * Neither the name of Ing.-Buero Dr. Michael Lehning nor the names of its
37  * contributors may be used to endorse or promote products derived from
38  * this software without specific prior written permission
39  *
40  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
41  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
42  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
43  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
44  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
45  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
46  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
47  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
48  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
49  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
50  * POSSIBILITY OF SUCH DAMAGE.
51  *
52  * Authors:
53  * Michael Lehning <michael.lehning@lehning.de>
54  *
55  * Copyright 2020 SICK AG
56  * Copyright 2020 Ing.-Buero Dr. Michael Lehning
57  *
58  */
59 
61 
62 /*
63  * Constructor
64  * @param[in] send_scan_data_rate frequency to generate and send scan data (default: 20 Hz)
65  * @param[in] scan_data_payload scan data payload (without the message header)
66  */
67 sick_scan_xd::test::TestServerLDMRSMsg::TestServerLDMRSMsg(rosNodePtr nh, double send_scan_data_rate, const std::vector<uint8_t> & scan_data_payload)
68 : m_nh(nh), m_send_scan_data_rate(send_scan_data_rate), m_send_scan_data(false), m_send_scan_data_cnt(0), m_delta_range_cm(0), m_delta_range_step(1)
69 {
70  m_scan_data_payload = scan_data_payload;
71 }
72 
73 /*
74  * @brief Creates and returns the 24 byte message header containing the magic word 0xAFFEC0C2, the data type and payload length.
75  * @param[in] data_type data type identifier, f.e. 0x2010 for a command request or 0x2020 for a command response
76  * @param[in] payload_length length of payload in byte
77  * @return 24 byte message header
78  */
79 std::vector<uint8_t> sick_scan_xd::test::TestServerLDMRSMsg::createMessageHeader(size_t data_type, size_t payload_length)
80 {
81  std::vector<uint8_t> msg_header(24);
82  std::vector<uint8_t> magic_word = { 0xaf, 0xfe, 0xc0, 0xc2 };
83  msg_header[0] = magic_word[0];
84  msg_header[1] = magic_word[1];
85  msg_header[2] = magic_word[2];
86  msg_header[3] = magic_word[3];
87  msg_header[8] = ((payload_length >> 24) & 0xFF);
88  msg_header[9] = ((payload_length >> 16) & 0xFF);
89  msg_header[10] = ((payload_length >> 8) & 0xFF);
90  msg_header[11] = ((payload_length) & 0xFF);
91  msg_header[14] = ((data_type >> 8) & 0xFF);
92  msg_header[15] = ((data_type) & 0xFF);
93  return msg_header;
94 }
95 
96 /*
97  * @brief Receives a LDMRS message if data on a tcp socket are available.
98  * Non-blocking function (i.e. it returns immediately) if no data available.
99  * If data available, this function returns after a complete message has been received, or an error occured.
100  * @param[in] tcp_client_socket socket to read from
101  * @param[out] message message received from client
102  * @param[out] is_binary always true for LDMRS
103  * @return true, if a message has been received, false otherwise
104  */
105 bool sick_scan_xd::test::TestServerLDMRSMsg::receiveMessage(sick_scan_xd::ServerSocket & tcp_client_socket, std::vector<uint8_t> & message, bool & is_binary)
106 {
107  is_binary = true;
108  message.clear();
109  assert(m_nh);
110 
111  // Note from telegram listing: "a mixture of little and big endian encoding is used. While the message
112  // header, the frame around the data payload, is encoded using big endian format, the
113  // payload itself is always encoded in little endian format.
114  // Be sure to encode/decode correctly!"
115 
116  // Receive 24 byte message header
117  uint8_t magic_word[4] = { 0xaf, 0xfe, 0xc0, 0xc2 };
118  uint8_t header[24] = {0};
119  if (!tcp_client_socket.read(sizeof(header), &header[0], false))
120  {
121  return false; // no data available or error receiving message header
122  }
123  if (memcmp(&header[0], &magic_word[0], 4) != 0)
124  {
125  ROS_ERROR_STREAM("## ERROR sick_scan_xd::test::TestServerLDMRSMsg::receiveMessage(): error receiving magic word 0xAFFEC0C2");
126  return false; // error receiving magic word
127  }
128 
129  // Decode 4 byte payload length (big endian)
130  size_t payload_length = 0;
131  for(int n = 8; n < 12; n++)
132  payload_length = ((payload_length << 8) | (header[n] & 0xFF));
133 
134  // Decode 2 byte data type (big endian)
135  size_t data_type = 0;
136  for(int n = 14; n < 16; n++)
137  data_type = ((data_type << 8) | (header[n] & 0xFF));
138 
139  // Decode 8 byte ntp time (big endian)
140  uint64_t ntp_time = 0;
141  for(int n = 16; n < 24; n++)
142  ntp_time = ((ntp_time << 8) | (header[n] & 0xFF));
143 
144  // Read payload
145  message.resize(24 + payload_length);
146  memcpy(message.data(), &header[0], sizeof(header));
147  if (!tcp_client_socket.read(payload_length, message.data() + 24))
148  {
149  ROS_ERROR_STREAM("## ERROR sick_scan_xd::test::TestServerLDMRSMsg::receiveMessage(): error receiving " << payload_length << " byte tcp payload");
150  return false; // error receiving tcp payload
151  }
152 
153  return true;
154 }
155 
156 /*
157  * @brief Generate a response to a message received from client.
158  * @param[in] message_received message received from client
159  * @param[in] is_binary true for binary messages, false for ascii messages
160  * @param[out] response response to the client
161  * @return true, if a response has been created, false otherwise (no response required or invalid message received)
162  */
163 bool sick_scan_xd::test::TestServerLDMRSMsg::createResponse(const std::vector<uint8_t> & message_received, bool is_binary, std::vector<uint8_t> & response)
164 {
165  std::vector<uint8_t> payload;
166  response.clear();
167  if(message_received.size() < 24)
168  {
169  ROS_ERROR_STREAM("## ERROR sick_scan_xd::test::TestServerLDMRSMsg::createResponse(): invalid input message");
170  return false;
171  }
172 
173  // Note from telegram listing: "a mixture of little and big endian encoding is used. While the message
174  // header, the frame around the data payload, is encoded using big endian format, the
175  // payload itself is always encoded in little endian format.
176  // Be sure to encode/decode correctly!"
177 
178  // Decode 2 byte data type (big endian)
179  size_t data_type = 0;
180  for(int n = 14; n < 16; n++)
181  data_type = ((data_type << 8) | (message_received[n] & 0xFF));
182  if(data_type != 0x2010)
183  {
184  return false; // not a command => no response
185  }
186  // Decode 2 byte command id (little endian)
187  if(message_received.size() < 26)
188  {
189  ROS_ERROR_STREAM("## ERROR sick_scan_xd::test::TestServerLDMRSMsg::createResponse(): invalid command message");
190  return false;
191  }
192  size_t command_id = (message_received[24] & 0xFF) | (message_received[25] << 8);
193  std::map<int, std::string> command_id_names = {
194  {0x0000, "\"Reset\""}, {0x0001, "\"Get Status\""}, {0x0004, "\"Save Config\""}, {0x0010, "\"Set Parameter\""}, {0x0011, "\"Get Parameter\""}, {0x001A, "\"Reset Default Parameters\""},
195  {0x0020, "\"Start Measure\""}, {0x0021, "\"Stop Measure\""}, {0x0030, "\"Set NTP Timestamp Sec\""}, {0x0031, "\"Set NTP Timestamp Frac Sec\""}
196  };
197  std::map<int, std::string> parameter_id_names = {
198  {0x1000, "\"IP address\""}, {0x1001, "\"TCP Port\""}, {0x1002, "\"Subnet Mask\""}, {0x1003, "\"Standard gateway\""}, {0x1010, "\"CAN Base ID\""}, {0x1011, "\"CAN Baud Rate\""},
199  {0x1012, "\"Data Output Flag\""}, {0x1013, "\"maxObjectsViaCAN\""}, {0x1014, "\"ContourPointDensity\""}, {0x1016, "\"CAN object data options\""}, {0x1017, "\"Minimum Object Age\""},
200  {0x1100, "\"Start angle\""}, {0x1101, "\"End angle\""}, {0x1102, "\"Scan frequency\""}, {0x1103, "\"Sync angle offset\""}, {0x1104, "\"angular resolution type\""}, {0x1105, "\"angleTicksPerRotation\""},
201  {0x1108, "\"RangeReduction\""}, {0x1109, "\"Upside down mode\""}, {0x110A, "\"Ignore near range\""}, {0x110B, "\"Sensitivity control active\""}, {0x1200, "\"SensorMounting_X\""}, {0x1201, "\"SensorMounting_Y\""},
202  {0x1202, "\"SensorMounting_Z\""}, {0x1203, "\"SensorMounting_Yaw\""}, {0x1204, "\"SensorMounting_Pitch\""}, {0x1205, "\"SensorMounting_Roll\""}, {0x1206, "\"VehicleFrontToFrontAxle\""},
203  {0x1207, "\"FrontAxleToRearAxle\""}, {0x1208, "\"RearAxleToVehicleRear\""}, {0x120A, "\"SteerRatioType\""}, {0x120C, "\"SteerRatioPoly0\""}, {0x120D, "\"SteerRatioPoly1\""}, {0x120E, "\"SteerRatioPoly2\""},
204  {0x120F, "\"SteerRatioPoly3\""}, {0x1210, "\"Vehicle Motion Data Flags\""}, {0x2208, "\"EnableSensorInfo\""}, {0x3302, "\"BeamTilt\""}, {0x3500, "\"Timemeter\""}, {0x3600, "\"Enable APD control\""},
205  {0x4000, "\"NumSectors\""}, {0x4001, "\"StartAngle, Sector 1\""}, {0x4002, "\"StartAngle, Sector 2\""}, {0x4003, "\"StartAngle, Sector 3\""}, {0x4004, "\"StartAngle, Sector 4\""}, {0x4005, "\"StartAngle, Sector 5\""},
206  {0x4006, "\"StartAngle, Sector 6\""}, {0x4007, "\"StartAngle, Sector 7\""}, {0x4008, "\"StartAngle, Sector 8\""}, {0x4009, "\"Angular resolution, Sector 1\""}, {0x400A, "\"Angular resolution, Sector 2\""},
207  {0x400B, "\"Angular resolution, Sector 3\""}, {0x400C, "\"Angular resolution, Sector 4\""}, {0x400D, "\"Angular resolution, Sector 5\""}, {0x400E, "\"Angular resolution, Sector 6\""}, {0x400F, "\"Angular resolution, Sector 7\""},
208  {0x4010, "\"Angular resolution, Sector 8\""}, {0x7000, "\"Detailed error code for FlexRes\""}
209  };
210 
211  // Command "Reset"
212  if(command_id == 0x0000)
213  {
214  return false; // Reply: For this command (Reset), no reply is sent
215  }
216  // Command "Get status"
217  else if(command_id == 0x0001)
218  {
219  payload.resize(32); // 32 byte status information
220  payload[0] = 0x01; // id status request
221  payload[1] = 0x00; // id status request
222  payload[3] = 0x12; // firmware version 1.23.4
223  payload[2] = 0x34; // firmware version 1.23.4
224  payload[5] = 0x56; // fpga version 5.67.8
225  payload[4] = 0x78; // fpga version 5.67.8
226  payload[13] = 0x01; // temperature 0x01EC = 23.88 degree celsius
227  payload[12] = 0xEC; // temperature 0x01EC = 23.88 degree celsius
228  payload[15] = 0x20; // serial number 0 (year)
229  payload[14] = 0x21; // serial number 0 (calender week)
230  payload[17] = 0x00; // serial number 1
231  payload[16] = 0x01; // serial number 1
232  payload[19] = 0x00; // serial number 2
233  payload[18] = 0x01; // serial number 2
234 
235  payload[21] = 0x20; // version timestamp YY
236  payload[20] = 0x20; // version timestamp YY
237  payload[23] = 0x05; // version timestamp MM
238  payload[22] = 0x06; // version timestamp DD
239  payload[25] = 0x07; // version timestamp hh
240  payload[24] = 0x08; // version timestamp mm
241 
242  payload[27] = 0x20; // version timestamp YY
243  payload[26] = 0x20; // version timestamp YY
244  payload[29] = 0x01; // version timestamp MM
245  payload[28] = 0x02; // version timestamp DD
246  payload[31] = 0x03; // version timestamp hh
247  payload[30] = 0x04; // version timestamp mm
248 
249  }
250  // Command "Set Parameter"
251  else if(command_id == 0x0010)
252  {
253  size_t parameter_id = (message_received.size() >= 29) ? ((message_received[28] & 0xFF) | (message_received[29] << 8)) : 0;
254  uint32_t parameter_value = (message_received.size() >= 33) ? ((message_received[30] & 0xFF) | (message_received[31] << 8) | (message_received[32] << 16) | (message_received[33] << 24)) : 0;
255  payload.resize(2); // 2 byte acknowledge with command id
256  payload[0] = 0x10; // id status request
257  ROS_INFO_STREAM("sick_scan_xd::test::TestServerLDMRSMsg::createResponse(): command id 0x10 \"Set Parameter\", parameter id 0x"
258  << std::hex << parameter_id << " " << parameter_id_names[parameter_id] << ", parameter value 0x" << std::hex << parameter_value);
259  }
260  // Command "Get Parameter", Read a single parameter from the LD-MRS
261  else if(command_id == 0x0011)
262  {
263  size_t parameter_id = (message_received.size() >= 29) ? ((message_received[28] & 0xFF) | (message_received[29] << 8)) : 0;
264  uint32_t parameter_value = (message_received.size() >= 33) ? ((message_received[30] & 0xFF) | (message_received[31] << 8) | (message_received[32] << 16) | (message_received[33] << 24)) : 0;
265  payload.resize(8); // 8 byte parameter information
266  payload[0] = 0x11; // id status request
267  payload[2] = message_received[28];
268  payload[3] = message_received[29];
269  ROS_INFO_STREAM("sick_scan_xd::test::TestServerLDMRSMsg::createResponse(): command id 0x11 \"Get Parameter\", parameter id 0x"
270  << std::hex << parameter_id << parameter_id_names[parameter_id] << ", parameter value 0x" << std::hex << parameter_value);
271  }
272  // Start Measure
273  else if(command_id == 0x0020)
274  {
275  m_send_scan_data = true;
276  m_last_scan_data = std::chrono::system_clock::now() + std::chrono::seconds(5); // start scanning in 5 seconds
277  payload.resize(2); // 2 byte acknowledge with command id
278  payload[0] = 0x20; // id status request
279  ROS_INFO_STREAM("sick_scan_xd::test::TestServerLDMRSMsg::createResponse(): command id 0x20 -> start scanning in 5 seconds");
280  }
281  // Stop Measure
282  else if(command_id == 0x0021)
283  {
284  m_send_scan_data = false;
285  payload.resize(2); // 2 byte acknowledge with command id
286  payload[0] = 0x21; // id status request
287  ROS_INFO_STREAM("sick_scan_xd::test::TestServerLDMRSMsg::createResponse(): command id 0x21 -> stop scanning");
288  }
289  // Default: a command will be acknowledged by the same command id without any command reply data
290  else
291  {
292  payload.resize(2); // 2 byte acknowledge with command id
293  payload[0] = (command_id & 0xFF); // command id (LSB)
294  payload[1] = ((command_id >> 8) & 0xFF); // command id (MSB)
295  }
296 
297  // response := 24 byte header (big endian) + payload (little endian)
298  std::vector<uint8_t> msg_header = createMessageHeader(0x2020, payload.size());
299  response.reserve(msg_header.size() + payload.size());
300  response.insert(response.end(), msg_header.begin(), msg_header.end());
301  if(!payload.empty())
302  response.insert(response.end(), payload.begin(), payload.end());
303 
304  ROS_INFO_STREAM("sick_scan_xd::test::TestServerLDMRSMsg::createResponse(): command id 0x" << std::hex << command_id << " " << command_id_names[command_id]
305  << ", " << std::dec << response.size() << " byte response");
306  return true;
307 }
308 
309 /*
310  * @brief Generate a scan data message.
311  * @param[out] scandata scan data message
312  * @return true, if a a scan data message has been created, false otherwise (f.e. if a sensor does not generate scan data)
313  */
314 bool sick_scan_xd::test::TestServerLDMRSMsg::createScandata(std::vector<uint8_t> & scandata)
315 {
316  scandata.clear();
317  if(!m_send_scan_data || std::chrono::duration<double>(std::chrono::system_clock::now() - m_last_scan_data).count() < 1/m_send_scan_data_rate) // frequency to generate and send scan data (default: 20 Hz)
318  {
319  return false; // scan data disabled
320  }
321 
322  // Simulate the scan of a moving object: Loop over all scan points in the payload
323  // and increase/decrease the range of all scan points
324  // scan_points_start := payload + 44 = Start Scan Point List
325  // scan_points_start + 2 : 2 byte horizontal angle in ticks
326  // scan_points_start + 4 : 2 byte range in cm
327  // each scan point encoded with 10 byte
328  std::vector<uint8_t> payload = m_scan_data_payload;
329  for(size_t range_offset = 48; range_offset + 10 < payload.size(); range_offset += 10)
330  {
331  size_t range_cm = (payload[range_offset] & 0xFF) | (payload[range_offset + 1] << 8);
332  range_cm += m_delta_range_cm;
333  payload[range_offset] = (range_cm & 0xFF);
334  payload[range_offset + 1] = ((range_cm >> 8) & 0xFF);
335  }
336 
337  // response := 24 byte header (big endian) + payload (little endian)
338  std::vector<uint8_t> msg_header = createMessageHeader(0x2202, payload.size());
339  scandata.reserve(msg_header.size() + payload.size());
340  scandata.insert(scandata.end(), msg_header.begin(), msg_header.end());
341  scandata.insert(scandata.end(), payload.begin(), payload.end());
342 
343  // Increase counter and range for the next scan
344  m_last_scan_data = std::chrono::system_clock::now();
345  m_send_scan_data_cnt += 1;
346  m_delta_range_cm += m_delta_range_step;
347  if(m_delta_range_cm > 500 && m_delta_range_step > 0)
348  {
349  m_delta_range_cm = 500;
350  m_delta_range_step = -m_delta_range_step;
351  }
352  if(m_delta_range_cm < 0 && m_delta_range_step < 0)
353  {
354  m_delta_range_cm = 0;
355  m_delta_range_step = -m_delta_range_step;
356  }
357  ROS_INFO_STREAM("sick_scan_xd::test::TestServerLDMRSMsg::createScandata(" << m_send_scan_data_cnt << "): " << scandata.size() << " byte scan data generated");
358  if(m_send_scan_data_cnt <= 1)
359  ROS_INFO_STREAM("sick_scan_xd::test::TestServerLDMRSMsg::createScandata(): Generating " << scandata.size() << " byte scan data with " << m_send_scan_data_rate << " Hz");
360 
361  return true;
362 }
response
const std::string response
sick_scan_xd::test::TestServerLDMRSMsg::m_scan_data_payload
std::vector< uint8_t > m_scan_data_payload
Definition: test_server_ldmrs_msg.h:125
sick_scan_xd::test::TestServerLDMRSMsg::createResponse
virtual bool createResponse(const std::vector< uint8_t > &message_received, bool is_binary, std::vector< uint8_t > &response)
Definition: test_server_ldmrs_msg.cpp:163
sick_scan_xd::test::TestServerLDMRSMsg::createMessageHeader
static std::vector< uint8_t > createMessageHeader(size_t data_type, size_t payload_length)
Definition: test_server_ldmrs_msg.cpp:79
ROS::now
ROS::Time now(void)
Definition: ros_wrapper.cpp:116
message
def message(msg, *args, **kwargs)
pcap_json_converter.payload
string payload
Definition: pcap_json_converter.py:130
sick_scan_xd::test::TestServerLDMRSMsg::receiveMessage
virtual bool receiveMessage(sick_scan_xd::ServerSocket &tcp_client_socket, std::vector< uint8_t > &message, bool &is_binary)
Definition: test_server_ldmrs_msg.cpp:105
ROS_INFO_STREAM
#define ROS_INFO_STREAM(...)
Definition: sick_scan_ros2_example.cpp:71
sick_scan_xd::test::TestServerLDMRSMsg::createScandata
virtual bool createScandata(std::vector< uint8_t > &scandata)
Definition: test_server_ldmrs_msg.cpp:314
ros::NodeHandle
sick_scan_xd::test::TestServerLDMRSMsg::TestServerLDMRSMsg
TestServerLDMRSMsg(rosNodePtr nh, double send_scan_data_rate=20.0, const std::vector< uint8_t > &scan_data_payload=std::vector< uint8_t >())
Definition: test_server_ldmrs_msg.cpp:67
ROS::seconds
double seconds(ROS::Duration duration)
Definition: ros_wrapper.cpp:180
ROS_ERROR_STREAM
#define ROS_ERROR_STREAM(...)
Definition: sick_scan_ros2_example.cpp:72
roswrap::message_traits::header
std_msgs::Header * header(M &m)
returns Header<M>::pointer(m);
Definition: message_traits.h:281
sick_scan_xd::ServerSocket::read
virtual int read(int num_bytes, std::vector< uint8_t > &out_buffer, bool read_blocking=true)
Definition: server_socket.cpp:197
test_server_ldmrs_msg.h
sick_scan_xd::ServerSocket
Definition: server_socket.h:83


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