Jump to content

Programming Tutorial:Implementing an Input Logger

From BCI2000 Wiki
Revision as of 14:21, 4 September 2008 by Mellinger (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

In this tutorial, you will learn how to implement a BCI2000 Input Logger component. Here, Input Logging refers to recording the state of input devices, such as joysticks, keyboards, or mice, into BCI2000 state variables.

Overview

In BCI2000, Input Logging can be done with per-sample resolution. Typically, BCI2000 data acquisition, signal processing, and application feedback code runs in a pipe synchronously, being called once per BCI2000 sample block, and cannot detect state changes in an input device more frequently than that.

To support Input Logging at a per-sample resolution, BCI2000 allows code to post so-called events from a separate thread, which are time-stamped internally and matched against the current data block's time stamp in order to associate them with individual samples.

In this tutorial, we will discuss how to implement Input Logging for a device by polling its state in regular intervals. Generally, relying on OS events to detect changes in device state is preferred over polling; however, whether and how device state is available via OS events depends strongly on the device's driver software, and it is thus difficult to provide a valid example. Readers interested in Input Logging via OS events should read this tutorial first, and then proceed to the logger component's source code for a non-polling example.

Requisites

An input logger component consists mainly of a combination of a few existing software components, which are all provided by BCI2000 except the device API itself.

Device API

The device API provides functions that allow to read, or manipulate, the state of the input device. Typically, it consists of a library (DLL), and an associated header file.

For the sake of this tutorial, we will assume that the device has the shape of thumb wheel, and has one continuous degree of freedom. In its header file, ThumbWheel.h, it comes with a C-style interface:

#define THUMB_WHEEL_MAX_POS 32767
enum { ThumbOK = 0, ThumbBusy, ThumbUnavailable };
int ThumbWheelInit();
int ThumbWheelGetPos();

In order to connect to the thumb wheel, we call ThumbWheelInit(), receiving ThumbOK if everything is fine. The ThumbWheelGetPos() function will return the wheel's current position, as an integer between zero and THUMB_WHEEL_MAX_POS.

Event Interface

Using the BCI2000 event interface, device state may be written into BCI2000 states asynchronously. We will use the event interface to record the wheel's position into a state called WheelPos, writing

#include "BCIEvent.h"
...
bcievent << "WheelPos " << ThumbWheelGetPos();

Thread Interface

In order to observe the wheel's state independently of BCI2000's processing of data blocks, we create a thread that polls wheel state in regular intervals. We will use a BCI2000 thread class (BCI2000/src/shared/utils/OSThread).

Note that we avoid sending events unless the device state has actually changed. Otherwise, the event queue will grow very large, increasing overall processing and memory load even if there is no information to record.

#include "OSThread.h"
#include "ThumbWheel.h"

class ThumbThread : public OSThread
{
  virtual int Execute()
  {
    if( ThumbOK == ThumbWheelInit() )
    {
      int lastWheelPos = -1;
      while( !IsTerminating() )
      {
        Sleep( 1 );
        int curWheelPos = ThumbWheelGetPos();
        if( curWheelPos != lastWheelPos )
          bcievent << "WheelPos " << ThumbWheelGetPos();
        lastWheelPos = curWheelPos;
      }
    }
  return 0;
}