Forked from
Florian Leander Singer / RobotAPI
2693 commits behind the upstream repository.
-
Johann Mantel authoredJohann Mantel authored
SickScanAdapter.cpp 46.99 KiB
/**
* \file
* \brief Laser Scanner communication (TCP Helper Class)
* Copyright (C) 2013, Osnabrück University
* Copyright (C) 2017, Ing.-Buero Dr. Michael Lehning, Hildesheim
* Copyright (C) 2017, SICK AG, Waldkirch
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Osnabrück University nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* * Neither the name of SICK AG nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission
* * Neither the name of Ing.-Buero Dr. Michael Lehning nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* Last modified: 12th Dec 2017
*
* Authors:
* Michael Lehning <michael.lehning@lehning.de>
* Jochen Sprickerhof <jochen@sprickerhof.de>
* Martin Günther <mguenthe@uos.de>
*
* Based on the TiM communication example by SICK AG.
*
*/
#ifdef _MSC_VER
#pragma warning(disable: 4996)
#pragma warning(disable: 4267)
#pragma warning(disable: 4101) // C4101: "e" : Unreferenzierte lokale Variable
#define _WIN32_WINNT 0x0501
#endif
#include "SickScanAdapter.h"
#include <sick_scan/tcp/colaa.hpp>
#include <sick_scan/tcp/colab.hpp>
#include <sick_scan/sick_generic_radar.h>
#include <sick_scan/helper/angle_compensator.h>
#include <sick_scan/sick_scan_config_internal.h>
#include <sick_scan/sick_generic_imu.h>
#ifdef ROSSIMU
#include <sick_scan/rosconsole_simu.hpp>
#endif
#include <sick_scan/binScanf.hpp>
#include <sick_scan/RadarScan.h>
#include <sick_scan/Encoder.h>
#include <boost/asio.hpp>
#include <boost/lambda/lambda.hpp>
#include <algorithm>
#include <iterator>
#include <boost/lexical_cast.hpp>
#include <vector>
#ifndef rad2deg
#define rad2deg(x) ((x) / M_PI * 180.0)
#endif
#define deg2rad_const (0.017453292519943295769236907684886f)
std::vector<unsigned char> exampleData(65536);
std::vector<unsigned char> receivedData(65536);
//static long receivedDataLen = 0;
static int getDiagnosticErrorCode()
{
#ifdef _MSC_VER
#undef ERROR
return (2);
#else
return (diagnostic_msgs::DiagnosticStatus::ERROR);
#endif
}
namespace armarx
{
bool emulateReply(UINT8* requestData, int requestLen, std::vector<unsigned char>* replyVector)
{
std::string request;
std::string reply;
std::vector<std::string> keyWordList;
std::vector<std::string> answerList;
std::string scannerType = "???";
ros::NodeHandle nhPriv;
enum
{
SIMU_RADAR = 0, SIMU_MRS_1XXX = 1, SIMU_NUM
};
// XXX
keyWordList.push_back("sMN SetAccessMode");
answerList.push_back("sAN SetAccessMode 1");
keyWordList.push_back("sWN EIHstCola");
answerList.push_back("sWA EIHstCola");
keyWordList.push_back("sRN FirmwareVersion");
answerList.push_back("sRA FirmwareVersion 8 1.0.0.0R");
keyWordList.push_back("sRN OrdNum");
answerList.push_back("sRA OrdNum 7 1234567");
keyWordList.push_back("sWN TransmitTargets 1");
answerList.push_back("sWA TransmitTargets");
keyWordList.push_back("sWN TransmitObjects 1");
answerList.push_back("sWA TransmitObjects");
keyWordList.push_back("sWN TCTrackingMode 0");
answerList.push_back("sWA TCTrackingMode");
keyWordList.push_back("sRN SCdevicestate");
answerList.push_back("sRA SCdevicestate 1");
keyWordList.push_back("sRN DItype");
answerList.push_back("sRA DItype D RMS3xx-xxxxxx");
keyWordList.push_back("sRN ODoprh");
answerList.push_back("sRA ODoprh 451");
keyWordList.push_back("sMN mSCloadappdef");
answerList.push_back("sAN mSCloadappdef");
keyWordList.push_back("sRN SerialNumber");
answerList.push_back("sRA SerialNumber 8 18020073");
keyWordList.push_back("sMN Run");
answerList.push_back("sAN Run 1s");
keyWordList.push_back("sRN ODpwrc");
answerList.push_back("sRA ODpwrc 20");
keyWordList.push_back("sRN LocationName");
answerList.push_back("sRA LocationName B not defined");
keyWordList.push_back("sEN LMDradardata 1");
answerList.push_back("sEA LMDradardata 1");
for (int i = 0; i < requestLen; i++)
{
request += (char) requestData[i];
}
for (size_t i = 0; i < keyWordList.size(); i++)
{
if (request.find(keyWordList[i]) != std::string::npos)
{
reply = (char) 0x02;
reply += answerList[i];
reply += (char) 0x03;
}
}
replyVector->clear();
for (size_t i = 0; i < reply.length(); i++)
{
replyVector->push_back((unsigned char) reply[i]);
}
return (true);
}
SickScanAdapter::SickScanAdapter(const std::string& hostname, const std::string& port, int& timelimit,
sick_scan::SickGenericParser* parser, char cola_dialect_id)
:
sick_scan::SickScanCommon(parser),
socket_(io_service_),
deadline_(io_service_),
parser_ptr(parser),
hostname_(hostname),
port_(port),
timelimit_(timelimit)
{
setEmulSensor(false);
if ((cola_dialect_id == 'a') || (cola_dialect_id == 'A'))
{
this->setProtocolType(CoLa_A);
}
if ((cola_dialect_id == 'b') || (cola_dialect_id == 'B'))
{
this->setProtocolType(CoLa_B);
}
assert(this->getProtocolType() != CoLa_Unknown);
m_numberOfBytesInReceiveBuffer = 0;
m_alreadyReceivedBytes = 0;
this->setReplyMode(0);
// io_service_.setReadCallbackFunction(boost::bind(&SopasDevice::readCallbackFunction, this, _1, _2));
// Set up the deadline actor to implement timeouts.
// Based on blocking TCP example on:
// http://www.boost.org/doc/libs/1_46_0/doc/html/boost_asio/example/timeouts/blocking_tcp_client.cpp
deadline_.expires_at(boost::posix_time::pos_infin);
checkDeadline();
}
SickScanAdapter::~SickScanAdapter()
{
// stop_scanner();
close_device();
}
using boost::asio::ip::tcp;
using boost::lambda::var;
using boost::lambda::_1;
/*!
\brief parsing datagram and publishing ros messages
\return error code
*/
int SickScanAdapter::loopOnce(LaserScan& scanData)
{
static int cnt = 0;
unsigned char receiveBuffer[65536];
int actual_length = 0;
static unsigned int iteration_count = 0;
//Always ASCII
bool useBinaryProtocol = false;
ros::Time recvTimeStamp = ros::Time::now(); // timestamp incoming package, will be overwritten by get_datagram
// tcp packet
ros::Time recvTimeStampPush = ros::Time::now(); // timestamp
int packetsInLoop = 0;
const int maxNumAllowedPacketsToProcess = 25; // maximum number of packets, which will be processed in this loop.
int numPacketsProcessed = 0; // count number of processed datagrams
do
{
int result = get_datagram(recvTimeStamp, receiveBuffer, 65536, &actual_length, useBinaryProtocol, &packetsInLoop);
numPacketsProcessed++;
ros::Duration dur = recvTimeStampPush - recvTimeStamp;
if (result != 0)
{
ARMARX_ERROR_S << "Read Error when getting datagram: " << result;
return sick_scan::ExitError; // return failure to exit node
}
if (actual_length <= 0)
{
return sick_scan::ExitSuccess;
} // return success to continue looping
// ----- if requested, skip frames
if (iteration_count++ % (config_.skip + 1) != 0)
{
return sick_scan::ExitSuccess;
}
//msg.header.stamp = recvTimeStamp + ros::Duration(config_.time_offset);
//datagrams are enclosed in <STX> (0x02), <ETX> (0x03) pairs
char* buffer_pos = (char*) receiveBuffer;
char* dstart, *dend;
bool dataToProcess = true;
std::vector<float> vang_vec;
vang_vec.clear();
dstart = NULL;
dend = NULL;
while (dataToProcess)
{
size_t dlength;
int success = -1;
int numEchos = 1;
int echoMask = 0;
LaserScannerInfo scanInfo;
// Always Parsing Ascii-Encoding of datagram
dstart = strchr(buffer_pos, 0x02);
if (dstart != NULL)
{
dend = strchr(dstart + 1, 0x03);
}
if ((dstart != NULL) && (dend != NULL))
{
dataToProcess = true; // continue parsing
dlength = dend - dstart;
*dend = '\0';
dstart++;
}
else
{
dataToProcess = false;
break;
}
// HEADER of data followed by DIST1 ... DIST2 ... DIST3 .... RSSI1 ... RSSI2.... RSSI3...
// <frameid>_<sign>00500_DIST[1|2|3]
//success = parser_ptr->parse_datagram(dstart, dlength, config_, msg, numEchos, echoMask);
success = parseDatagram(dstart, dlength, config_, scanData, scanInfo, numEchos, echoMask, true);
if (success == sick_scan::ExitSuccess)
{
if (numEchos > 5)
{
ARMARX_ERROR_S << "Too many echos";
}
// cloud_.header.stamp = recvTimeStamp + ros::Duration(config_.time_offset);
// cloud_.header.frame_id = config_.frame_id;
// cloud_.header.seq = 0;
// cloud_.height = numTmpLayer * numValidEchos; // due to multi echo multiplied by num. of layers
// cloud_.width = msg.ranges.size();
// cloud_.is_bigendian = false;
// cloud_.is_dense = true;
// cloud_.point_step = numChannels * sizeof(float);
// cloud_.row_step = cloud_.point_step * cloud_.width;
// cloud_.fields.resize(numChannels);
// for (int i = 0; i < numChannels; i++)
// {
// std::string channelId[] = {"x", "y", "z", "intensity"};
// cloud_.fields[i].name = channelId[i];
// cloud_.fields[i].offset = i * sizeof(float);
// cloud_.fields[i].count = 1;
// cloud_.fields[i].datatype = sensor_msgs::PointField::FLOAT32;
// }
cnt++;
if (cnt == 25)
{
ARMARX_INFO_S << "10th measurement: (" << scanData[10].angle << ", " << scanData[10].distance << ")";
cnt = 0;
}
}
// Start Point
if (dend != NULL)
{
buffer_pos = dend + 1;
}
} // end of while loop
}
while ((packetsInLoop > 0) && (numPacketsProcessed < maxNumAllowedPacketsToProcess));
return sick_scan::ExitSuccess; // return success to continue looping
}
//Adapted from sick_generic_parser
int SickScanAdapter::parseDatagram(char* datagram, size_t datagram_length, sick_scan::SickScanConfig& config,
LaserScan& scanData, LaserScannerInfo& scanInfo, int& numEchos, int& echoMask, bool updateScannerInfo)
{
int HEADER_FIELDS = 32;
char* cur_field;
size_t count;
// Reserve sufficient space
std::vector<char*> fields;
fields.reserve(datagram_length / 2);
// ----- only for debug output
std::vector<char> datagram_copy_vec;
datagram_copy_vec.resize(datagram_length + 1); // to avoid using malloc. destructor frees allocated mem.
char* datagram_copy = &(datagram_copy_vec[0]);
ARMARX_INFO_S << "parse_datagram(): Copying";
strncpy(datagram_copy, datagram, datagram_length); // datagram will be changed by strtok
datagram_copy[datagram_length] = 0;
count = 0;
cur_field = strtok(datagram, " ");
while (cur_field != NULL)
{
fields.push_back(cur_field);
cur_field = strtok(NULL, " ");
}
count = fields.size();
ARMARX_INFO_S << "parse_datagram(): Validate header";
// Validate header. Total number of tokens is highly unreliable as this may
// change when you change the scanning range or the device name using SOPAS ET
// tool. The header remains stable, however.
if ((int) count < HEADER_FIELDS)
{
ARMARX_ERROR_S << "received less fields than minimum fields (actual: " << count
<< ", minimum: " << HEADER_FIELDS << "), ignoring scan\n"
<< "are you using the correct node? (124 --> sick_tim310_1130000m01, > 33 --> sick_tim551_2050001, 580 --> sick_tim310s01, 592 --> sick_tim310)";
return sick_scan::ExitError;
}
if (strcmp(fields[15], "0"))
{
ARMARX_ERROR_S << "Field 15 of received data is not equal to 0. Unexpected data, ignoring scan";
return sick_scan::ExitError;
}
if (strcmp(fields[20], "DIST1"))
{
ARMARX_ERROR_S << "Field 20 of received data is not equal to DIST1i. Unexpected data, ignoring scan";
return sick_scan::ExitError;
}
// More in depth checks: check data length and RSSI availability
// 25: Number of data (<= 10F)
unsigned short int number_of_data = 0;
sscanf(fields[25], "%hx", &number_of_data);
//int numOfExpectedShots = parser_ptr->basicParams[scannerIdx].getNumberOfShots();
//if (number_of_data < 1 || number_of_data > numOfExpectedShots)
//{
// ARMARX_ERROR_S << "Data length is outside acceptable range 1-%d (%d). Ignoring scan";//, numOfExpectedShots, number_of_data);
// return sick_scan::ExitError;
//}
if ((int) count < HEADER_FIELDS + number_of_data)
{
ARMARX_ERROR_S << "Less fields than expected for %d data points (%zu). Ignoring scan"; //, number_of_data, count);
return sick_scan::ExitError;
}
// Calculate offset of field that contains indicator of whether or not RSSI data is included
size_t rssi_idx = 26 + number_of_data;
bool rssi = false;
if (strcmp(fields[rssi_idx], "RSSI1") == 0)
{
rssi = true;
}
unsigned short int number_of_rssi_data = 0;
if (rssi)
{
sscanf(fields[rssi_idx + 5], "%hx", &number_of_rssi_data);
// Number of RSSI data should be equal to number of data
if (number_of_rssi_data != number_of_data)
{
// ARMARX_ERROR("Number of RSSI data is lower than number of range data (%d vs %d", number_of_data,
// number_of_rssi_data);
return sick_scan::ExitError;
}
// Check if the total length is still appropriate.
// RSSI data size = number of RSSI readings + 6 fields describing the data
if ((int) count < HEADER_FIELDS + number_of_data + number_of_rssi_data + 6)
{
// ARMARX_ERROR("Less fields than expected for %d data points (%zu). Ignoring scan", number_of_data, count);
return sick_scan::ExitError;
}
if (strcmp(fields[rssi_idx], "RSSI1"))
{
// ARMARX_ERROR("Field %zu of received data is not equal to RSSI1 (%s). Unexpected data, ignoring scan", rssi_idx + 1,
// fields[rssi_idx + 1]);
}
}
ARMARX_INFO_S << "parse_datagram(): Checks complete";
// ----- read fields into msg
//msg.header.frame_id = config.frame_id;
// evtl. debug stream benutzen
// ROS_DEBUG("publishing with frame_id %s", config.frame_id.c_str());
ros::Time start_time = ros::Time::now(); // will be adjusted in the end
// <STX> (\x02)
// 0: Type of command (SN)
// 1: Command (LMDscandata)
// 2: Firmware version number (1)
// 3: Device number (1)
// 4: Serial number (eg. B96518)
// 5 + 6: Device Status (0 0 = ok, 0 1 = error)
// 7: Telegram counter (eg. 99)
// 8: Scan counter (eg. 9A)
// 9: Time since startup (eg. 13C8E59)
// 10: Time of transmission (eg. 13C9CBE)
// 11 + 12: Input status (0 0)
// 13 + 14: Output status (8 0)
// 15: Reserved Byte A (0)
// 16: Scanning Frequency (5DC)
unsigned short scanning_freq = -1;
sscanf(fields[16], "%hx", &scanning_freq);
// msg.scan_time = 1.0 / (scanning_freq / 100.0);
// 17: Measurement Frequency (36)
unsigned short measurement_freq = -1;
sscanf(fields[17], "%hx", &measurement_freq);
// msg.time_increment = 1.0 / (measurement_freq * 100.0);
// 18: Number of encoders (0)
// 19: Number of 16 bit channels (1)
// 20: Measured data contents (DIST1)
// 21: Scaling factor (3F800000)
// ignored for now (is always 1.0):
// 22: Scaling offset (00000000) -- always 0
// 23: Starting angle (FFF92230)
if (updateScannerInfo)
{
uint starting_angle = (uint) - 1;
sscanf(fields[23], "%x", &starting_angle);
scanInfo.minAngle = (starting_angle / 10000.0) / 180.0 * M_PI - M_PI / 2;
// ROS_DEBUG("starting_angle: %d, angle_min: %f", starting_angle, msg.angle_min);
// 24: Angular step width (2710)
unsigned short angular_step_width = -1;
sscanf(fields[24], "%hx", &angular_step_width);
scanInfo.stepSize = (angular_step_width / 10000.0) / 180.0 * M_PI;
scanInfo.maxAngle = scanInfo.minAngle + (number_of_data - 1) * scanInfo.stepSize;
}
// 25: Number of data (<= 10F)
// This is already determined above in number_of_data
//int index_min = 0;
ARMARX_INFO_S << "parse_datagram(): Parsing";
//param_ptr->checkForDistAndRSSI(fields, number_of_data, distNum, rssiNum, msg.ranges, msg.intensities, echoMask);
int distNum = 0;
int rssiNum = 0;
int baseOffset = 20;
echoMask = 0;
// More in depth checks: check data length and RSSI availability
// 25: Number of data (<= 10F)
unsigned short int curr_number_of_data = 0;
if (strstr(fields[baseOffset], "DIST") != fields[baseOffset]) // First initial check
{
// ARMARX_ERROR("Field 20 of received data does not start with DIST (is: %s). Unexpected data, ignoring scan",
// fields[20]);
return sick_scan::ExitError;
}
//Read either intensity or distance data, regarding what type is given in the datagram
std::vector<float> distVal, intensityVal;
for (int offset = 20; offset < (int) fields.size();)
{
bool distFnd = false;
bool rssiFnd = false;
if (strlen(fields[offset]) == 5)
{
if (strstr(fields[offset], "DIST") == fields[offset])
{
distFnd = true;
distNum++;
int distId = -1;
if (1 == sscanf(fields[offset], "DIST%d", &distId))
{
echoMask |= (1 << (distId - 1)); // set bit regarding to id
}
}
else if (strstr(fields[offset], "RSSI") == fields[offset])
{
rssiNum++;
rssiFnd = true;
}
}
if (rssiFnd || distFnd)
{
offset += 5;
if (offset >= (int) fields.size())
{
ARMARX_ERROR_S << "Missing RSSI or DIST data";
return sick_scan::ExitError;
}
curr_number_of_data = 0;
sscanf(fields[offset], "%hx", &curr_number_of_data);
if (curr_number_of_data != number_of_data)
{
ARMARX_ERROR_S << "number of dist or rssi values mismatching.";
return sick_scan::ExitError;
}
offset++;
// Here is the first value
for (int i = 0; i < curr_number_of_data; i++)
{
if (distFnd)
{
unsigned short iRange;
sscanf(fields[offset + i], "%hx", &iRange);
float range = iRange / 1000.0;
distVal.push_back(range);
}
else
{
unsigned short iRSSI;
sscanf(fields[offset + i], "%hx", &iRSSI);
intensityVal.push_back((float) iRSSI);
}
}
offset += number_of_data;
}
else
{
offset++; // necessary????
}
}
numEchos = distNum;
ARMARX_INFO_S << "numDistanceVals: " << distNum << ", numIntensityVals: " << rssiNum;
//TODO: Write ScanSteps with intensity
scanData.reserve(distVal.size());
for (int i = 0; i < (int) distVal.size(); i++)
{
LaserScanStep step;
step.angle = i * scanInfo.stepSize;
step.distance = distVal[i];
scanData.push_back(step);
}
// 26 + n: RSSI data included
// IF RSSI not included:
// 26 + n + 1 .. 26 + n + 3 = unknown (but seems to be [0, 1, B] always)
// 26 + n + 4 .. count - 4 = device label
// count - 3 .. count - 1 = unknown (but seems to be 0 always)
// <ETX> (\x03)
// msg.range_min = override_range_min_;
// msg.range_max = override_range_max_;
ARMARX_INFO_S << "parse_datagram(): Time check";
return sick_scan::ExitSuccess;
}
void SickScanAdapter::disconnectFunction()
{
}
void SickScanAdapter::disconnectFunctionS(void* obj)
{
if (obj != NULL)
{
((SickScanAdapter*)(obj))->disconnectFunction();
}
}
void SickScanAdapter::readCallbackFunctionS(void* obj, UINT8* buffer, UINT32& numOfBytes)
{
((SickScanAdapter*) obj)->readCallbackFunction(buffer, numOfBytes);
}
void SickScanAdapter::setReplyMode(int _mode)
{
m_replyMode = _mode;
}
int SickScanAdapter::getReplyMode()
{
return (m_replyMode);
}
/*!
\brief Set emulation flag (using emulation instead of "real" scanner - currently implemented for radar
\param _emulFlag: Flag to switch emulation on or off
\return
*/
void SickScanAdapter::setEmulSensor(bool _emulFlag)
{
m_emulSensor = _emulFlag;
}
/*!
\brief get emulation flag (using emulation instead of "real" scanner - currently implemented for radar
\param
\return bool: Flag to switch emulation on or off
*/
bool SickScanAdapter::getEmulSensor()
{
return (m_emulSensor);
}
//
// Look for 23-frame (STX/ETX) in receive buffer.
// Move frame to start of buffer
//
// Return: 0 : No (complete) frame found
// >0 : Frame length
//
SopasEventMessage SickScanAdapter::findFrameInReceiveBuffer()
{
UINT32 frameLen = 0;
UINT32 i;
// Depends on protocol...
if (getProtocolType() == CoLa_A)
{
//
// COLA-A
//
// Must start with STX (0x02)
if (m_receiveBuffer[0] != 0x02)
{
// Look for starting STX (0x02)
for (i = 1; i < m_numberOfBytesInReceiveBuffer; i++)
{
if (m_receiveBuffer[i] == 0x02)
{
break;
}
}
// Found beginning of frame?
if (i >= m_numberOfBytesInReceiveBuffer)
{
// No start found, everything can be discarded
m_numberOfBytesInReceiveBuffer = 0; // Invalidate buffer
return SopasEventMessage(); // No frame found
}
// Move frame start to index 0
UINT32 newLen = m_numberOfBytesInReceiveBuffer - i;
memmove(&(m_receiveBuffer[0]), &(m_receiveBuffer[i]), newLen);
m_numberOfBytesInReceiveBuffer = newLen;
}
// Look for ending ETX (0x03)
for (i = 1; i < m_numberOfBytesInReceiveBuffer; i++)
{
if (m_receiveBuffer[i] == 0x03)
{
break;
}
}
// Found end?
if (i >= m_numberOfBytesInReceiveBuffer)
{
// No end marker found, so it's not a complete frame (yet)
return SopasEventMessage(); // No frame found
}
// Calculate frame length in byte
frameLen = i + 1;
return SopasEventMessage(m_receiveBuffer, CoLa_A, frameLen);
}
else if (getProtocolType() == CoLa_B)
{
UINT32 magicWord;
UINT32 payloadlength;
if (m_numberOfBytesInReceiveBuffer < 4)
{
return SopasEventMessage();
}
UINT16 pos = 0;
magicWord = colab::getIntegerFromBuffer<UINT32>(m_receiveBuffer, pos);
if (magicWord != 0x02020202)
{
// Look for starting STX (0x02020202)
for (i = 1; i <= m_numberOfBytesInReceiveBuffer - 4; i++)
{
pos = i; // this is needed, as the position value is updated by getIntegerFromBuffer
magicWord = colab::getIntegerFromBuffer<UINT32>(m_receiveBuffer, pos);
if (magicWord == 0x02020202)
{
// found magic word
break;
}
}
// Found beginning of frame?
if (i > m_numberOfBytesInReceiveBuffer - 4)
{
// No start found, everything can be discarded
m_numberOfBytesInReceiveBuffer = 0; // Invalidate buffer
return SopasEventMessage(); // No frame found
}
else
{
// Move frame start to index
UINT32 bytesToMove = m_numberOfBytesInReceiveBuffer - i;
memmove(&(m_receiveBuffer[0]), &(m_receiveBuffer[i]), bytesToMove); // payload+magic+length+s+checksum
m_numberOfBytesInReceiveBuffer = bytesToMove;
}
}
// Pruefe Laenge des Pufferinhalts
if (m_numberOfBytesInReceiveBuffer < 9)
{
// Es sind nicht genug Daten fuer einen Frame
printInfoMessage("SickScanCommonNw::findFrameInReceiveBuffer: Frame cannot be decoded yet, only " +
::toString(m_numberOfBytesInReceiveBuffer) + " bytes in the buffer.", m_beVerbose);
return SopasEventMessage();
}
// Read length of payload
pos = 4;
payloadlength = colab::getIntegerFromBuffer<UINT32>(m_receiveBuffer, pos);
printInfoMessage(
"SickScanCommonNw::findFrameInReceiveBuffer: Decoded payload length is " + ::toString(payloadlength) +
" bytes.", m_beVerbose);
// Ist die Datenlaenge plausibel und wuede in den Puffer passen?
if (payloadlength > (sizeof(m_receiveBuffer) - 9))
{
// magic word + length + checksum = 9
printWarning(
"SickScanCommonNw::findFrameInReceiveBuffer: Frame too big for receive buffer. Frame discarded with length:"
+ ::toString(payloadlength) + ".");
m_numberOfBytesInReceiveBuffer = 0;
return SopasEventMessage();
}
if ((payloadlength + 9) > m_numberOfBytesInReceiveBuffer)
{
// magic word + length + s + checksum = 10
printInfoMessage(
"SickScanCommonNw::findFrameInReceiveBuffer: Frame not complete yet. Waiting for the rest of it (" +
::toString(payloadlength + 9 - m_numberOfBytesInReceiveBuffer) + " bytes missing).", m_beVerbose);
return SopasEventMessage(); // frame not complete
}
// Calculate the total frame length in bytes: Len = Frame (9 bytes) + Payload
frameLen = payloadlength + 9;
//
// test checksum of payload
//
UINT8 temp = 0;
UINT8 temp_xor = 0;
UINT8 checkSum;
// Read original checksum
pos = frameLen - 1;
checkSum = colab::getIntegerFromBuffer<UINT8>(m_receiveBuffer, pos);
// Erzeuge die Pruefsumme zum Vergleich
for (UINT16 i = 8; i < (frameLen - 1); i++)
{
pos = i;
temp = colab::getIntegerFromBuffer<UINT8>(m_receiveBuffer, pos);
temp_xor = temp_xor ^ temp;
}
// Vergleiche die Pruefsummen
if (temp_xor != checkSum)
{
printWarning("SickScanCommonNw::findFrameInReceiveBuffer: Wrong checksum, Frame discarded.");
m_numberOfBytesInReceiveBuffer = 0;
return SopasEventMessage();
}
return SopasEventMessage(m_receiveBuffer, CoLa_B, frameLen);
}
// Return empty frame
return SopasEventMessage();
}
/**
* Read callback. Diese Funktion wird aufgerufen, sobald Daten auf der Schnittstelle
* hereingekommen sind.
*/
void SickScanAdapter::processFrame(ros::Time timeStamp, SopasEventMessage& frame)
{
if (getProtocolType() == CoLa_A)
{
printInfoMessage(
"SickScanCommonNw::processFrame: Calling processFrame_CoLa_A() with " + ::toString(frame.size()) + " bytes.",
m_beVerbose);
// processFrame_CoLa_A(frame);
}
else if (getProtocolType() == CoLa_B)
{
printInfoMessage(
"SickScanCommonNw::processFrame: Calling processFrame_CoLa_B() with " + ::toString(frame.size()) + " bytes.",
m_beVerbose);
// processFrame_CoLa_B(frame);
}
// Push frame to recvQueue
DatagramWithTimeStamp dataGramWidthTimeStamp(timeStamp, std::vector<unsigned char>(frame.getRawData(),
frame.getRawData() +
frame.size()));
// recvQueue.push(std::vector<unsigned char>(frame.getRawData(), frame.getRawData() + frame.size()));
recvQueue.push(dataGramWidthTimeStamp);
}
void SickScanAdapter::readCallbackFunction(UINT8* buffer, UINT32& numOfBytes)
{
ros::Time rcvTimeStamp = ros::Time::now(); // stamp received datagram
bool beVerboseHere = false;
printInfoMessage(
"SickScanCommonNw::readCallbackFunction(): Called with " + toString(numOfBytes) + " available bytes.",
beVerboseHere);
ScopedLock lock(&m_receiveDataMutex); // Mutex for access to the input buffer
UINT32 remainingSpace = sizeof(m_receiveBuffer) - m_numberOfBytesInReceiveBuffer;
UINT32 bytesToBeTransferred = numOfBytes;
if (remainingSpace < numOfBytes)
{
bytesToBeTransferred = remainingSpace;
// printWarning("SickScanCommonNw::readCallbackFunction(): Input buffer space is to small, transferring only " +
// ::toString(bytesToBeTransferred) + " of " + ::toString(numOfBytes) + " bytes.");
}
else
{
// printInfoMessage("SickScanCommonNw::readCallbackFunction(): Transferring " + ::toString(bytesToBeTransferred) +
// " bytes from TCP to input buffer.", beVerboseHere);
}
if (bytesToBeTransferred > 0)
{
// Data can be transferred into our input buffer
memcpy(&(m_receiveBuffer[m_numberOfBytesInReceiveBuffer]), buffer, bytesToBeTransferred);
m_numberOfBytesInReceiveBuffer += bytesToBeTransferred;
UINT32 size = 0;
while (1)
{
// Now work on the input buffer until all received datasets are processed
SopasEventMessage frame = findFrameInReceiveBuffer();
size = frame.size();
if (size == 0)
{
// Framesize = 0: There is no valid frame in the buffer. The buffer is either empty or the frame
// is incomplete, so leave the loop
printInfoMessage("SickScanCommonNw::readCallbackFunction(): No complete frame in input buffer, we are done.",
beVerboseHere);
// Leave the loop
break;
}
else
{
// A frame was found in the buffer, so process it now.
printInfoMessage(
"SickScanCommonNw::readCallbackFunction(): Processing a frame of length " + ::toString(frame.size()) +
" bytes.", beVerboseHere);
processFrame(rcvTimeStamp, frame);
UINT32 bytesToMove = m_numberOfBytesInReceiveBuffer - size;
memmove(&(m_receiveBuffer[0]), &(m_receiveBuffer[size]), bytesToMove); // payload+magic+length+s+checksum
m_numberOfBytesInReceiveBuffer = bytesToMove;
}
}
}
else
{
// There was input data from the TCP interface, but our input buffer was unable to hold a single byte.
// Either we have not read data from our buffer for a long time, or something has gone wrong. To re-sync,
// we clear the input buffer here.
m_numberOfBytesInReceiveBuffer = 0;
}
}
int SickScanAdapter::init_device()
{
int portInt;
sscanf(port_.c_str(), "%d", &portInt);
m_nw.init(hostname_, portInt, disconnectFunctionS, (void*) this);
m_nw.setReadCallbackFunction(readCallbackFunctionS, (void*) this);
if (this->getEmulSensor())
{
ARMARX_INFO_S << "Sensor emulation is switched on - network traffic is switched off.";
}
else
{
m_nw.connect();
}
return sick_scan::ExitSuccess;
}
int SickScanAdapter::close_device()
{
ARMARX_ERROR_S << "Disconnecting TCP-Connection.";
m_nw.disconnect();
return 0;
}
bool SickScanAdapter::stopScanData()
{
stop_scanner();
return (true);
}
void SickScanAdapter::handleRead(boost::system::error_code error, size_t bytes_transfered)
{
ec_ = error;
bytes_transfered_ += bytes_transfered;
}
void SickScanAdapter::checkDeadline()
{
if (deadline_.expires_at() <= boost::asio::deadline_timer::traits_type::now())
{
// The reason the function is called is that the deadline expired. Close
// the socket to return all IO operations and reset the deadline
socket_.close();
deadline_.expires_at(boost::posix_time::pos_infin);
}
// Nothing bad happened, go back to sleep
deadline_.async_wait(boost::bind(&SickScanAdapter::checkDeadline, this));
}
int SickScanAdapter::numberOfDatagramInInputFifo()
{
int numVal = this->recvQueue.getNumberOfEntriesInQueue();
return (numVal);
}
int SickScanAdapter::readWithTimeout(size_t timeout_ms, char* buffer, int buffer_size, int* bytes_read,
bool* exception_occured, bool isBinary)
{
// Set up the deadline to the proper timeout, error and delimiters
deadline_.expires_from_now(boost::posix_time::milliseconds(timeout_ms));
ec_ = boost::asio::error::would_block;
bytes_transfered_ = 0;
// Polling - should be changed to condition variable in the future
int waitingTimeInMs = 1; // try to lookup for new incoming packages
size_t i;
for (i = 0; i < timeout_ms; i += waitingTimeInMs)
{
if (false == this->recvQueue.isQueueEmpty())
{
break;
}
boost::this_thread::sleep(boost::posix_time::milliseconds(waitingTimeInMs));
}
if (i >= timeout_ms)
{
ROS_ERROR("no answer received after %zu ms. Maybe sopas mode is wrong.\n", timeout_ms);
return (sick_scan::ExitError);
}
DatagramWithTimeStamp datagramWithTimeStamp = this->recvQueue.pop();
*bytes_read = datagramWithTimeStamp.datagram.size();
memcpy(buffer, &(datagramWithTimeStamp.datagram[0]), datagramWithTimeStamp.datagram.size());
return (sick_scan::ExitSuccess);
}
/**
* Send a SOPAS command to the device and print out the response to the console.
*/
int SickScanAdapter::sendSOPASCommand(const char* request, std::vector<unsigned char>* reply, int cmdLen)
{
#if 0
if (!socket_.is_open())
{
ROS_ERROR("sendSOPASCommand: socket not open");
diagnostics_.broadcast(getDiagnosticErrorCode(), "sendSOPASCommand: socket not open.");
return sick_scan::ExitError;
}
#endif
int sLen = 0;
int preambelCnt = 0;
bool cmdIsBinary = false;
if (request != NULL)
{
sLen = cmdLen;
preambelCnt = 0; // count 0x02 bytes to decide between ascii and binary command
if (sLen >= 4)
{
for (int i = 0; i < 4; i++)
{
if (request[i] == 0x02)
{
preambelCnt++;
}
}
}
if (preambelCnt < 4)
{
cmdIsBinary = false;
}
else
{
cmdIsBinary = true;
}
int msgLen = 0;
if (cmdIsBinary == false)
{
msgLen = strlen(request);
}
else
{
int dataLen = 0;
for (int i = 4; i < 8; i++)
{
dataLen |= ((unsigned char) request[i] << (7 - i) * 8);
}
msgLen = 8 + dataLen + 1; // 8 Msg. Header + Packet +
}
#if 1
if (getEmulSensor())
{
emulateReply((UINT8*) request, msgLen, reply);
}
else
{
bool debugBinCmd = false;
if (debugBinCmd)
{
printf("=== START HEX DUMP ===\n");
for (int i = 0; i < msgLen; i++)
{
unsigned char* ptr = (UINT8*) request;
printf("%02x ", ptr[i]);
}
printf("\n=== END HEX DUMP ===\n");
}
m_nw.sendCommandBuffer((UINT8*) request, msgLen);
}
#else
/*
* Write a SOPAS variable read request to the device.
*/
try
{
boost::asio::write(socket_, boost::asio::buffer(request, msgLen));
}
catch (boost::system::system_error& e)
{
ROS_ERROR("write error for command: %s", request);
diagnostics_.broadcast(getDiagnosticErrorCode(), "Write error for sendSOPASCommand.");
return sick_scan::ExitError;
}
#endif
}
// Set timeout in 5 seconds
const int BUF_SIZE = 65536;
char buffer[BUF_SIZE];
int bytes_read;
// !!!
if (getEmulSensor())
{
}
else
{
if (readWithTimeout(getReadTimeOutInMs(), buffer, BUF_SIZE, &bytes_read, 0, cmdIsBinary) == sick_scan::ExitError)
{
ARMARX_INFO_S << "sendSOPASCommand: no full reply available for read after (ms): " << getReadTimeOutInMs();
return sick_scan::ExitError;
}
if (reply)
{
reply->resize(bytes_read);
std::copy(buffer, buffer + bytes_read, &(*reply)[0]);
}
}
return sick_scan::ExitSuccess;
}
int SickScanAdapter::get_datagram(ros::Time& recvTimeStamp, unsigned char* receiveBuffer, int bufferSize,
int* actual_length,
bool isBinaryProtocol, int* numberOfRemainingFifoEntries)
{
if (NULL != numberOfRemainingFifoEntries)
{
*numberOfRemainingFifoEntries = 0;
}
this->setReplyMode(1);
if (this->getEmulSensor())
{
#ifndef ROSSIMU
// boost::this_thread::sleep(boost::posix_time::milliseconds(waitingTimeInMs));
ros::Time timeStamp = ros::Time::now();
uint32_t nanoSec = timeStamp.nsec;
double waitTime10Hz = 10.0 * (double) nanoSec / 1E9; // 10th of sec. [0..10[
uint32_t waitTime = (int) waitTime10Hz; // round down
double waitTimeUntilNextTime10Hz = 1 / 10.0 * (1.0 - (waitTime10Hz - waitTime));
ros::Duration(waitTimeUntilNextTime10Hz).sleep();
SickScanRadar radar(this);
radar.setEmulation(true);
radar.simulateAsciiDatagram(receiveBuffer, actual_length);
recvTimeStamp = ros::Time::now();
#endif
}
else
{
const int maxWaitInMs = getReadTimeOutInMs();
std::vector<unsigned char> dataBuffer;
#if 1 // prepared for reconnect
bool retVal = this->recvQueue.waitForIncomingObject(maxWaitInMs);
if (retVal == false)
{
ARMARX_ERROR_S << "Timeout during waiting for new datagram";
return sick_scan::ExitError;
}
else
{
// Look into receiving queue for new Datagrams
//
//
DatagramWithTimeStamp datagramWithTimeStamp = this->recvQueue.pop();
if (NULL != numberOfRemainingFifoEntries)
{
*numberOfRemainingFifoEntries = this->recvQueue.getNumberOfEntriesInQueue();
}
recvTimeStamp = datagramWithTimeStamp.timeStamp;
dataBuffer = datagramWithTimeStamp.datagram;
}
#endif
// dataBuffer = this->recvQueue.pop();
long size = dataBuffer.size();
memcpy(receiveBuffer, &(dataBuffer[0]), size);
*actual_length = size;
}
#if 0
static int cnt = 0;
char szFileName[255];
sprintf(szFileName, "/tmp/dg%06d.bin", cnt++);
FILE* fout;
fout = fopen(szFileName, "wb");
if (fout != NULL)
{
fwrite(receiveBuffer, size, 1, fout);
fclose(fout);
}
#endif
return sick_scan::ExitSuccess;
if (!socket_.is_open())
{
ROS_ERROR("get_datagram: socket not open");
diagnostics_.broadcast(getDiagnosticErrorCode(), "get_datagram: socket not open.");
return sick_scan::ExitError;
}
/*
* Write a SOPAS variable read request to the device.
*/
std::vector<unsigned char> reply;
// Wait at most 5000ms for a new scan
size_t timeout = 30000;
bool exception_occured = false;
char* buffer = reinterpret_cast<char*>(receiveBuffer);
if (readWithTimeout(timeout, buffer, bufferSize, actual_length, &exception_occured, isBinaryProtocol) !=
sick_scan::ExitSuccess)
{
ROS_ERROR_THROTTLE(1.0, "get_datagram: no data available for read after %zu ms", timeout);
diagnostics_.broadcast(getDiagnosticErrorCode(), "get_datagram: no data available for read after timeout.");
// Attempt to reconnect when the connection was terminated
if (!socket_.is_open())
{
#ifdef _MSC_VER
Sleep(1000);
#else
sleep(1);
#endif
ARMARX_INFO << "Failure - attempting to reconnect";
return init();
}
return exception_occured ? sick_scan::ExitError : sick_scan::ExitSuccess; // keep on trying
}
return sick_scan::ExitSuccess;
}
}