相信初学激光雷达的童鞋们,在实现代码编程这一块,都会在网上搜到一个SopasCommunication这个关于LMS100的激光雷达数据采集示例程序,这是sick官方程序,基于C++/CLI实现socket连接,基于.net实现,平台移植性不好。我用的是LMS511的激光雷达,用这个程序也是可以实现连接,数据采集的。但是因为程序本身很大,有很多头文件、实现文件。我是基于DOC文件夹下SopasCommunication.chm里面的Sopas Communication Documentation来进行翻译的。希望能给学习激光雷达的人提供帮助!
SopasCommunication是一个简单的框架,以证明利用SOPAS通信架构实现和SICK设备的通信。该框架提供了基本的功能去调用设备的方法,同意设备事件,从设备中读取变量,向设备里写变量。
一些预定义SOPAS方法,如“登录”是可用的,可以作为一个例子,从设备查询信息,或在设备上执行方法。该框架支持可选的的通信渠道,SOPAS协议和SOPAS框架。
通信部分要求保持简单(见限制),并且只被用作工作示例。多线程是不被使用在这里的。同步请求被发送到该设备,然后这个框架等待回答。这是通过忙等待完成,但包括睡眠命令,以减少处理器负载。如果没有收到回应,15秒后框架将返回。
看以下章节以获取更详细的信息。
版本
这个框架被分成两部分:(在此情况下LMS100)的特定部分和一个普遍的供所有设备使用的部分。此版本包括以下版本:
版本:
通用:v1.02
LMS100:v1.02
为了获得当前版本号,可分别执行这两个函数 CSopasInterface::GetFrameworkVersion()或 CLms100SopasInterface::GetDeviceFrameworkVersion()
局限
在此版本中也有一定的局限性,可能在未来的版本中被淘汰:
(1)为了保持通信结构简单忙等待被使用在轮询中。睡眠命令发出,以减少对WIN32系统的处理器负载。这是不可移植的。
(2)该框架提供的登录密码可能不适用于旧设备(如LMS400)工作。见CSopasLoginLevel如何改变框架使用的密码。
(3)不支持64位整数
(4)ASCII成帧被限制为10万字节的帧的长度。见CSopasAsciiFramer
编译项目
该项目是用C++/ CLI编写的,并能够与Microsoft Visual Studio 2005及以上版本进行编译。在.NET框架V2.0下必须执行的程序。
仅在数据传送类(CTcpCommunication和CSerialCommunication),实施例主要功能实际上是使用.NET框架。其他的一切都是用普通的C ++。有一个名为SopasCommunicationLms100_cpp.vcproj的额外的项目(包括相关的解决方案文件),这是一个VS2005的普通的C ++项目。该项目是可编译的,但你需要自己实现数据传输功能。该框架的这一部分应该可在最近的任一C ++编译器重编译。
注意:
现在有一个睡眠命令包含CCommunicationHandler::SendAndReceive()和CCommunicationHandler:: PollAsyncAnswers(),以减少在忙等待时处理器的负载。这是不可移植的,并且只在WIN32定义设置的前提下被编译。其他平台将使用忙等待满处理器负荷。
示例
主要功能(C++/ CLI-仅单个项目)包含了LMS100的例子。这个例子通过TCP/ IP使用默认的IP地址为192.168.0.1连接到LMS100。你也可以通过提供它作为一个命令行参数设置IP地址。该示例连接到LMS100,设置50Hz的频率,0.5度的角分辨率,并根据开始 -结束的角度范围的扫描配置。然后测量模式被启动,程序等待直到LMS100到达测量模式。然后程序获得的扫描和显示一些状态信息。
然后程序调查1000次扫描根反复读取相关的设备变量。作为一个例子,它计算在90度的光束的平均值。因为该程序实际上可能轮询扫描速度比用来检测新的扫描而被更新了电报计算器速度要快。
作为第二个例子相同的过程是通过被同意的SCANDATA事件和处理SCANDATA在回调函数来完成的。
注意:
为了使该示例正常工作,SCANDATA格式必须被配置成包含在第一个16位通道距离数据。这不被检查。
使用
建立连接
通过创建一个通信通道来实现连接设备,建立连接(支持TCP/IP和串口连接)
CTcpCommunication ipComm; if(ipComm.Connect(IPAddress::Parse("192.168.0.1")) == true) { //... }
注意:
只有C++/CLI项目提供使用串口连接或者是TCP/IP连接。你可以通过CCommunication继承和实现所有必要的接口方法来实现你自己的数据传输。
与设备通信
与设备的通信由CSopasInterface类及其子类完成。当你创建一个实例,你还通过创建按类的实例指定的通信信道(见上文),根据相关的类创建实例中的帧(二进制/ ASCII)和协议(COLA-A/ COLA-B)。
// Using TCP/IP connection, ASCII framing and COLA-A-protocol CSopasAsciiFramer framer; CColaAProtocol protocol(ipComm, framer) CLms100SopasInterface sopas(protocol);注意:
确保CProtocol,CCommunication和CFramer情况下保留的范围长于CLms100SopasInterface。这是封装自己的函数,直接使用的协议来源于CSopasInterface或其后代类。
使用预定义的方法来获取数据
该CLms100SopasInterface提供了一些功能从LMS100获取数据。请看下面的例子:
// Get version information void SopasExample(CLms100SopasInterface& rSopas) { std::string version; std::string name; if (rSopas.GetVersionString(name, version) == true) { printf("Name: %s, Version: %s\n", name.c_str(), version.c_str()); } // Login with according SOPAS userlevel if(rSopas.Login(SopasLoginLevel::AUTHORIZEDCLIENT) == true) { // ... } // Start measuring (SOPAS method returns 0 if everything is OK) CLms100SopasInterface::EMeasureError retVal; if(rSopas.StartMeasure(&retVal) == true) { // ... } }注意:
如果调用CSopasInterface的方法失败,你应该调用CSopasInterface:: GetSopasErrorCode()。存在两种可能性:
(1)该设备报告SOPAS错误代码(如无效的方法名)。
(2)还有一个错误(例如超时,没有收到足够的数据,...)。在这种情况下,GetSopasErrorCode报告0。
预定义功能概述
目前可提供下列功能(CSopasInterface):
(1)Login
(2)Logout
(3)GetVersionString
对于LMS100以下的功能是可用的(CLms100SopasInterface):
(1)GetScanData
(2)GetState
(3)StartMeasure
(4)StopMeasure
(5)GetScanDataConfig
(6)SubscribeScanDataEvent
数据类型
正确地将数据序列化和反序列化前提是数据类型是已知的。类型定义已被定义为基本数据类型并且一些复杂和合成类型也已确定。它们应该用来简化序列化和反序列化。详情请参见数据类型。数据流
数据被序列化或反序列将被存储在二进制数据流(见CBinaryDataStream)。该数据流有助于通过保持一个写位置,并且相应地增加的读取位置,以简化数据处理。有一些辅助功能,以将数据写入到该流,或从它读出数据。流可以轻松连续读取和写入,请参见下面的例子。
建立自己的函数来读取一个变量
如果你想从设备中读一个变量,你必须创建功能如下:
bool ReadMyVariable(CProtocol& rProtocol) { bool bRetVal = false; // Build request, send it and wait for answer CBinaryDataStream* pReceivedData = rProtocol.ReadVariable("MyVarName"); // Function blocks until data has been received. The data is returned or // null pointer is returned on error, e.g. timeout, wrong answer, ... if (pReceivedData != 0) { // Deserialize the answer. Let's assume the following data type: // struct // { // SOPAS_Float f; // SOPAS_UInt8 c; // SOPAS_UInt32 l; // }; // Get access to deserializer of according protocol CDeserializer& rDeserializer = rProtocol.GetDeserializer(); // Deserialize variables in the same order as defined in the struct above. SOPAS_Float f; SOPAS_UInt8 c; SOPAS_UInt32 l; bRetVal = rDeserializer.Deserialize(*pReceivedData, f); bRetVal &= rDeserializer.Deserialize(*pReceivedData, c); bRetVal &= rDeserializer.Deserialize(*pReceivedData, l); // Additional check: All data of the answer should have been processed. // Otherwise the data type structure might have changed. bRetVal &= pReceivedData->IsReadPosAtEnd(); // Note: The struct values are ignored in this example. // bRetVal shows success of deserialization. } // All data processed, delete it delete pReceivedData; return bRetVal; }
该变量可以是简单的类型或聚合的类型。在这种情况下,所有的元素因为通信被一个个序列化。你需要知道变量的类型和调用相应的反序列化函数来从应答中提取值。应该使用typedef为SOPAS定义大多数简单类型。
注意:
二进制数据被包封在CBinaryDataStream内,它保留了读出和写入指针的当前位置。这使得它更容易从响应中反序列化的所有的连续元素。
建立自己的函数去调用的方法
如果你想在设备上调用一个方法,你必须自己序列化所有参数并且反序列化响应。至于变量的相关数据类型可以是简单的或复杂的结构。该框架可以不知道的参数的结构。
序列化基本上是做同样的方式:你可以通过使用类型定义为SOPAS数据类型编写简单数据类型到流。你必须预先创建数据流。存在两种可能性:
(1)查询你要序列化的每个数据类型所需的大小。
(2)创建流,要求在任何情况下估计都有足够大的所需大小。如果序列化失败则流不够大
bool InvokeMyMethod(CProtocol& rProtocol) { // Assume a device method that takes the following parameter: // struct // { // SOPAS_Int8 c; // SOPAS_Float f; // SOPAS_Bool b; // }; bool bRetVal = false; // Set parameters accordingly (might be done outside of this function) SOPAS_Int8 c = 23; SOPAS_Float f = 23.5f; SOPAS_Bool b = 0; // Create stream to store serialized parameters // Using exact stream size. // Alternatively just use 100 as stream size which should be big enough... CSerializer& rSerializer = rProtocol.GetSerializer(); size_t minStreamSize = rSerializer.GetSerializedSize(c) + rSerializer.GetSerializedSize(f) + rSerializer.GetSerializedSize(b); // Create the stream as lokal variable so it's automatically deleted afterwards CBinaryDataStream serializedParamStream(static_cast<unsigned int>(minStreamSize)); // Serialize parameters into stream bool bSerialized = true; bSerialized &= rSerializer.Serialize(serializedParamStream, c); bSerialized &= rSerializer.Serialize(serializedParamStream, f); bSerialized &= rSerializer.Serialize(serializedParamStream, b); if (bSerialized == true) { // Build request CBinaryDataStream* pReceivedData = rProtocol.InvokeMethod("MyMethodName", &serializedParamStream); // Deserializing the result is done the same way as for reading variables // ... // bRetVal might be set to "true" here... } return bRetVal; }
复杂类型
复杂的数据类型(如结构),只需要通过元素反序列化元素。也有一些类型的,虽然可以直接反序列化。
字符串
字符串是通过调用相关功能来实现反序列化。SOPAS区分可变长度和固定长度的字符串。这种差异对反序列化本身很重要。因此字符串类型被加入。使用SOPAS_FlexString和SOPAS_FixString相应地。所述FixString的长度不能改变。它的内容可以是长或短,但将始终使用初使长度要发送的字符串(零填充或clipping被使用)。该FlexString只是一个关于std:: string的类型定义。
// Send request, receive data // ... SOPAS_FlexString flexString; // Length will be extracted from stream bSuccess = rDeserializer.Deserialize(*pReceivedData, flexString); printf("%s", flexString.c_str()); SOPAS_FixString fixString(7); // For example this string has a fixed length of 7. bSuccess = rDeserializer.Deserialize(*pReceivedData, fixString); printf("%s", fixString.data.c_str());
简单类型的数组
还有一个辅助函数来反序列化简单类型的数组。基本上所有的元素都是经过其他反序列化上。数组长度必须是已知的。对于FlexArrays一个简单的包装实现。它们的长度可以变化,并且是从数据流中获取。仅用于数据传输。
// Send request, receive data // ... SOPAS_UInt16 myArray[10]; // Same size as defined on SOPAS-device bSuccess = rDeserializer.DeserializeArray(*pReceivedData, 10, &myArray[0]); SOPAS_FlexArray<SOPAS_Double, 10> myFlexArray; // 10 is the maximum size bSuccess = rDeserializer.DeserializeArray(*pReceivedData, myFlexArray);
检查FlexArray的占用长度(SOPAS_FlexArray:: uiFlexArrayLength)。所包含的数据是唯一有效直到相关索引。
XBytes(位域)
所述SOPAS类型XByte用于表示位域。由于结构中使用位域是依赖编译器的XByte类型已创建。一个XByte就像符号和无符号位域的结构。这个结构必须由用户来建立。每个元素通过名称访问。
// Assume the following bitfield // { // int x: 3; // unsigned int y: 9; // unsigned int b: 1; // } // // Construct the bitfield: SOPAS_XByte myBitfield; bool bSuccess = myBitfield.AddSignedBitfield("x", 3); bSuccess &= myBitfield.AddUnsignedBitfield("y", 9); bSuccess &= myBitfield.AddUnsignedBitfield("b", 1); if(bSuccess == true) { // Send request, receive data // ... // Use deserializer: bSuccess = rDeserializer.Deserialize(*pReceivedData, myBitfield); if(bSuccess == true) { // Access results // Signed/unsigned must match for the request. This is checked. int x; unsigned int y, b; bSuccess = myBitfield.GetSignedValue("x", x); bSuccess &= myBitfield.GetUnsignedValue("y", y); bSuccess &= myBitfield.GetUnsignedValue("b", b); if(bSuccess == true) { printf("x: %d, y: %u, b: %u\n", x, y, b); // Example output might be: "-1, 257, 0" } } }
异步事件和方法
异步设备可以在请求发出后的任一时刻响应。它们需要在请求之后被轮询,否则TCP/ IP队列可以卡住。注意,事件将到来直到它们不被同意,所以程序需要在主循环轮询。异步方法的答案仅仅需要轮询,直到它们已经接收一次。所需时间取决于所调用的方法。
欲了解更多详情,请参阅CCommunicationHandler。
注意:
如果设置了定义COMMUNICATION_DEBUG然后无效的或意外的答案会在控制台上引起相关消息。
void ProcessEvent(CSopasInterface& rSopas) { // Register callback for event processing rSopas.SetAsyncCallback(&MyCallback); // Subscribe event CProtocol& rProtocol = rSopas.GetProtocol(); rProtocol.SubscribeEvent("my_evt", true); // Process events for 10 seconds rSopas.PollAsyncAnswers(10000); // Unsubscribe event rProtocol.SubscribeEvent("my_evt", false); // Poll some more to remove any event that might have been sent while unsubscribing rProtocol.PollAsyncAnswers(500); }
轮询期间为每个传入事件(已注册)的回调将被调用。在回调你可以处理你所期望的所有注册的事件或异步方法响应。
void MyCallback(const SDecodedAnswer& rAnswer, CBinaryDataStream& rStream, CDeserializer& rDeserializer) { if(rAnswer.eAnswerType == SDecodedAnswer::EVENT_RESULT) { // Process subscribed events if(rAnswer.coName.compare("my_evt") == 0) { // Deserialize event data (assuming just one SOPAS_UInt16 here) SOPAS_UInt16 data; if(rDeserializer.Deserialize(rStream, data) == true) { // Initialize average and display detailed scan information on first run printf("Event data received: %u\n", data); } else { printf("Error during scandata deserialization (event based)\n"); } } } }
另一种方式是由CCommunicationHandler派生实现类中的一个回调函数CCommunicationHandler:: IAsyncCallback。
class MyCallback : public CCommunicationHandler::IAsyncCallback { virtual void AsyncCallback(const SDecodedAnswer&, CBinaryDataStream&, CDeserializer&) { // See example above for a real callback implementation. printf("Callback called"); } }; void ProcessEvent(CSopasInterface& rSopas) { // Register callback for event processing MyCallback callback; rSopas.SetAsyncCallback(&callback); // Subscribe event CProtocol& rProtocol = rSopas.GetProtocol(); rProtocol.SubscribeEvent("my_evt", true); // ... see example above // Make sure that callback class stays in scope longer, or disable callback here: rSopas.SetAsyncCallback(static_cast<CCommunicationHandler::IAsyncCallback*>(0)); }