User Tutorial:BCI2000Remote
Description
BCI2000Remote is a proxy interface class to the BCI2000 Operator module, and allows to start up, configure, and control BCI2000 from other applications. Internally, it maintains a telnet connection to the Operator module, and sends Operator Scripting commands to control it. However, no knowledge of these scripting commands is required in order to use the BCI2000Remote class from your own application.
BCI2000Remote is most useful when writing applications in C++, or in another language for which bindings to the BCI2000RemoteLib library exist, such as Python, or MATLAB.
Note
BCI2000Remote is a means of interfacing with the BCI2000 Operator module meaning that there are potentially hundreds of ways we leverage this tool to have a meaningful interface with BCI2000. Because of this we will illustrate a few different integrations using different languages. This "How-to" will be separated into three separate sections: Python, C++, and Matlab.
Python Tutorial
- Find your Python folder. If you have an existing Python environment with your code find it using the Sys Library like this:
import sys locate_python = sys.exec_prefix print(locate_python)
-
Locate your BCI2000Remote.py file which should have compiled into your prog folder.
Find where in your Python code you wish to integrate BCI2000. In this example I will show the minimum requirements for integration with BCI2000:
# Set BCI2000 path import sys, time sys.path.append('C:\\BCI2000.x64\\prog') # BCI2000 setup import BCI2000Remote bci = BCI2000Remote.BCI2000Remote() bci.WindowVisible = True bci.WindowTitle = 'BCI2000 Remote Python' bci.Connect() bci.Execute('cd ${BCI2000LAUNCHDIR}') bci.Execute('ADD EVENT Square 2 0') bci.StartupModules(('SignalGenerator', 'DummySignalprocessing', 'DummyApplication')) bci.Execute('Wait for Connected') bci.LoadParametersRemote('..\\parms\\fragments\\amplifiers\\SignalGenerator.prm') bci.Execute('Wait for Running') bci.Execute('Set event Square 1')
Here is how you import BCI2000Remote:
Here is how you instantiate the operator object:
Here is how to customize the operator and connect to it:
Here is how you add your states:
How to specify your signal source, signal processing, and application modules:
How to load a parameter file(you can learn more about parameters here):
How to connect and issue commands to BCI2000:
-
And finally, how to tell BCI2000 to set states to a value during runtime:
Now that you've added those components to your code all you have to do is run your code. You should see the BCI2000 operator along with the system log, source watcher, and the timing window.
C++ Tutorial
- Open the C++ file or solution you would like to integrate with BCI2000 in Visual Studio, and find where it is located on your computer.
-
Navigate to the program's properties, and make the following changes:
- Under Configuration Properties -> C++ -> General, add the following folders from BCI2000 (64-bit)'s src folder: \core\Operator\BCI2000Remote, \shared\utils\Lib, \shared\config.
- Navigate to Configuration Properties -> Linker -> Input, and add ws2_32.lib to Additional Dependencies.
- Under Configuration Properties -> C++ -> Advanced, add 4996 to the Disable Specific Warnings section.
- Add your Operator executable and the BCI2000RemoteLib dynamic library to the same directory as the program you intend to integrate. This can be found in the prog folder.
- Also, add the following files to the directory with your program and Operator.exe:
- BCI2000Connection.cpp
- BCI2000Connection.h
- BCI2000Remote.cpp
- BCI2000Remote.h
- BCI2000RemoteLib.cpp
- BCI2000RemoteLib.h
- SelfPipe.cpp
- SelfPipe.h
- sockstream.cpp
- sockstream.h
-
Make sure that you include all of your dependencies including BCI2000Remote.h
#include "BCI2000Remote.h" #include <string> #include <vector> #include <iostream>
-
Next, instantiate the BCI2000Remote object
int main( int argc, char* argv[] ) { // Instantiate a BCI2000Remote object BCI2000Remote bci; // Assume that Operator executable resides in the same directory as this program. std::string path = ( argc > 0 ) ? argv[0] : ""; size_t pos = path.find_last_of( "\\/" ); path = ( pos != std::string::npos ) ? path.substr( 0, pos + 1 ) : ""; // Start the Operator module, and connect bci.OperatorPath( path + "Operator" ); if( !bci.Connect() ) { std::cerr << bci.Result(); return -1; }
-
Specify your startup modules and any flags you wish to start them with
// Startup modules std::vector<std::string> modules{ "SignalGenerator --LogMouse=1", "ARSignalProcessing", "CursorTask" }; if( !bci.StartupModules( modules ) ) { std::cerr << bci.Result(); return -1; }
-
Load any parameter files and set subject information
bci.LoadParametersRemote( "../parms/examples/CursorTask_SignalGenerator.prm" ); bci.SubjectID( "SUB" ); // Start a run if( !bci.Start() ) { std::cerr << bci.Result(); return -1; }
-
And here we just have BCI2000 send us back a feedback signal:
std::string state; while( bci.GetSystemState( state ) && state == "Running" ) { double value = 0; bci.GetControlSignal( 1, 1, value ); std::cout << "Control signal: " << value << ", press Enter to proceed" << std::flush; std::string line; std::getline( std::cin, line ); } return 0; }
- Now that you've added those components to your code all you have to do is compile and run it. You should see the BCI2000 operator along with the system log, source watcher, and the timing window. Note: Be sure to compile this in your prog folder. This can be done by navigating again to Configuration Properties -> General, and changing the Output Directory to the path of the BCI2000 prog folder.
#include "BCI2000Remote.h"
#include <string>
#include <vector>
#include <iostream>
int main( int argc, char* argv[] )
{
// Instantiate a BCI2000Remote object
BCI2000Remote bci;
// Assume that Operator executable resides in the same directory as this program.
std::string path = ( argc > 0 ) ? argv[0] : "";
size_t pos = path.find_last_of( "\\/" );
path = ( pos != std::string::npos ) ? path.substr( 0, pos + 1 ) : "";
// Start the Operator module, and connect
bci.OperatorPath( path + "Operator" );
if( !bci.Connect() )
{
std::cerr << bci.Result();
return -1;
}
// Startup modules
std::vector<std::string> modules{ "SignalGenerator --LogMouse=1", "ARSignalProcessing", "CursorTask" };
if( !bci.StartupModules( modules ) )
{
std::cerr << bci.Result();
return -1;
}
// Load a parameter file, and set subject information
bci.LoadParametersRemote( "../parms/examples/CursorTask_SignalGenerator.prm" );
bci.SubjectID( "SUB" );
// Start a run
if( !bci.Start() )
{
std::cerr << bci.Result();
return -1;
}
// Print feedback signal
std::string state;
while( bci.GetSystemState( state ) && state == "Running" )
{
double value = 0;
bci.GetControlSignal( 1, 1, value );
std::cout << "Control signal: " << value << ", press Enter to proceed" << std::flush;
std::string line;
std::getline( std::cin, line );
}
return 0;
}
MatlabTutorial
- Open the Matlab file you would like to integrate with BCI2000.
- Locate the BCI2000RemoteLib file in the BCI2000 prog folder, and BCI2000RemoteLib.h in src\core\Operator\BCI2000Remote, and load these into the Matlab file. Modify the file paths in loadlibrary(...) as needed.
Note that BCI2000RemoteLib's file name has a "64" appended when it was built in 64 bit mode, and a "32" if built iin 32 bit mode.
In this tutorial, we will be using the 64 bit version.
%% c library load, initial part BCI2000root = 'C:\bci2000.x64'; if not(libisloaded('bci')) loadlibrary(fullfile(BCI2000root,'prog','BCI2000RemoteLib64'),... fullfile(BCI2000root,'src','core','Operator','BCI2000Remote','BCI2000RemoteLib.h'), 'alias', 'bci') end libfunctions('bci')
- Locate Operator.exe in the prog folder. Recover the memory, change the directory, and make the window visible in BCI2000. Again, modify the file path if it differs.
%need to call BCI2000Remote_Delete to recover the memory bciHandle = calllib('bci', 'BCI2000Remote_New'); calllib('bci', 'BCI2000Remote_SetOperatorPath', bciHandle, fullfile(BCI2000root,'prog','Operator')); % if we fail cto establish a connection to BCI2000Remote if calllib('bci', 'BCI2000Remote_Connect', bciHandle) ~= 1 fprintf('bci connect fail!') calllib('bci', 'BCI2000Remote_Delete', bciHandle); % call BCI2000Remote_Delete to recovery the memory return end % Startup BCI2000 calllib('bci', 'BCI2000Remote_Execute', bciHandle,'Change directory $BCI2000LAUNCHDIR', 0); calllib('bci', 'BCI2000Remote_Execute', bciHandle,'Show window; Set title ${Extract file base $0}', 0); calllib('bci', 'BCI2000Remote_Execute', bciHandle,'Reset system', 0);
- Add new states, events, or parameters before starting up the system. Note that it is greatly preferred to use events instead of states because of timing precision.
% Create new parameter (must be done before startup) calllib('bci', 'BCI2000Remote_Execute', bciHandle, 'Add Parameter Application:TestParameterField string TestParameter= FirstNewParameter % % %', 0); % Define events % Define new event with 1 bit of information (2 colors) calllib('bci', 'BCI2000Remote_Execute', bciHandle, 'add event Square 1 0', 0);
- Add in the proper modules (Signal Generator, Signal Processor, and Application). Refer to the documentation Technical_Reference:BCI2000Remote_Library for more information on BCI2000 remote functions. Also refer to the example Matlab code under Programming_Reference:BCI2000Remote_Class#Matlab_Example.
- Load parameter files, modify parameters directly, and add watches after BCI2000 is connected. It can also be helpful to wait for bci2000 to start running (collecting data) before proceeding.
% Load parameter files calllib('bci', 'BCI2000Remote_LoadParametersRemote', bciHandle, fullfile(BCI2000root,'parms','fragments','amplifiers','SignalGenerator.prm')); % Set parameter values calllib('bci', 'BCI2000Remote_SetDataDirectory', bciHandle, fullfile(BCI2000root,'data','BJH')); %Set watches to appear automatically calllib('bci', 'BCI2000Remote_Execute', bciHandle, 'visualize watch Square', 0); % Wait for the user to click SetConfig and Start calllib('bci', 'BCI2000Remote_Execute', bciHandle, 'Wait for Running', 0);
- While BCI2000 is running you can also get and set event and state values, and also get parameter values. Note that you cannot modify parameter values while BCI2000 is running.
% Get parameter value SamplingRate = calllib('bci', 'BCI2000Remote_GetParameter', bciHandle, 'SamplingRate'); fprintf(['\nSamplingRate: ' num2str(SamplingRate) 'Hz\n']) % Get event value [~,~,~,Square]=calllib('bci', 'BCI2000Remote_GetEventVariable', bciHandle, 'Square', 0.0); % BCI2000 set white rectangle to 1 (true) calllib('bci', 'BCI2000Remote_Execute', bciHandle, 'Set event Square 1', 0); % Get state value % Immediately after data block has been acquired from hardware, the % DataIOFilter writes a 16-bit millisecond-resolution time stamp into the % SourceTime state. Block duration is measured as the difference between % two consecutive time stamps. [~,~,~,SourceTime]=calllib('bci', 'BCI2000Remote_GetStateVariable', bciHandle, 'SourceTime', 0.0); fprintf(['\nSourceTime: ', num2str(SourceTime), '\n']) % Stop running BCI2000 calllib('bci', 'BCI2000Remote_SetStateVariable', bciHandle, 'Running', 0);
% Startup system localhost
calllib('bci', 'BCI2000Remote_Execute', bciHandle,'Startup system localhost', 0);
% Establish connection to three modules
SourceModule = 'SignalGenerator';
modules = libpointer('stringPtrPtr', {[SourceModule ' --local --LogKeyboard=1'], 'DummySignalProcessing', 'DummyApplication'});
calllib('bci', 'BCI2000Remote_StartupModules2', bciHandle, modules, 3);
% Wait for connected before loading parameters!
calllib('bci', 'BCI2000Remote_Execute', bciHandle, 'Wait for Connected', 0);
Video
Also see the PsychoPy page for more details on the installation process, APIs, and hooks.








