本页介绍了基于Windows SDK的一个简单SDK应用程序,其基于MFC对话框,包含以下几个主要功能

  • 地图实时获取并绘制在对话框上
  • 手动遥控控制底盘行走(前进,后退,左转,右转,停止)
  • 控制底盘规划路径移动到给定目标点
  • 获得底盘实时状态信息,包括运行状态,电量,当前位置,当前朝向角,SDK/Slamware版本信息

此参考设计尽量去除了与SDK无关的技术细节,例如使用最简单的MFC对话框程序,使用基于定时器的地图绘制,没有引入多线程机制等。

其设计目标是

  • 提供一个Windows应用程序的最小系统给到客户,帮助客户减少学习成本,能够快速上手
  • 去除与SDK无关的技术信息,降低客户在此系统之上引入更多特性的修改成本

此参考例程代码没有经过详细的量产品质测试,因此无法直接用于产品级开发。

此例程仅用于展示如何使用SDK接口的常见接口函数,提供快速开发的参考样例。

直接使用此代码用于最终产品,并由此产生的错误或是故障,不在本司责任范围之内。



本页内容



参考例程下载

SDK_Ref_win32.7z


运行环境准备

  • 软件平台

    • Visual Studio 2010  SP1
    • Slamware Windows SDK:Slamware Windows SDK
    • RoboStudio(用于交叉验证):Robostudio installer 
    • Sample Code: 

      使用更高版本的Visual Studio可能会带来编译异常。

      使用Visual Studio 2010(无SP1)可能会因为无法与.Net Framework兼容而报编译错误,此时增加SP1更新包即可解决问题

  • 硬件平台

          (以下任选其一)

    • Slamware SDP mini 
    • Slamware SDP
    • Zeus/Apollo等底盘系统

      对于首次使用Slamware SDK进行编程的用户来说,不建议在最开始使用基于自己底盘搭载Slamcore模块用于产品开发。此方式无法有效定位问题,即是基于SDK的应用程序问题,还是底盘部分存在故障。强烈建议选择以上列表中的一个用于初始开发。

编译运行

  1. 打开参考工程文件,右键打开属性选项,将Slamware SDK 的include目录和lib目录添加到工程,然后编译工程

    Slamware SDK的include和lib目录无需复制到参考例程目录,只需在Visual Studio里指定路径即可。

    如果不指定此两个路径,编译中会报找不到.h文件,或者找不到库函数等错误。

    编译过程中的warning可以忽略

  2. 连接到底盘wifi(一般以SLAMWARE开头,后面跟着6位MAC地址)


  3. 运行参考程序,会弹出如下对话框,点击连接

    对于wifi连接,AP模式下,底盘默认IP地址为192.168.11.1,如果改为Station模式,则底盘的IP地址由底盘所连接的路由器动态分配,或者由用户指定,此时需使用此IP地址

    对于有线网络连接,IP地址默认为192.168.11.1,不可修改。

  4. 在如下对话框里,左侧是地图显示,随着底盘的移动,地图内容会实时更新。右侧是控制区和状态显示区。

    操作指南
    1. 点击向前,向后,左转,右转,底盘会相应运动,点击停止运动,底盘立即停止

      每点击一次,底盘只运动一小段时间,如果需要保持运动,需要不停点击。例如,如果希望底盘持续前进,需要持续点击向前按钮,一旦停止点击,底盘很快即停止运动

      通过此种方式控制底盘运动时,底盘处于遥控控制模式,底盘不会自动避障,故需留意周围环境,避免撞到人或物。

    2. 输入X,Y坐标后,点击到这去按键,底盘会运动到给定的目标点。X,Y坐标点的获得,可以根据当前位置显示的坐标记录
    3. 最上方是当前位置和机器人状态的实时显示,其中每一次底盘运动,运动状态都会相应改变;每次底盘位置或是朝向角发生改变,当前位置区域也会发生变化
    4. 点击退出按键可以退出程序

程序说明

程序主要分成两个功能模块

  1. 控制底盘行走
    1. 遥控控制底盘行走
      遥控底盘需要使用moveBy接口,接口中传入动作类型指令,其代码主要位于每个按键的点击响应函数中,以“向前”按键为例

      void CSDK_Ref_win32Dlg::OnBnClickedBtnForward()
      {
      	// TODO: Add your control notification handler code here
      	try{
      		rpos::core::Direction direction(rpos::core::ACTION_DIRECTION::FORWARD);
      		moveAction = robot.moveBy(direction);
      	}catch(const ConnectionFailException &e) 
      	{
      		MessageBox(_T("连接异常,程序关闭"), _T("错误"), MB_OK);
      		CDialogEx::OnCancel();
          } catch(const RequestTimeOutException &e) 
      	{
      		MessageBox(_T("请求超时,请重试"), _T("错误"), MB_OK);
          }catch(const std::exception &e)
      	{
      		MessageBox(_T("发生异常,程序关闭"), _T("错误"), MB_OK);
      	}
      }
    2. 自主规划路径行走
      自主规划路径使用moveTo接口,接口中传入目标位置坐标点,其代码位于“到这里”按键的点击响应函数中

      void CSDK_Ref_win32Dlg::OnBnClickedBtnMove()
      {
      	// TODO: Add your control notification handler code here
      	double _x = _tstof(m_dest_x);
      	double _y = _tstof(m_dest_y);
      
      
      	try{
      		rpos::core::Location loc(_x, _y);
      		moveAction = robot.moveTo(loc, false, true);
      	}catch(const ConnectionFailException &e) 
      	{
      		MessageBox(_T("连接异常,程序关闭"), _T("错误"), MB_OK);
      		CDialogEx::OnCancel();
          }catch(const std::exception &e)
      	{
      		MessageBox(_T("发生异常,程序关闭"), _T("错误"), MB_OK);
              CDialogEx::OnCancel();
      	}
      }
  2. 实时更新底盘状态和地图

    实时更新是基于两个定时超时中断的定时器,其中,更新地图的定时器每33毫秒超时中断一次,底盘状态信息的定时器每100毫秒超时中断一次,这两个定时器都在程序初始化时设定

    BOOL CSDK_Ref_win32Dlg::OnInitDialog()
    {
    	CDialogEx::OnInitDialog();
    	......	
    
    	// set timer#1 with interval 100 milisecond for status update
    	SetTimer(1, 100, NULL);
    	// set timer@4 with interval 33 milisecond for map update
    	SetTimer(4, 33, NULL);
    
    	......
    }

    定时器超时后,会进入OnTimer函数中做相应处理,以下是OnTimer的代码。通过判断不同定时器的编号,执行不同的操作

    void CSDK_Ref_win32Dlg::OnTimer(UINT_PTR nIDEvent)
    {
    	if(nIDEvent == 1)
    	{
    		// TODO: Add your message handler code here and/or call default
    		UpdateData(TRUE);
    		try{
    			// update current pose (position + heading angle)
    			rpos::core::Pose pose = robot.getPose();
    			m_info_x = pose.x();
    			m_info_y = pose.y();
    			m_info_yaw = pose.yaw();
    
    			// update battery status
    			m_info_battery = robot.getBatteryPercentage();
    
    			// update action status
    			if(moveAction.isEmpty())
    				m_info_action_status = "Idle";
    			else
    			{
    				switch(moveAction.getStatus())
    				{
    				case ActionStatusWaitingForStart:
    					m_info_action_status = "Waiting For Start";
    					break;
    
    				case ActionStatusRunning:
    					m_info_action_status = "Running";
    					break;
    
    				case ActionStatusFinished:
    					m_info_action_status = "Finished";
    					break;
    
    				case ActionStatusPaused:
    					m_info_action_status = "Paused";
    					break;
    
    				case ActionStatusStopped:
    					m_info_action_status = "Stopped";
    					break;
    
    				case ActionStatusError:
    					m_info_action_status = "Error";
    					break;
    
    				default:
    					m_info_action_status = "Error";
    					break;
    				}
    			}
    		}catch(const ConnectionFailException &e) 
    		{
    			MessageBox(_T("连接异常,程序关闭"), _T("错误"), MB_OK);
    			CDialogEx::OnCancel();
    		}catch(const std::exception &e)
    		{
    			MessageBox(_T("发生异常,程序关闭"), _T("错误"), MB_OK);
    			CDialogEx::OnCancel();
    		}
    		// update the display
    		UpdateData(FALSE);
    	}
    	else
    	{
    		// update map display
    		drawMap();
    	}
    	CDialogEx::OnTimer(nIDEvent);
    }

    地图绘制是在drawMap中完成的,其使用getMapData接口,从Slamcode获得实时的地图数据,该数据是像素点的原始数据(raw data),不存在任何图片格式信息,为了显示在界面上,将其封装成Bitmap图像,之后显示在图形界面上。

    void CSDK_Ref_win32Dlg::drawMap(void)
    {
    	BITMAPINFO _mapDesc ;
    	DWORD* pData ;
    	HBITMAP hBitmap ;
    
    	try{
    		// get map from slamware
    		rpos::core::RectangleF knownArea = robot.getLocationProvider().getKnownArea(MapTypeBitmap8Bit, location_provider::EXPLORERMAP);
    		location_provider::Map map = robot.getMap(MapTypeBitmap8Bit, knownArea, rpos::features::location_provider::EXPLORERMAP);
    		int mapWidth = map.getMapDimension().x();
    		int mapHeight = map.getMapDimension().y();
    
    		// initialize the bitmap header structure
    		memset(&_mapDesc, 0, sizeof(_mapDesc));							// clear BITMAPHEADER
    		_mapDesc.bmiHeader.biSize = sizeof(_mapDesc);					// size of BITMAPHEADER
    		_mapDesc.bmiHeader.biWidth = mapWidth;							// width by pixels
    		_mapDesc.bmiHeader.biHeight = mapHeight;						// height by pixels
    		_mapDesc.bmiHeader.biPlanes = 1;								// 1 plane
    		_mapDesc.bmiHeader.biBitCount = 32;								// 24bit Color
    		_mapDesc.bmiHeader.biCompression = BI_RGB;						// no compression
    		_mapDesc.bmiHeader.biSizeImage = mapHeight * mapWidth;			// size by pixels
    
    		// create DIB image to fill the map data
    		hBitmap = CreateDIBSection (::GetDC(NULL), (BITMAPINFO *) &_mapDesc, DIB_RGB_COLORS, (void**)&pData, NULL, 0);
    		// fill the map data
    		for (int posY = 0; posY < mapHeight; ++posY)
    		{
    			for (int posX = 0; posX < mapWidth; ++posX)
    			{            
    				// get map pixel
    				rpos::system::types::_u8 mapValue_8bit = map.getMapData()[posX + posY * mapWidth] + 127;
    				// fill the bitmap data
    				pData[posX + posY*mapWidth] = RGB(mapValue_8bit,mapValue_8bit,mapValue_8bit);
    			}
    		}
    		// update bitmap map file to picture control component
    		m_pic_map.SetBitmap(hBitmap);
    
    	}catch(const ConnectionFailException &e) 
    	{
    		MessageBox(_T("连接异常,程序关闭"), _T("错误"), MB_OK);
    		CDialogEx::OnCancel();
        }catch(const std::exception &e)
    	{
    		MessageBox(_T("发生异常,程序关闭"), _T("错误"), MB_OK);
            CDialogEx::OnCancel();
    	}
    }

    上面的代码仅用于演示如何操作地图数据并将其显示。

    上述代码可能存在性能较低的问题,包括但不限于每次均重画所有像素数据,如果地图面积很大,会导致刷新速度较慢;每33毫秒重画一次地图,如果drawMap自身所需时间大于33ms,会造成严重的显示问题,等等。

    建议在实际编写应用程序时,需认真考虑地图绘制的时效性以及稳定性。