好记性不如烂笔头。最近需要用VRPN获取设备数据,有些设备不在VRPN现有支持设备列表里,就想着自己改一下源码添加一下。在网上找了一段时间发现包括官网上的教程基本都是教你怎么用的,没找到告诉你怎么改的,所以就自己摸索了一下,简单记录一下,以防以后用到忘了。
鉴于介绍啥是VRPN以及VRPN的基本使用方法的文章已经有了,我在这就不多写了,可以参考这篇博客https://blog.csdn.net/huhaoxuan2010/article/details/77015017
等我用一阵子如果发现有坑的话我再自己写一篇。
1.工程建立与结构树分析
我用的是7.33的版本,在win10环境下用vs2019编译。源码github上就能搜到,获取源码后使用CMAKE构建一下,得到一个VRPN.sln的工程。打开工程,里面的结构如下,看了一遍发现需要更改的代码都在Library的Core VRPN Server Library里,后边的修改都是在这里面。
!注意:我编译的时候出了点问题,首先是提示我有些lib找不到,看了提示后发现是Core VRPN Server Library这个project没有编译通过,我单独编译发现是有部分syntax error,然后我把win10 SDK改成了Win8.1的就好了。
再编译的时候就没有lib的问题了,但是vrpn_server这个project编译不过去,跟之前是同样的问题,我改了SDK也就好了。
2.添加一个设备
想要添加一个设备最简单的方式就是找个相似的然后照着改。为了方便验证,我尝试添加一个自定义的鼠标进去(鼠标所有机子都有,方便验证是否生效,而且鼠标包含了Analog和Button两个类别的输入,比较具有代表性),找了工程里已有的Mouse作为参照。我首先通过在全工程中检索Mouse关键词,找到所有相关的代码,然后筛选出需要修改的相关文件如下:
2.1 Core VRPN Server Library---Header Files---vrpn_Generic_server_object.h文件
这个文件是管理定义设备代码的头文件,需要在其中声明设备的设置函数,以5dt为例,函数名为
int setup_Mouse(char *&pch, char *line, FILE * /*config_file*/);
在下边依样画葫芦,复制这行,改个名字声明我们自己的setup函数。
int setup_MyMouse(char *&pch, char *line, FILE * /*config_file*/);
2.2 Core VRPN Server Library---Source Files---vrpn_Generic_server_object.c文件
这是对应的C文件,需要修改的地方会多一点,
1) 首先:添加我们设备的头文件(先写上,找不到正常,后边我们会添加这些文件),找到Mouse的头文件引用位置
#include "vrpn_Mouse.h" // for vrpn_Button_SerialMouse, etc
复制一个在下边,改成我们的
#include "vrpn_MyMouse.h" // for vrpn_Button_SerialMouse, etc
2) 然后:找到头文件中声明setup函数的具体定义,
int vrpn_Generic_Server_Object::setup_Mouse(char *&pch, char *line,
FILE * /*config_file*/)
{
char s2[LINESIZE];
VRPN_CONFIG_NEXT();
// Get the arguments (class, mouse_name)
if (sscanf(pch, "%511s", s2) != 1) {
fprintf(stderr, "Bad vrpn_Mouse line: %s\n", line);
return -1;
}
// Open the box
if (verbose) {
printf("Opening vrpn_Mouse: %s\n", s2);
}
try {
_devices->add(new vrpn_Mouse(s2, connection));
}
catch (...) {
fprintf(stderr, "could not create vrpn_Mouse\n");
#ifdef linux
fprintf(stderr, "- Is the GPM server running?\n");
fprintf(stderr,
"- Are you running on a linux console (not an xterm)?\n");
#endif
return -1;
}
return 0;
}
复制一份在下边,改成我们的,代码里面因为名称问题报错的内容做相应修改,
int vrpn_Generic_Server_Object::setup_MyMouse(char *&pch, char *line,
FILE * /*config_file*/)
{
char s2[LINESIZE];
VRPN_CONFIG_NEXT();
// Get the arguments (class, Mymouse_name)
if (sscanf(pch, "%511s", s2) != 1) {
fprintf(stderr, "Bad vrpn_MyMouse line: %s\n", line);
return -1;
}
// Open the box
if (verbose) {
printf("Opening vrpn_MyMouse: %s\n", s2);
}
try {
_devices->add(new vrpn_MyMouse(s2, connection));
}
catch (...) {
fprintf(stderr, "could not create vrpn_MyMouse\n");
#ifdef linux
fprintf(stderr, "- Is the GPM server running?\n");
fprintf(stderr,
"- Are you running on a linux console (not an xterm)?\n");
#endif
return -1;
}
return 0;
}
最后,找到下边这行代码,这个代码应该是根据配置文件判断需要激活哪些设备的连接如果被设置为激活状态,就去调用setup函数。前面的字符串就是配置文件里的设备名称。
else if (VRPN_ISIT("vrpn_Mouse")) {VRPN_CHECK(setup_Mouse);}
还是一样,复制粘贴一份,改一下
else if (VRPN_ISIT("vrpn_MyMouse")) {VRPN_CHECK(setup_MyMouse);}
2.3 Core VRPN Server Library---CMakeLists.txt
这是Cmake的编译配置文件,需要添加上我们新建设备的头文件和c文件(后边我们会添加),找到vrpn_Mouse.h和vrpn_Mouse.C在他们下边分别添加vrpn_MyMouse.h和vrpn_MyMouse.C即可。
2.4 Core VRPN Server Library---Header Files---Vrpn_Mouse.h
现在开始正式添加我们的代码,找到Vrpn_Mouse.h文件所在位置,在相同文件夹中复制一份出来改名为Vrpn_MyMouse.h,把文件中所有Mouse都替换为MyMouse,然后在工程中将这个文件添加到VRPN Server Library---Header Files文件夹下。
2.5 Core VRPN Server Library---Source Files---Vrpn_Mouse.c
与2.4中头文件类似,找到Vrpn_Mouse.C文件所在位置,在相同文件夹中复制一份出来改名为Vrpn_MyMouse.C,把文件中所有Mouse都替换为MyMouse,然后在工程中将这个文件添加到Core VRPN Server Library--- Source Files文件夹下。
完成以上5个文件的修改与添加后可以把整个解决方案从新生成一下,如果不报错,我们就成功添加了一个设备,这个设备的名称为MyMouse,使用的是Mouse的逻辑。在server的config文件中添加MyMouse MyMouse0(其中MyMouse是我们在代码里定义的设备类型,MyMouse0相当于这个设备的一个连接实例,客户端连接的时候使用的是MyMouse0),然后使用vrpn_print_devices程序可以使用MyMouse0替代Mouse0读取鼠标信息。
3.代码分析
下边我们开始分析Vrpn_MyMouse.h与Vrpn_MyMouse.C文件中的代码,来实现自定义数据的接入。我们参考一下复制出来的代码,从头文件的定义中发现,里面定义了两个类(名称里加了My,否则编译时会跟Mouse里的定义冲突):
class VRPN_API vrpn_MyMouse;
class VRPN_API vrpn_Button_MySerialMouse;
第一个对应标准的鼠标设备,通过Windows或者Linux的驱动读取信息。第二个是通用串口设备,可以适配通过串口连接的与鼠标数据类似的设备(手柄等)。对照Config文件中的描述,发现vrpn_Mouse以及vrpn_Button_SerialMouse均被定义为了一个独立的Device。所以可以看出,同一个文件中可以定义多个设备,而不必针对每一个设备独立添加一组头文件和c文件。为了简化内容我们把第二个类vrpn_Button_SerialMouse相关的代码(头文件和c文件)都删除,只保留vrpn_Mouse相关的代码。关于代码的解析我就直接写道注释里了,英文部分为原始代码的注释,中文部分是我加的。总体来说,我们只用修改get_report()函数然后把我们通过SDK获取的自定义设备的触发信息按照类别填写到对应的变量里就可以了。改完编译一遍获得新的vrpn_server.exe,就可以正常接收数据了(如果出问题记着看看有没有在config文件中添加设备的名称)。
/* file: vrpn_Mouse.cpp
* author: Mike Weiblen mew@mew.cx 2004-01-14
* copyright: (C) 2003,2004 Michael Weiblen
* license: Released to the Public Domain.
* depends: gpm 1.19.6, VRPN 06_04
* tested on: Linux w/ gcc 2.95.4
* references: http://mew.cx/ http://vrpn.org/
* http://linux.schottelius.org/gpm/
*/
#include <stdio.h> // for NULL, fprintf, printf, etc
#include <string.h> // for strncpy
#include "vrpn_BaseClass.h" // for ::vrpn_TEXT_ERROR
#include "vrpn_MyMouse.h"
#include "vrpn_Serial.h" // for vrpn_open_commport, etc
#if defined(linux) && defined(VRPN_USE_GPM_MOUSE)
#include <gpm.h> // for Gpm_Event, Gpm_Connect, etc
#endif
#if !( defined(_WIN32) && defined(VRPN_USE_WINSOCK_SOCKETS) )
# include <sys/select.h> // for select, FD_ISSET, FD_SET, etc
#endif
#ifdef _WIN32
#include <windows.h>
#pragma comment (lib, "user32.lib")
// Fix sent in by Andrei State to make this compile under Visual Studio 6.0.
// If you need this, you also have to copy multimon.h from the DirectX or
// another Windows SDK into a place where the compiler can find it.
#ifndef SM_XVIRTUALSCREEN
#define COMPILE_MULTIMON_STUBS
#include "multimon.h"
#endif
#endif
///
vrpn_MyMouse::vrpn_MyMouse( const char* name, vrpn_Connection * cxn ) :
vrpn_Analog( name, cxn ),
vrpn_Button_Filter( name, cxn )
{
int i;
// initialize the vrpn_Analog
vrpn_Analog::num_channel = 2;//定义模拟量输出为两个通道,对应鼠标位置的x和y
for( i = 0; i < vrpn_Analog::num_channel; i++) {
vrpn_Analog::channel[i] = vrpn_Analog::last[i] = 0;
}//将数值初始化,last应该是记录的上一次的数值,用来和当前数据对比计算变化,vrpn_print_device应该是用了这俩数判断数据是否发生变化,发生变化后将新的数据打印出来。
// initialize the vrpn_Button_Filter
vrpn_Button_Filter::num_buttons = 3;//定义三个按钮,分别对应鼠标的左键、中键以及右键
for( i = 0; i < vrpn_Button_Filter::num_buttons; i++) {
vrpn_Button_Filter::buttons[i] = vrpn_Button_Filter::lastbuttons[i] = 0;
}//和上边一样
//Linux相关代码,利用GPM读取数据
#if defined(linux) && defined(VRPN_USE_GPM_MOUSE)
// attempt to connect to the GPM server
gpm_zerobased = 1;
gpm_visiblepointer = 1;
Gpm_Connect gc;
gc.eventMask = ~0;
gc.defaultMask = GPM_MOVE | GPM_HARD;
gc.maxMod = 0;
gc.minMod = 0;
if( Gpm_Open( &gc, 0 ) < 0 )
{
// either GPM server is not running, or we're trying to run
// on an xterm.
throw GpmOpenFailure();
}
set_alerts( 1 );
#elif defined(_WIN32)
// Nothing needs to be opened under Windows; we just make direct
// calls below to find the values.
#else
fprintf(stderr,"vrpn_Mouse::vrpn_Mouse() Not implement on this architecture\n");
#endif
}
///
vrpn_MyMouse::~vrpn_MyMouse()
{
#if defined(linux) && defined(VRPN_USE_GPM_MOUSE)
Gpm_Close();
#endif
}
///
//循环函数,服务起来后就是不断执行里面的两个函数
void vrpn_MyMouse::mainloop()
{
get_report();//获取设备数据,自定义设备时需要修改的代码!!!!!!
server_mainloop();//响应客户端查询请求,告知客户端服务器仍在线,在mainloop中必须添加
}
///
//获取设备数据的代码
int vrpn_MyMouse::get_report()
{
//Linunx的代码
#if defined(linux) && defined(VRPN_USE_GPM_MOUSE)
fd_set readset;
FD_ZERO( &readset );
FD_SET( gpm_fd, &readset );
struct timeval timeout = { 0, 0 };
select( gpm_fd+1, &readset, NULL, NULL, &timeout );
if( ! FD_ISSET( gpm_fd, &readset ) )
return 0;
Gpm_Event evt;
if( Gpm_GetEvent( &evt ) <= 0 )
return 0;
if( evt.type & GPM_UP )
{
if( evt.buttons & GPM_B_LEFT ) buttons[0] = 0;
if( evt.buttons & GPM_B_MIDDLE ) buttons[1] = 0;
if( evt.buttons & GPM_B_RIGHT ) buttons[2] = 0;
}
else
{
buttons[0] = (evt.buttons & GPM_B_LEFT) ? 1 : 0;
buttons[1] = (evt.buttons & GPM_B_MIDDLE) ? 1 : 0;
buttons[2] = (evt.buttons & GPM_B_RIGHT) ? 1 : 0;
}
channel[0] = (vrpn_float64) evt.dx / gpm_mx;
channel[1] = (vrpn_float64) evt.dy / gpm_my;
return 1;
#elif defined(_WIN32)
//设置鼠标按键的代码,应该是系统中预设的
const unsigned LEFT_MOUSE_BUTTON = 0x01;
const unsigned RIGHT_MOUSE_BUTTON = 0x02;
const unsigned MIDDLE_MOUSE_BUTTON = 0x04;
// Find out if the mouse buttons are pressed.
//调用SDK获取鼠标按键状态并写入buttons变量。注意,要是想自己添加按键类设备,就是用设备的SDK获取按键状态,然后写入vrpn_Button::buttons这个变量就行了
if (0x80000 & GetKeyState(LEFT_MOUSE_BUTTON)) {
vrpn_Button::buttons[0] = 1;
} else {
vrpn_Button::buttons[0] = 0;
}
if (0x80000 & GetKeyState(MIDDLE_MOUSE_BUTTON)) {
vrpn_Button::buttons[1] = 1;
} else {
vrpn_Button::buttons[1] = 0;
}
if (0x80000 & GetKeyState(RIGHT_MOUSE_BUTTON)) {
vrpn_Button::buttons[2] = 1;
} else {
vrpn_Button::buttons[2] = 0;
}
// Find the position of the cursor in X,Y with range 0..1 across the screen
POINT curPos;
GetCursorPos(&curPos);
//对于模拟量也是一样,利用设备SDK获取以后填入vrpn_Analog::channel即可
vrpn_Analog::channel[0] = (vrpn_float64)(curPos.x - GetSystemMetrics(SM_XVIRTUALSCREEN)) / GetSystemMetrics(SM_CXVIRTUALSCREEN);
vrpn_Analog::channel[1] = (vrpn_float64)(curPos.y - GetSystemMetrics(SM_YVIRTUALSCREEN)) / GetSystemMetrics(SM_CYVIRTUALSCREEN);
vrpn_gettimeofday( ×tamp, NULL );//获取时间戳
report_changes();
return 1;
#else
return 0;
#endif
}
///
//当数据变化时发送
void vrpn_MyMouse::report_changes( vrpn_uint32 class_of_service )
{
vrpn_Analog::timestamp = timestamp;
vrpn_Button_Filter::timestamp = timestamp;
vrpn_Analog::report_changes( class_of_service );//变化时输出,用这个就只有移动鼠标时接到输出
vrpn_Button_Filter::report_changes();
}
///
//不论数据是否变化都发送
void vrpn_MyMouse::report( vrpn_uint32 class_of_service )
{
vrpn_Analog::timestamp = timestamp;
vrpn_Button_Filter::timestamp = timestamp;
vrpn_Analog::report( class_of_service );//不论变化与否都输出,用这个就会一直接收到鼠标的位置信息
vrpn_Button_Filter::report_changes();
}
4. 小结
通过这个例子基本就清楚Analog以及Button类的设备应该怎么自定义添加了,从零添加一个设备基本分为以下几步:
- 找到设备的SDK,实现使用自己的C/C++代码获取设备的数据
- 在Core VRPN Server Library中添加一个设备(建议按照设备数据类型找个相似的复制一份改)
- 修改get_report()代码,将获取的数据填写到对应的变量中(我目前就试了Button和Analog,比较复杂的Tracker还没有测试,也许有其他可调的参数或者需要修改的函数,请酌情修改)
- 重新编译生成新的vrpn_server.exe,在Config文件中添加对应设备
后边我会继续测试Tracker的使用,搞定以后再更新。