OpoenVIBE—Unity3D脑机接口实验平台搭建:二、OpenVIBE与Unity3D通信

之前的文章有提到,通过Cykit将OpenVIBE与EPOC+连接。完成设备连接后,就可以参考官方案例实现自己的脑-机接口控制程序了。
我的目标是实现四个指令的SSVEP脑-机接口,官方SSVEP案例场景只实现了三个指令,所以要在这个基础上进行一些修改,案例中的脑电处理场景确实很容易被改为四个分类,但是呈现SSVEP刺激的程序只能自己来实现了。
视觉刺激可以通过很多方法实现,我是选用的Unity来做的。无论用什么方法,视觉刺激程序和OV的通讯是关键问题,因为在离线采集的时候OV是作为逻辑控制中心的。

通过VRPN通信实现OPenVIBE与Unity3D之间的指令传递

OV的中列举了实现数据传输的方法,具体内容可以参考下面的链接,对于指令的传输,官方是推荐使用VRPN的方式,案例中也确实是由这种方式实现,但是目前官方VRPN的相关说明文档好像被删掉了,相关代码我会附在后面,代码需要引入OV源码中VRPN的include和lib,可以根据自己所用版本下载相应的OV源码并编译得到。

OpenVIBE与其他程序数据通信方法:http://openvibe.inria.fr/overview-sending-data-stimulations-out-from-openvibe/
OpenVIBE源码编译说明:http://openvibe.inria.fr/build-instructions/

OpenVIBE提供了VRPN Server和VRPN Client两种工具,可以实现Server向Client的单向传输。也就是说需要在Unity程序中分别实现相应的VRPN Client和VRPN Server功能。结构如下图:
在这里插入图片描述
官方给的VRPN Server和VRPN Client程序是C++语言写的,所以根据需求将其封装成DLL导入到Unity3D中使用,这种方法网上有很多教程,我在这里随便贴几个作为参考。

unity调用c++ dll方法介绍:https://blog.csdn.net/yuhan61659/article/details/80335574
C#调用C++数组,结构体DLL:https://www.cnblogs.com/ye-ming/p/7976986.html
C#调用DLL各种传参:https://www.cnblogs.com/ahuo/p/5457420.html

VRPN Server官方案例代码

参考官方代码,根据自己的需求在程序中实现相应的VRPN Server和VRPN Client功能,从而实现通信。

main.cpp

#include "GenericVRPNServer.h"
#include <iostream>
#include <cmath>
#include <windows.h>

#define DEFAULT_PORT 50555
#define M_PI 3.1415926

using namespace std;
int main(){

	GenericVRPNServer* vrpnService = GenericVRPNServer::getInstance(DEFAULT_PORT);
	const char* buttonDevice = "button_test";
	const char* analogDevice = "analog_test";
	vrpnService->addAnalog(analogDevice,2);
	vrpnService->addButton(buttonDevice, 2);
	
	double time = 0;
	double period = 0;
	while (true)
	{
		if (period >= 2 * M_PI) 
		{
			vrpnService->changeButtonState(buttonDevice, 0, 1 - vrpnService->getButtonState(buttonDevice, 0));
			period = 0;
		}
		vrpnService->changeAnalogState(analogDevice, sin(time), cos(time));
		
		time = time + 0.01;
		period = period + 0.01;
		vrpnService->loop();
		Sleep(10);
	}
	GenericVRPNServer::deleteInstance();
	vrpnService = nullptr;
	return 0;
}

GenericVRPNServer.cpp

#include "GenericVRPNServer.h"

#include <vrpn_Connection.h>
#include <vrpn_Button.h>
#include <vrpn_Analog.h>

#include <cstdarg>

GenericVRPNServer* GenericVRPNServer::serverInstance = nullptr;

GenericVRPNServer::GenericVRPNServer(int port)
{
	_connection = vrpn_create_server_connection(port);
}

GenericVRPNServer::~GenericVRPNServer()
{
	deleteInstance();
}

GenericVRPNServer* GenericVRPNServer::getInstance(int port)
{
	if (serverInstance == NULL)
	{
		serverInstance = new GenericVRPNServer(port);
	}

	return serverInstance;
}

void GenericVRPNServer::deleteInstance(void)
{
	for (std::map<std::string, ButtonServer>::iterator it = serverInstance->_buttonServer.begin(); it != serverInstance->_buttonServer.end(); ++it)
	{
		delete it->second.server;
	}
	serverInstance->_buttonServer.clear();

	for (std::map<std::string, AnalogServer>::iterator it = serverInstance->_analogServer.begin(); it != serverInstance->_analogServer.end(); ++it)
	{
		delete it->second.server;
	}
	serverInstance->_analogServer.clear();

	delete serverInstance;
	serverInstance = nullptr;
}

void GenericVRPNServer::loop()
{
	
	for (std::map<std::string, ButtonServer>::iterator it = _buttonServer.begin(); it != _buttonServer.end(); ++it)
	{
		it->second.server->mainloop();
	}
	

	for (std::map<std::string, AnalogServer>::iterator it = _analogServer.begin(); it != _analogServer.end(); ++it)
	{
		it->second.server->mainloop();
	}

	_connection->mainloop();
}


void GenericVRPNServer::addButton(std::string name, int buttonCount)
{
	ButtonServer serverObject;

	serverObject.server = new vrpn_Button_Server(name.c_str(), _connection, buttonCount);
	serverObject.buttonCount = buttonCount;

	_buttonServer.insert(std::pair<std::string, ButtonServer>(name, serverObject));
	_buttonServer[name].cache.clear();
	_buttonServer[name].cache.resize(buttonCount);
}

void GenericVRPNServer::changeButtonState(std::string name, int index, int state)
{
	_buttonServer[name].server->set_button(index, state);
	_buttonServer[name].cache[index] = state;
}

int GenericVRPNServer::getButtonState(std::string name, int index)
{
	return _buttonServer[name].cache[index];
}

void GenericVRPNServer::addAnalog(std::string name, int channelCount)
{
	AnalogServer serverObject;

	serverObject.server = new vrpn_Analog_Server(name.c_str(), _connection, channelCount);
	serverObject.channelCount = channelCount;

	_analogServer.insert(std::pair<std::string, AnalogServer>(name, serverObject));
}

void GenericVRPNServer::changeAnalogState(std::string name, ...)
{
	double* channels = _analogServer[name].server->channels();

	va_list list;

	va_start(list, name);

	for (int i = 0; i < _analogServer[name].channelCount; i++)
	{
		channels[i] = va_arg(list, double);
	}

	va_end(list);

	_analogServer[name].server->report();
}

double* GenericVRPNServer::getAnalogChannels(std::string name)
{
	return _analogServer[name].server->channels();
}

void GenericVRPNServer::reportAnalogChanges(std::string name)
{
	_analogServer[name].server->report();
}

GenericVRPNServer.h

/**
 * \file GenericVRPNServer.h
 * \author Jozef Legény
 *
 * Copyright : Inria (2012)
 * License : LGPLv2 -> AGPL3
 */

#ifndef __GenericVRPNServer__
#define __GenericVRPNServer__

#include <map>
#include <vector>
#include <string>

class vrpn_Connection;
class vrpn_Button_Server;
class vrpn_Analog_Server;

/**
 * \class GenericVRPNServer
 * \brief A class providing a very simple generic VRPN server capable of creating Analog and Button controls.
 */
class GenericVRPNServer
{

	public:

		struct ButtonServer {
			vrpn_Button_Server* server;
			int buttonCount;
			std::vector<int> cache;
		};

		struct AnalogServer {
			vrpn_Analog_Server* server;
			int channelCount;
		};


		/// Public singleton factory
		static GenericVRPNServer* getInstance(int port);

		static void deleteInstance(void);

		/// Public destructor
		~GenericVRPNServer();

		/// The loop() method has to be called periodically in order for vrpn to work
		void loop();

		/** Creates a new button object within the VRPN server
		 * \param name name of the vrpn peripheral
		 * \param buttonCount number of virtual buttons in the peripeheral
		 */
		void addButton(std::string name, int buttonCount);

		/** Change the button state of a button inside a created VRPN peripheral
		 * \param name name of the vrpn peripheral containing the button
		 * \param index index of the button (beginning by 0)
		 * \param state new state of the button 0 = off, 1 = on
		 */
		void changeButtonState(std::string name, int index, int state);


		/** Get the state of a button
		 * \param name name of the vrpn peripheral containing the button
		 * \param index index of the button (beginning by 0)
		 * \return the state of the button
		 */
		int getButtonState(std::string name, int index);

		/** Creates a new analog object within the VRPN server
		 * \param name name of the vrpn peripheral
		 * \param channelCount number of channels in the peripeheral
		 */
		void addAnalog(std::string name, int channelCount);

		/** Change the state of channels of an analog VRPN peripheral
		 * \param name name of the vrpn peripheral containing the analog control
		 * \param ellipsis list of the values (double)
		 */
		void changeAnalogState(std::string name, ...);


		/** Gets a pointer to the channel array
		 * \param name name of the vrpn peripheral containing the analog control
		 * \return pointer to the array containing the channels
		 */
		double* getAnalogChannels(std::string name);

		/** Marks the selected analog server channels as modified so the values are sent in the next loop
		 * \param name name of the vrpn peripheral containing the analog control
		 */ 
		void reportAnalogChanges(std::string name);

	private:
		static GenericVRPNServer* serverInstance;

		GenericVRPNServer(int port);

		vrpn_Connection* _connection;
		std::map<std::string, ButtonServer> _buttonServer;
		std::map<std::string, AnalogServer> _analogServer;

};

#endif // __GenericVRPNServer__


VRPN Client官方案例代码

#include<iostream>
#include<vrpn_Button.h>
#include<vrpn_Analog.h>



void VRPN_CALLBACK vrpn_button_callback(void* user_data, vrpn_BUTTONCB button)
{
	std::cout << "Button ID : " << button.button << " / Button State : " << button.state << std::endl;

	//if (button.button == 1)
	//{
	//	std::cout << "Quit requested by button press" << std::endl;
	//	*(bool*)user_data = false;
	//}
}

void VRPN_CALLBACK vrpn_analog_callback(void* user_data, vrpn_ANALOGCB analog)
{
	for (int i = 0; i < analog.num_channel; i++)
	{
		std::cout << "Analog Channel : " << i << " / Analog Value : " << analog.channel[i] << std::endl;
	}
}

int main(int argc, char** argv) {

	if (argc != 1 && argc != 3) {
		std::cout << "Usage:\n\n" << argv[0] << " [buttonDevice] [analogDevice]\n";
		return 1;
	}

	//const char* buttonDevice = "openvibe_vrpn_button@localhost";
	const char* buttonDevice = "openvibe_vrpn_button@localhost";
	const char* analogDevice = "openvibe_vrpn_analog@localhost";

	if (argc == 3) {
		buttonDevice = argv[1];
		analogDevice = argv[2];
	}
	std::cout << "Polling these VRPN devices\n  Button: " << buttonDevice << "\n  Analog: " << analogDevice << "\n";

	
	bool running = true;
	//VRPN Btoon Obj
	vrpn_Button_Remote* VRPNButton;
	VRPNButton = new vrpn_Button_Remote(buttonDevice);
	VRPNButton->register_change_handler(&running, vrpn_button_callback);

	//VRPN Analog Obj
	vrpn_Analog_Remote* VRPNAnalog;
	VRPNAnalog = new vrpn_Analog_Remote(analogDevice);
	VRPNAnalog->register_change_handler(NULL, vrpn_analog_callback);

	while (running)
	{
		VRPNButton->mainloop();
	}

	VRPNAnalog->unregister_change_handler(NULL, vrpn_analog_callback);
	VRPNButton->unregister_change_handler(&running, vrpn_button_callback);
	

	delete VRPNButton;
	delete VRPNAnalog;

	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_15017307/article/details/116465449