Programming Reference:BCI2000Remote Class: Difference between revisions

From BCI2000 Wiki
Jump to navigation Jump to search
No edit summary
Line 219: Line 219:
calllib('bci', 'BCI2000Remote_SetOperatorPath', bciHandle,'C:/BCI2000.x64/prog/Operator');
calllib('bci', 'BCI2000Remote_SetOperatorPath', bciHandle,'C:/BCI2000.x64/prog/Operator');
if calllib('bci', 'BCI2000Remote_Connect', bciHandle) ~= 1
if calllib('bci', 'BCI2000Remote_Connect', bciHandle) ~= 1
     fprintf('bci connect fail!')
     fprintf('Could not connect to BCI2000, aborting.')
     calllib('bci', 'BCI2000Remote_Delete', bciHandle);
     calllib('bci', 'BCI2000Remote_Delete', bciHandle);
     return
     return
Line 246: Line 246:
     pause(1);
     pause(1);
end
end
calllib('bci', 'BCI2000Remote_Delete', bciHandle);
</pre>
</pre>



Revision as of 13:37, 8 March 2022

Synopsis

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 through the loadlibrary command, specifying src/core/Operator/BCI2000Remote/BCI2000RemoteLib.h as the header file associated with the library.

For controlling BCI2000 under Windows from MATLAB, Windows Automation compatible scripting languages, or .NET languages, there exists a separate automation interface. For controlling BCI2000 from shell scripts (such as the Windows command interpreter, or Unix shells like bash), there exists a BCI2000 command executable and the BCI2000Shell.

Location

BCI2000/src/core/Operator/BCI2000Remote

Functional Description

After instantiating a BCI2000Remote object, set its OperatorPath property to point to the desired BCI2000 Operator module. Then, call its Connect() method to start up that module, and connect to it. You may then use the StartupModules() method in order to start up BCI2000 core modules.

Alternatively, you may specify the path to a BCI2000 batch file in the BCI2000/batch directory. An appropriate batch file should start up Operator and core modules, and needs to forward its command line arguments to the Operator module. All batch files coming with BCI2000 fulfill this condition. When a batch file is specified, there is no need to call the StartupModules() method.

As yet another option, you may connect to an Operator module that is already running, e.g., on a remote machine on the network. In this case, set the BCI2000Remote object's OperatorPath property to an empty string, and its TelnetAddress property to the address that the remote Operator module is listening on, i.e. the remote machine's IP address, followed with a colon, and the port specified when starting up the remote Operator module. The remote Operator module must have been started with the "--Telnet" command line option, followed with the address to listen on. On the remote machine, this would typically be "localhost:3999". Don't use any of the ports between 4000 and 4002, as this will interfere with the communication between the Operator module, and BCI2000 core modules. Depending on whether the remote Operator module has been started up together with its core modules, or not, you may then call the StartupModules() method in order to start up BCI2000 core modules on the remote machine.

Once all BCI2000 modules are running, you may load parameter files locally or remotely using the LoadParametersLocal() and LoadParametersRemote() methods. Subject ID, Session ID, and data directory are set using the respective BCI2000Remote properties.

A recording may then be started by calling the Start() method. Depending on configuration, the recording will terminate automatically, or needs to be terminated by a call to the Stop() method.

To run BCI2000 in a different configuration (i.e., with different core modules), it is not necessary to terminate the Operator module. Rather, you may execute StartupModules() multiple times in order to terminate currently running core modules, and start different ones.

Once you are done using BCI2000, you may close the connection by calling the Disconnect() method, or by deleting the BCI2000Remote object. Depending on whether the Operator module was started up by the previous Connect() call, this will terminate the Operator module as well. When connection was made to an already running Operator module, local or remote, the connection will be closed but the Operator module will remain running.

BCI2000Remote Members

The BCI2000Remote class has the following members:

Properties

double Timeout=5 (rw)

Timeout in seconds. Applies when establishing communication between BCI2000Remote and BCI2000.

string OperatorPath="" (rw)

Path to Operator module, or batch file. When empty, BCI2000Remote tries to connect to an already running Operator module which may be on a different machine.

string TelnetAddress="localhost:3999" (rw)

Telnet address to use for connection. Defaults to localhost:3999. When an operator path is specified, this must be a local address which will be used for communication. To connect to a remote machine, specify the machine's IP address, followed with BCI2000's listening port. You should take care to avoid specifying a port that is used by BCI2000's internal connections between Operator and core modules. By default, these are 4000, 4001, and 4002.

bool WindowVisible=2 (rw)

Visibility of the BCI2000 main window. 0: Invisible, 1: visible, 2: don't change.

string WindowTitle (rw)

The title of the BCI2000 main window.

string Result (r)

Text output of the previously executed command, or an error message when the previously executed command failed.

Recording information

string SubjectID (rw)

The subject ID for the next recording. When this property is empty, the subject ID is taken from the current value of the SubjectName parameter.

string SessionID (rw)

The session ID for the next recording. When this property is empty, the session ID is taken from the SubjectSession parameter.

string DataDirectory (rw)

The data directory for the next recording. When a relative path is given, it is taken relative to BCI2000/data. When this property is empty, the data directory is taken from the DataDirectory parameter.

Connection methods

bool Connect()

Connect to BCI2000. When OperatorPath is not empty, and no operator module is listening at TelnetAddress, this starts up the operator module before connecting. Will return true on success.

bool Connect( BCI2000Remote )

Connects to the same Operator module as the BCI2000Remote instance given as an argument. This will not start BCI2000 when it is not running, thus it is necessary that the argument instance's Connect() method has been called before.

bool Disconnect()

Disconnect from an existing connection. Terminates the running operator module if it was started by the previous Connect() call.

Control of operation

bool StartupModules( vector(string) modules )

Start BCI2000 core modules, listed by executable name, including possible command line arguments. Each entry in the array corresponds to a single core module, given relative to the Operator module's working directory, which defaults to BCI2000/prog. You do not need to call this function if the OperatorPath property specifies a batch file rather than the Operator module itself.

bool SetConfig()

Applies the current set of parameters. Will be called from Start() automatically. Returns false if parameters could not be set, e.g. because the system was in running state, or because there was a preflight error when applying parameters. Use the Result property to obtain more error information.

bool Start()

Starts a new run (recording) with current parameters. Returns false if the run could not be started. Use the Result property to obtain more error information.

bool Stop()

Stops the current run (recording).

Parameters and system state

bool GetParameter( string name, string& value )

Copies the specified parameter's value into the value output argument. Returns false if parameter does not exist.

bool SetParameter( string name, string value )

Sets the specified parameter to the given value. The parameter must exist in the system. To add a parameter, use the Execute() method in conjunction with the ADD PARAMETER scripting command.

bool LoadParametersLocal( string file )

Load parameters from a file, relative to the working directory of the calling application. This function returns false if the specified file was not found but will not provide information whether parameters were sent successfully. Rather, illegal parameter lines will be silently ignored.

bool LoadParametersRemote( string file )

Load parameters from a file, relative to the working directory of the Operator module connected to. Unless explicitly changed, that working directory is the BCI2000/prog directory in which the Operator module resides.

bool AddStateVariable( string name, int bitWidth, double initialValue )

Adds a state variable to the system. This is only possible before core modules have connected to the Operator module.

bool GetStateVariable( string name, double& value )

Gets the value of a named BCI2000 state variable. If no error occurred, the value will be copied into the "value" output variable.

bool SetStateVariable( string name, double value )

Sets the value of the named BCI2000 state variable to the given value. Returns false on error.

bool GetSystemState( string& state )

Gets the current system state (i.e., state of operation). If no error occurred, the "state" output variable will contain the name of the current system state, which will be one of the names listed at User Reference:Operator Module Scripting#GET_SYSTEM_STATE.

bool GetControlSignal( int channel, int element, double& value )

Copies the value of the control signal at the given indices into the "value" output variable. Indices are 1-based. Typically, the control signal has only a single element, and the number of output channels corresponds to the number of feedback dimensions in feedback experiments, or is 1 in evoked potential experiments.

Scripting methods

bool Execute( string command, int* exitCode = 0 )

Execute an arbitrary BCI2000 scripting command. A return value of "true" does not indicate successful execution of the command; rather, it indicates that the command was sent, and its result was received. In general, the Result property needs to be examined in order to determine whether the command was executed successfully.

In many cases, the optional exitCode argument may be specified to obtain the result status of a command in an integer variable that the exitCode pointer points to. The exact meaning of the exit code depends on the command that has been executed, so it is necessary to be aware of a command's possible responses before evaluating an exit code.

In detail, the exit code is determined as follows:

  • When Execute() is used to execute a SYSTEM command, the exit code will match the exit code of the program that was run through the SYSTEM command. By convention, such exit codes are nonzero on failure, and zero on success.
  • For other commands, the exit code is derived from the Result property as follows:
    • If Result may be interpreted as a single floating point number, the exit code will be 0 for a nonzero number, and 1 if the number is zero.
    • If Result is "true" or "false" (without considering case), the exit code will be 0 for "true", and 1 for "false".
    • If Result is empty, the exit code will be 0.
    • If Result does not match any of the previous conditions, the exit code will be -1 to indicate that there was no obvious interpretation.

This scheme is chosen to simplify client code for the most common cases:

  • external programs run through the SYSTEM command,
  • commands which do not have an output except in case of failure,
  • functions returning binary values, such as the test command,
  • expressions evaluated as logical conditions.

In all these cases, the exit code will be 0 on success, and 1 on failure.

When executing a command that may return an arbitrary string (e.g., EXECUTE SCRIPT, or ECHO), the exit code should not be used. In this case, the exitCode argument may be omitted, or may be NULL.

A full documentation of BCI2000 Operator scripting commands is available under User Reference:Operator Module Scripting.

bool SetScript( string handlers, string script )

Associate a sequence of BCI2000 scripting commands with the specified events. Events are given by name, as defined in the scripting reference. Multiple handler names may be specified, concatenated with a pipe '|' character.

bool GetScript( string handler, string& script )

Copy the handler script into the script output variable.

Watches

Watch* NewWatch( string expression, WatchCallback, void* callbackData )

Creates a new watch object that watches on the expression or expressions given, and calls the specified callback each time the expression value changes. CallbackData is arbitrary user data (e.g., a pointer to an object) required for processing the callback. If multiple expressions are given, they must be separated by tab characters.

The watch object may be disposed of by calling an ordinary delete.

void WatchCallback( void* callbackData, const char* values )

Signature of a user-defined callback function that receives expression values, along with the arbitrary user data stored in callbackData. The values string starts with a two-digit packet count, followed with one value for each expression, and a trailing CRLF. Values are separated by tab characters.

Dependencies

To facilitate the use of BCI2000Remote, it only depends on very few files from the BCI2000 framework. To use BCI2000Remote in a C++ project, you will need to add the following cpp files to the project, and their parent directories to the list of include paths:

BCI2000/src/core/Operator/BCI2000Remote/BCI2000Remote.cpp
BCI2000/src/core/Operator/BCI2000Remote/BCI2000Connection.cpp
BCI2000/src/shared/utils/Lib/sockstream.cpp
BCI2000/src/shared/utils/Lib/selfpipe.cpp

Also, on Windows you will need to link against this additional library:

WS2_32

BCI2000RemoteLib

The BCI2000RemoteLib is a dynamic library that exports a C-style wrapper around the BCI2000Remote class, such that it may be used from scripting languages that support execution of library code. A C-style wrapper is required because no stable conventions exist regarding the export of classes and objects from dynamic libraries.

BCI2000Remote member functions are directly mapped on C functions by adding an object handle as a first argument, and by replacing std::string arguments with C string arguments. To instantiate a BCI2000Remote object, and obtain an object handle, call BCI2000Remote_New(); specify this handle as a first argument to any BCI2000Remote member function you would like to call, and finally delete the object by specifying its handle as an argument to BCI2000Remote_Delete(). An object handle should always be treated as an opaque reference, and never be cast to a pointer of type BCI2000Remote*.

Similarly, BCI20000Remote_NewWatch() returns a reference to a watch object as a void*. Once you are done with the watch object, call BCI2000Remote_DeleteWatch() to dispose of it.

Any function that returns a C string as a const char* may return a zero pointer, indicating failure, or a valid pointer to a C string, indicating success. If a valid pointer is returned, the associated memory has been allocated by the library, and locked. To avoid memory leaks, a caller of such a function must use BCI2000Release() to indicate that it will no longer assume a pointer to be valid. Calling BCI2000Release() with a NULL argument is allowed, and will not do anything.

C++ Example

#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
  const char* modules[] = { "SignalGenerator --LogMouse=1", "ARSignalProcessing", "CursorTask" };
  std::vector<std::string> vModules( &modules[0], &modules[0] + sizeof( modules ) / sizeof( *modules ) );
  if( !bci.StartupModules( vModules ) )
  {
    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;
}

Matlab Example

%% c library load, initial part
clc;clear;
if not(libisloaded('bci'))
    loadlibrary('C:\BCI2000.x64\prog\BCI2000RemoteLib','C:\BCI2000.x64\src\core\Operator\BCI2000Remote\BCI2000RemoteLib.h', 'alias', 'bci')
end
libfunctions('bci')
%need to call BCI2000Remote_Delete to recover the memory
bciHandle = calllib('bci', 'BCI2000Remote_New');
calllib('bci', 'BCI2000Remote_SetOperatorPath', bciHandle,'C:/BCI2000.x64/prog/Operator');
if calllib('bci', 'BCI2000Remote_Connect', bciHandle) ~= 1
    fprintf('Could not connect to BCI2000, aborting.')
    calllib('bci', 'BCI2000Remote_Delete', bciHandle);
    return
end
calllib('bci', 'BCI2000Remote_Execute', bciHandle,'Change directory $BCI2000LAUNCHDIR',0);

calllib('bci', 'BCI2000Remote_SetWindowVisible', bciHandle,1);
modules = libpointer('stringPtrPtr', {'SignalGenerator', 'SpectralSignalProcessing', 'CursorTask'});
calllib('bci', 'BCI2000Remote_StartupModules2', bciHandle, modules, 3);
calllib('bci', 'BCI2000Remote_LoadParametersRemote', bciHandle, '../parms/examples/CursorTask_SignalGenerator.prm');
%%
%add states
calllib('bci', 'BCI2000Remote_AddStateVariable', bciHandle,'matlab',8, 0);

calllib('bci', 'BCI2000Remote_SetConfig', bciHandle);

calllib('bci', 'BCI2000Remote_Execute', bciHandle,'Show window watches',0);
calllib('bci', 'BCI2000Remote_Execute', bciHandle,'visualize watch matlab',0);
%start
calllib('bci', 'BCI2000Remote_Start', bciHandle);
pause(5);

%% send the behavior data to BCI2000
for i = 1:10
    calllib('bci', 'BCI2000Remote_SetStateVariable', bciHandle,'matlab', i);
    pause(1);
end

calllib('bci', 'BCI2000Remote_Delete', bciHandle);

See also

Contributions:BCI2000Automation, Contributions:BCI2000PresentationLink, Contributions:BCI2000Command, Contributions:BCI2000PythonBindings, Contributions:Applications