diff --git a/.gitignore b/.gitignore index f147edf..7b8788b 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,9 @@ ui_*.h *.qmlc *.jsc Makefile* -*build-* +*build-*/ +build/ +*-build-*/ *.qm *.prl @@ -50,3 +52,12 @@ compile_commands.json # QtCreator local machine specific files for imported projects *creator.user* + +#two downloaded folders + build +/DSI_API*/ +/liblsl*/ +/build/ +/API*/ +/LSL/* + +/dsi-api*/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d0fad36 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "cmake.configureArgs": [ + "-DLSL_DIR=YOUR_PATH_TO_LSL_LIBRARY", // Specify the path to the LSL cmake directory + "-DQt5_DIR=YOUR_PATH_TO_QT5_LIBRARY", // Specify the path to the Qt5 cmake directory + "-D CMAKE_PREFIX_PATH=YOUR_PATH_TO_QT5_MINGW" // Specify the path to Qt5 mingw81_64 or mingw64 + + ], + "files.associations": { + "mainwindow.h": "c", + "xstring": "cpp", + "iostream": "cpp", + "qbytearray": "cpp", + "initializer_list": "cpp", + "iterator": "cpp", + "memory": "cpp", + "xmemory": "cpp", + "qtgui": "cpp" + } + +} \ No newline at end of file diff --git a/CLI/dsi2lsl.c b/CLI/dsi2lsl.c index 63a2dde..61c1dc5 100644 --- a/CLI/dsi2lsl.c +++ b/CLI/dsi2lsl.c @@ -1,19 +1,34 @@ /* -This file implements the integration between Wearable Sensing DSI C/C++ API and -the LSL library. - -Copyright (C) 2014-2020 Syntrogi Inc dba Intheon. -*/ - + * dsi2lsl.c + * --------------------------------------------- + * Integration between Wearable Sensing DSI C/C++ API and Lab Streaming Layer (LSL). + * + * This program acquires data from a DSI headset and streams it over LSL for real-time + * data acquisition and analysis. It uses Windows threads for parallel processing: + * - DSI headset thread: continuously calls DSI_Headset_Idle to process incoming data. + * - Impedance thread: controls impedance measurement driver via runtime commands. + * + * Usage: + * - Run the executable and specify options via command line (see GlobalHelp). + * - Data is streamed to LSL and can be received by compatible clients. + * - Runtime commands: checkZOn, checkZOff, resetZ + * + * For support or feature requests, create a GitHub Issue or contact support@wearablesensing.com. + */ #include "DSI.h" #include "lsl_c.h" #include #include +#include #include #include +#include + -// Helper functions and macros that will be defined down below +// ----------------------------------------------------------------------------- +// Function Prototypes and Helper Macros +// ----------------------------------------------------------------------------- int StartUp( int argc, const char * argv[], DSI_Headset *headsetOut, int * helpOut ); int Finish( DSI_Headset h ); int GlobalHelp( int argc, const char * argv[] ); @@ -22,93 +37,386 @@ void OnSample( DSI_Headset h, double packetOffsetTime, void * userDa void getRandomString( char *s, const int len); const char * GetStringOpt( int argc, const char * argv[], const char * keyword1, const char * keyword2 ); int GetIntegerOpt( int argc, const char * argv[], const char * keyword1, const char * keyword2, int defaultValue ); - -float *sample; -static volatile int KeepRunning = 1; -void QuitHandler(int a){ KeepRunning = 0;} - -// error checking machinery -#define REPORT( fmt, x ) fprintf( stderr, #x " = " fmt "\n", ( x ) ) -int CheckError( void ){ - if( DSI_Error() ) return fprintf( stderr, "%s\n", DSI_ClearError() ); +int startAnalogReset( DSI_Headset h ); +int CheckImpedance( DSI_Headset h ); +void PrintImpedances( DSI_Headset h, double packetOffsetTime, void * userData ); + +// Global control flags +static volatile int KeepRunning = 1; // Main loop control +static volatile int DSI_Thread_Paused = 0;// Pause DSI thread + +/** + * Signal handler for graceful shutdown (Ctrl+C) + */ +void QuitHandler(int a) { KeepRunning = 0; } + +// Error checking macros +#define REPORT(fmt, x) fprintf(stderr, #x " = " fmt "\n", (x)) +int CheckError(void) { + if (DSI_Error()) return fprintf(stderr, "%s\n", DSI_ClearError()); else return 0; } -#define CHECK if( CheckError() != 0 ) return -1; +#define CHECK if (CheckError() != 0) return -1; + +#define MAX_COMMAND_LENGTH 256 +#define BUFFER_MILLISECONDS 1 // Sleep time for thread scheduling (milliseconds) +#define THREAD_INIT_WAIT_MS 500 // Wait time for thread initialization (milliseconds) +#define SHUTDOWN_IDLE_TIMEOUT 2.0 // Timeout for final packet reception during shutdown (seconds) +#define DEFAULT_ACCEL_RATE 30 // Default accelerometer sampling rate in Hz + +// ----------------------------------------------------------------------------- +// Constants +// ----------------------------------------------------------------------------- +/** + * CHUNK_SIZE: Number of samples per chunk pushed to LSL. + * Increasing CHUNK_SIZE beyond 9 increases timestamp difference. + * Decreasing it increases jitter. + */ +#define CHUNK_SIZE 9 + +// ----------------------------------------------------------------------------- +// Thread Parameter Structs +// ----------------------------------------------------------------------------- +/** + * ThreadParams: Parameters for impedance thread control. + */ +typedef struct { + DSI_Headset h; // Headset handle + volatile int printFlag; // Print impedance flag + volatile int startFlag; // Start impedance flag + volatile int stopFlag; // Stop impedance flag + lsl_outlet outlet; // LSL outlet +} ThreadParams; + +/** + * DSI_Processing_Thread + * --------------------- + * Thread function to continuously call DSI_Headset_Idle for data processing. + * @param lpParam: Pointer to DSI_Headset + * @return DWORD: 0 on success + */ +DWORD WINAPI DSI_Processing_Thread(LPVOID lpParam) { + DSI_Headset h = (DSI_Headset)lpParam; + fprintf(stdout, "DSI processing thread started.\n"); + + while (KeepRunning == 1) { + /* Only call Idle if the main thread hasn't paused us. */ + if (!DSI_Thread_Paused) { + /* Process data as quickly as possible with zero timeout */ + DSI_Headset_Idle(h, 0.0); + if (CheckError() != 0) { + fprintf(stderr, "Error in DSI processing thread. Exiting.\n"); + KeepRunning = 0; /* Signal main thread to exit. */ + } + } + else { + /* If paused, sleep to avoid busy-waiting */ + Sleep(BUFFER_MILLISECONDS); + } + } -int main( int argc, const char * argv[] ) -{ - // First load the libDSI dynamic library - const char * dllname = NULL; + fprintf(stdout, "DSI processing thread finished.\n"); + return 0; +} - // load the DSI DLL - int load_error = Load_DSI_API( dllname ); - if( load_error < 0 ) return fprintf( stderr, "failed to load dynamic library \"%s\"\n", DSI_DYLIB_NAME( dllname ) ); - if( load_error > 0 ) return fprintf( stderr, "failed to import %d functions from dynamic library \"%s\"\n", load_error, DSI_DYLIB_NAME( dllname ) ); - fprintf( stderr, "DSI API version %s loaded\n", DSI_GetAPIVersion() ); - if( strcmp( DSI_GetAPIVersion(), DSI_API_VERSION ) != 0 ) fprintf( stderr, "WARNING - mismatched versioning: program was compiled with DSI.h version %s but just loaded shared library version %s. You should ensure that you are using matching versions of the API files - contact Wearable Sensing if you are missing a file.\n", DSI_API_VERSION, DSI_GetAPIVersion() ); +/** + * ImpedanceThread + * --------------- + * Thread function to check for impedance activity and control impedance driver. + * @param lpParam: Pointer to ThreadParams + * @return DWORD: 0 on success + */ +DWORD WINAPI ImpedanceThread(LPVOID lpParam) { + fprintf(stdout, "DSI impedance thread started.\n"); + ThreadParams *params = (ThreadParams *)lpParam; + DSI_Headset h = params->h; + + while(KeepRunning == 1){ + if(params->startFlag){ + DSI_Headset_StartImpedanceDriver( h ); CHECK + // PrintImpedances( h, 0, "headings"); CHECK + /* Switch OnSample to PrintImpedances to print impedance values instead of raw signals. */ + DSI_Headset_SetSampleCallback( h, OnSample, params->outlet ); CHECK + params->startFlag = 0; + } + /* Uncomment the following lines to continuously print impedance check. */ + // while (params->printFlag) { + // DSI_Headset_Receive( h, 0.1, 0 ); CHECK + // } + if(params->stopFlag){ + DSI_Headset_StopImpedanceDriver( h ); CHECK + DSI_Headset_SetSampleCallback( h, OnSample, params->outlet ); CHECK + params->stopFlag = 0; + } + + if (CheckError() != 0) { + fprintf(stderr, "Error in impedance thread. Exiting.\n"); + KeepRunning = 0; /* Signal main thread to exit. */ + } + } + + fprintf(stdout, "DSI impedance thread finished.\n"); + return 0; +} - // Implements a Ctrl+C signal handler to quit the program (some terminals actually use Ctrl+Shift+C instead) +/** + * main + * ---- + * Entry point. Initializes DSI API, headset, LSL outlet, and starts threads. + */ +int main(int argc, const char *argv[]) +{ + srand((unsigned int)time(NULL)); // Seed RNG + const char *dllname = NULL; + char command[MAX_COMMAND_LENGTH]; + HANDLE sThread, iThread; + + // Load DSI DLL + int load_error = Load_DSI_API(dllname); + if (load_error < 0) return fprintf(stderr, "failed to load dynamic library \"%s\"\n", DSI_DYLIB_NAME(dllname)); + if (load_error > 0) return fprintf(stderr, "failed to import %d functions from dynamic library \"%s\"\n", load_error, DSI_DYLIB_NAME(dllname)); + fprintf(stderr, "DSI API version %s loaded\n", DSI_GetAPIVersion()); + if (strcmp(DSI_GetAPIVersion(), DSI_API_VERSION) != 0) + fprintf(stderr, "WARNING - mismatched versioning: program was compiled with DSI.h version %s but just loaded shared library version %s. You should ensure that you are using matching versions of the API files - contact Wearable Sensing if you are missing a file.\n", DSI_API_VERSION, DSI_GetAPIVersion()); + + // Set up Ctrl+C handler signal(SIGINT, QuitHandler); - // init the API and headset + // Initialize API and headset DSI_Headset h; int help, error; - error = StartUp( argc, argv, &h, &help ); - if( error || help){ + error = StartUp(argc, argv, &h, &help); + if (error || help) { GlobalHelp(argc, argv); return error; } // Initialize LSL outlet - const char * streamName = GetStringOpt( argc, argv, "lsl-stream-name", "m" ); - if (!streamName) - streamName = "WS-default"; - printf("Initializing %s outlet\n", streamName); - lsl_outlet outlet = InitLSL(h, streamName); CHECK; /* stream outlet */ + const char *streamName = GetStringOpt(argc, argv, "lsl-stream-name", "m"); + if (!streamName) streamName = "WS-default"; + fprintf(stdout, "Initializing %s outlet\n", streamName); + lsl_outlet outlet = InitLSL(h, streamName); CHECK; - // Set the sample callback (forward every data sample received to LSL) + /* Set the sample callback (forward every data sample received to LSL) */ DSI_Headset_SetSampleCallback( h, OnSample, outlet ); CHECK - // Start data acquisition - printf("Starting data acquisition\n"); + /* Start data acquisition */ + fprintf(stdout, "Starting data acquisition\n"); DSI_Headset_StartDataAcquisition( h ); CHECK - // Start streaming - printf("Streaming...\n"); + /* Custom struct for impedance flags */ + ThreadParams zFLag; + zFLag.h = h; /* Valid DSI_Headset variable */ + zFLag.printFlag = 0; /* Used to print impedance continuously */ + zFLag.startFlag = 0; + zFLag.stopFlag = 0; + zFLag.outlet = outlet; /* Valid LSL outlet */ + + /* Create the impedance thread */ + iThread = CreateThread(NULL, 0, ImpedanceThread, &zFLag, 0, NULL); + if (iThread == NULL) { + fprintf(stderr, "Error creating DSI impedance thread.\n"); + return Finish(h); + } + /* Create and start the DSI processing thread */ + sThread = CreateThread(NULL, 0, DSI_Processing_Thread, h, 0, NULL); + if (sThread == NULL) { + fprintf(stderr, "Error creating DSI processing thread.\n"); + /* Close the impedance thread handle to prevent leak */ + if (iThread != NULL) { + KeepRunning = 0; + WaitForSingleObject(iThread, 1000); + CloseHandle(iThread); + } + return Finish(h); + } + + fprintf(stderr, "Wait...\n"); + Sleep(THREAD_INIT_WAIT_MS); /* Wait for threads to initialize properly */ + fprintf(stderr, "Setup Ready\n"); + /* Start streaming */ + fprintf(stdout, "Streaming...\n"); while( KeepRunning==1 ){ - DSI_Headset_Idle( h, 0.0 ); CHECK + + /* + * Read a line of input from stdin (the terminal) + * fgets reads up to MAX_COMMAND_LENGTH-1 characters or until a newline. + * It includes the newline character if it fits in the buffer. + */ + if (fgets(command, MAX_COMMAND_LENGTH, stdin) == NULL) { + /* Handle potential error or EOF (End Of File) condition. */ + fprintf(stdout, "Error reading input or EOF reached.\n"); + break; /* Exit the loop on error */ + } + + /* + * Remove the trailing newline character if it exists + * fgets includes the newline, which can interfere with string comparisons. + */ + command[strcspn(command, "\n")] = 0; + + if (command[0] == '\0') { + /* If no command was entered (just spaces or empty line after stripping newline) */ + continue; /* Go back to the prompt */ + } + + else if (strcmp(command, "checkZOn") == 0) { + /* Start impedance driver */ + zFLag.stopFlag = 0; + zFLag.startFlag = 1; + + }else if (strcmp(command, "checkZOff") == 0) { + /* Stop impedance driver */ + zFLag.stopFlag = 1; + zFLag.startFlag = 0; + } + else if (strcmp(command, "resetZ") == 0) { + /* Reset impedance */ + startAnalogReset( h ); CHECK + } } - // Gracefully exit the program - printf("\n%s will exit now...\n", argv[ 0 ]); + /* Closing the threads */ + if (sThread != NULL) { + fprintf(stdout, "Waiting for DSI thread to terminate...\n"); + WaitForSingleObject(sThread, INFINITE); + CloseHandle(sThread); + fprintf(stdout, "DSI thread has terminated.\n"); + } + if (iThread != NULL) { + fprintf(stdout, "Waiting for impedance thread to terminate...\n"); + WaitForSingleObject(iThread, INFINITE); + CloseHandle(iThread); + fprintf(stdout, "Impedance thread has terminated.\n"); + } + + + /* Gracefully exit the program */ + fprintf(stdout, "\n%s will exit now...\n", argv[ 0 ]); lsl_destroy_outlet(outlet); return Finish( h ); } +/** + * Reset the analog impedance for a DSI headset. + * + * @param h - Valid DSI headset handle + * @return 0 on success + */ +int startAnalogReset(DSI_Headset h) { + if (h == NULL) { + fprintf(stderr, "Error: Invalid headset handle.\n"); + return -1; + } + /* Check initial analog reset mode */ + fprintf(stdout, "--> Initial analog reset mode: %d\n", DSI_Headset_GetAnalogResetMode(h)); + + DSI_Headset_StartAnalogReset(h); CHECK + return 0; +} + +/** + * Callback function invoked for each sample received from the DSI headset. + * Collects samples into a chunk buffer and pushes them to the LSL outlet in batches. + * + * @param h - Valid DSI headset handle + * @param unused_packet_offset_time - Unused packet offset time + * @param outlet - LSL outlet to push data to + */ +/* Helper struct for chunk buffer management */ +typedef struct { + float* buffer; + int sample_index_in_chunk; + unsigned int numberOfChannels; +} ChunkBufferManager; + +/* Helper function to initialize or get chunk buffer */ +static ChunkBufferManager* GetChunkBufferManager(DSI_Headset h, ChunkBufferManager **manager_ptr) { + if (*manager_ptr == NULL) { + *manager_ptr = (ChunkBufferManager*)malloc(sizeof(ChunkBufferManager)); + if (*manager_ptr == NULL) { + fprintf(stderr, "Fatal Error: Could not allocate memory for ChunkBufferManager.\n"); + return NULL; + } + (*manager_ptr)->numberOfChannels = DSI_Headset_GetNumberOfChannels(h); + (*manager_ptr)->sample_index_in_chunk = 0; + if ((*manager_ptr)->numberOfChannels > 0) { + (*manager_ptr)->buffer = (float*)malloc(CHUNK_SIZE * (*manager_ptr)->numberOfChannels * sizeof(float)); + if ((*manager_ptr)->buffer == NULL) { + fprintf(stderr, "Fatal Error: Could not allocate memory for chunk buffer.\n"); + free(*manager_ptr); + *manager_ptr = NULL; + return NULL; + } + } else { + (*manager_ptr)->buffer = NULL; + } + } + return *manager_ptr; +} + +/* Helper function to free chunk buffer */ +static void FreeChunkBufferManager(ChunkBufferManager **manager_ptr) { + if (*manager_ptr) { + if ((*manager_ptr)->buffer) free((*manager_ptr)->buffer); + free(*manager_ptr); + *manager_ptr = NULL; + } +} -// handler called on every sample, immediately forwards to LSL -void OnSample( DSI_Headset h, double packetOffsetTime, void * outlet) +static ChunkBufferManager *onSampleManager = NULL; + +/** + * OnSample + * -------- + * Callback for each sample received from DSI headset. + * Buffers samples and pushes them to LSL in chunks. + * + * @param h: DSI headset handle + * @param unused_packet_offset_time: Unused + * @param outlet: LSL outlet + */ +void OnSample(DSI_Headset h, double unused_packet_offset_time, void *outlet) { - unsigned int channelIndex; - unsigned int numberOfChannels = DSI_Headset_GetNumberOfChannels( h ); - if (sample == NULL) - sample = (float *)malloc( numberOfChannels * sizeof(float)); - for(channelIndex=0; channelIndex < numberOfChannels; channelIndex++){ - sample[channelIndex] = (float) DSI_Channel_GetSignal( DSI_Headset_GetChannelByIndex( h, channelIndex ) ); + (void)unused_packet_offset_time; + ChunkBufferManager *manager = GetChunkBufferManager(h, &onSampleManager); + if (!manager || !manager->buffer) return; + + // Fill buffer with current sample data + float* current_sample_ptr = &manager->buffer[manager->sample_index_in_chunk * manager->numberOfChannels]; + for (unsigned int channelIndex = 0; channelIndex < manager->numberOfChannels; channelIndex++) { + current_sample_ptr[channelIndex] = (float)DSI_Channel_GetSignal(DSI_Headset_GetChannelByIndex(h, channelIndex)); + } + + manager->sample_index_in_chunk++; + + // Push chunk to LSL when buffer is full + if (manager->sample_index_in_chunk == CHUNK_SIZE) { + lsl_push_chunk_ft(outlet, manager->buffer, (size_t)(CHUNK_SIZE * manager->numberOfChannels), lsl_local_clock()); + manager->sample_index_in_chunk = 0; } - lsl_push_sample_f(outlet, sample); } int Message( const char * msg, int debugLevel ){ return fprintf( stderr, "DSI Message (level %d): %s\n", debugLevel, msg ); } -// connect to headset +/** + * Initializes and connects to the DSI headset, prepares it for + * data acquisition. + * + * @param argc - Command-line argument count + * @param argv - Command-line argument vector + * @param headsetOut - Output pointer to store initialized headset handle. + * @param helpOut - Output flag indicating if help was requested. + * + * @return 0 on success, non-zero on error. + */ int StartUp( int argc, const char * argv[], DSI_Headset * headsetOut, int * helpOut ) { DSI_Headset h; - // read out any configuration options + /* Read out any configuration options. */ int help = GetStringOpt( argc, argv, "help", "h" ) != NULL; const char * serialPort = GetStringOpt( argc, argv, "port", "p" ); const char * montage = GetStringOpt( argc, argv, "montage", "m" ); @@ -118,61 +426,131 @@ int StartUp( int argc, const char * argv[], DSI_Headset * headsetOut, int * help if( helpOut ) *helpOut = help; if( help ) return 0; - // Passing NULL defers setup of the serial port connection until later... + /* Passing NULL defers setup of the serial port connection until later... */ h = DSI_Headset_New( NULL ); CHECK - // ...which allows us to configure the way we handle any debugging messages - // that occur during connection (see our definition of the `DSI_MessageCallback` - // function `Message()` above). + /* + * ...which allows us to configure the way we handle any debugging messages + * that occur during connection (see our definition of the `DSI_MessageCallback` + * function `Message()` above). + */ DSI_Headset_SetMessageCallback( h, Message ); CHECK DSI_Headset_SetVerbosity( h, verbosity ); CHECK - // Now we establish the serial port connection and initialize the headset. - // In this demo program, the string supplied in the --port command-line - // option is used as the serial port address (if this string is empty, the - // API will automatically look for an environment variable called - // DSISerialPort). + /* + * Now we establish the serial port connection and initialize the headset. + * In this demo program, the string supplied in the --port command-line + * option is used as the serial port address (if this string is empty, the + * API will automatically look for an environment variable called + * DSISerialPort). + */ DSI_Headset_Connect( h, serialPort ); CHECK - // Sets up the montage according to strings supplied in the --montage and - // --reference command-line options, if any. + /* + * Sets up the montage according to strings supplied in the --montage and + * --reference command-line options, if any. + */ DSI_Headset_ChooseChannels( h, montage, reference, 1 ); CHECK - // prints an overview of what is known about the headset + /* + * Check accelerometer feature availability and configure if enabled. + * The info string contains FeatureAvailability with "Accelerometer":1 (double quotes) + * and AccelerometerRate with 'AccelerometerRate': 30 (single quotes). + */ + const char *infoString = DSI_Headset_GetInfoString(h); + if (infoString) { + /* Look for "Accelerometer" in FeatureAvailability (uses double quotes in JSON object) */ + const char *accelField = strstr(infoString, "\"Accelerometer\""); + if (accelField) { + /* Move past the field name to find the value after the colon */ + accelField = strchr(accelField, ':'); + if (accelField) { + accelField++; /* Move past the colon */ + /* Skip whitespace */ + while (*accelField == ' ' || *accelField == '\t') accelField++; + /* Check if value is 1 (enabled) */ + if (*accelField == '1') { + /* Check current accelerometer rate from info string (uses single quotes) */ + const char *rateField = strstr(infoString, "'AccelerometerRate'"); + int currentRate = 0; + if (rateField) { + rateField = strchr(rateField, ':'); + if (rateField) { + rateField++; /* Move past the colon */ + /* Skip whitespace */ + while (*rateField == ' ' || *rateField == '\t') rateField++; + { + char *endptr = NULL; + long parsedRate = strtol(rateField, &endptr, 10); + if (endptr != rateField) { + currentRate = (int)parsedRate; + } else { + /* Parsing failed; keep currentRate as 0 to trigger default behavior */ + currentRate = 0; + } + } + } + } + + if (currentRate == 0) { + fprintf(stderr, "Accelerometer is enabled but rate is 0. Setting rate to %d Hz\n", DEFAULT_ACCEL_RATE); + DSI_Headset_SetAccelerometerRate(h, DEFAULT_ACCEL_RATE); CHECK + } else { + fprintf(stderr, "Accelerometer is already enabled at %d Hz\n", currentRate); + } + } + } + } + } + + /* Prints an overview of what is known about the headset. */ fprintf( stderr, "%s\n", DSI_Headset_GetInfoString( h ) ); CHECK + if( headsetOut ) *headsetOut = h; if( helpOut ) *helpOut = help; return 0; } - -// close connection to the hardware + +static ChunkBufferManager *impedanceManager = NULL; + int Finish( DSI_Headset h ) { - // This stops our application from responding to received samples. + /* This stops our application from responding to received samples. */ DSI_Headset_SetSampleCallback( h, NULL, NULL ); CHECK - // This send a command to the headset to tell it to stop sending samples. - DSI_Headset_StopDataAcquisition( h ); CHECK + /* Free the buffer allocated in OnSample and PrintImpedances to prevent memory leak. */ + FreeChunkBufferManager(&onSampleManager); + FreeChunkBufferManager(&impedanceManager); - // This allows more than enough time to receive any samples that were - // sent before the stop command is carried out, along with the alarm - // signal that the headset sends out when it stops - DSI_Headset_Idle( h, 1.0 ); CHECK + /* This sends a command to the headset to stop sending samples. */ + DSI_Headset_StopDataAcquisition( h ); CHECK - // This is the only really necessary step. Disconnects from the serial - // port, frees memory, etc. + /* + * This allows more than enough time to receive any samples that were + * sent before the stop command is carried out, along with the alarm + * signal that the headset sends out when it stops. + */ + DSI_Headset_Idle( h, SHUTDOWN_IDLE_TIMEOUT ); CHECK + + /* + * This is the only really necessary step. Disconnects from the serial + * port, frees memory, etc. + */ DSI_Headset_Delete( h ); CHECK return 0; } - void getRandomString(char *s, const int len) { int i = 0; - static const char alphanum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - for (i=0; i < len; ++i){ s[i] = alphanum[rand() % (sizeof(alphanum) - 1)];} + static const char alphanum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + unsigned int seed = (unsigned int)time(NULL) ^ (unsigned int)rand(); + srand(seed); // Reseed for more randomness per call + for (i = 0; i < len; ++i) { + s[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; + } s[len] = 0; } @@ -180,53 +558,68 @@ lsl_outlet InitLSL(DSI_Headset h, const char * streamName) { unsigned int channelIndex; unsigned int numberOfChannels = DSI_Headset_GetNumberOfChannels( h ); - int samplingRate = DSI_Headset_GetSamplingRate( h ); + double samplingRate = DSI_Headset_GetSamplingRate( h ); - // out stream declaration object + /* Output stream declaration object */ lsl_streaminfo info; - // some xml element pointers + /* Some xml element pointers */ lsl_xml_ptr desc, chn, chns, ref; - int imax = 16; - char source_id[imax]; + #define IMAX 16 + char source_id[IMAX]; char *long_label; char *short_label; char *reference; - // Note: an even better choice here may be the serial number of the device - getRandomString(source_id, imax); + /* Note: an even better choice here may be the serial number of the device. */ + getRandomString(source_id, IMAX); + fprintf(stderr, "Source ID: %s\n", source_id); - // Declare a new streaminfo (name: WearableSensing, content type: EEG, number of channels, srate, float values, source id + /* Declare a new streaminfo (name: WearableSensing, content type: EEG, number of channels, srate, float values, source id. */ info = lsl_create_streaminfo((char*)streamName,"EEG",numberOfChannels,samplingRate,cft_float32,source_id); - // Add some meta-data fields to it (for more standard fields, see https://github.com/sccn/xdf/wiki/Meta-Data) + if(!info) { + fprintf(stderr, "Failed to create LSL streaminfo.\n"); + return NULL; + } + fprintf(stderr, "Stream Name: %s\n", streamName); + /* Add some meta-data fields to it (for more standard fields, see https://github.com/sccn/xdf/wiki/Meta-Data). */ desc = lsl_get_desc(info); lsl_append_child_value(desc,"manufacturer","WearableSensing"); - - // Describe channel info + + /* Describe channel info */ chns = lsl_append_child(desc,"channels"); for( channelIndex=0; channelIndex < numberOfChannels ; channelIndex++) { chn = lsl_append_child(chns,"channel"); long_label = (char*) DSI_Channel_GetString( DSI_Headset_GetChannelByIndex( h, channelIndex ) ); - // Cut off "negative" part of channel name (e.g., the ref chn) - short_label = strtok(long_label, "-"); + /* Cut off "negative" part of channel name (e.g., the ref chn) */ + char label_buffer[256]; + strncpy_s(label_buffer, sizeof(label_buffer), long_label, _TRUNCATE); + label_buffer[sizeof(label_buffer) - 1] = '\0'; + char *context = NULL; + short_label = strtok_s(label_buffer, "-", &context); if(short_label == NULL) - short_label = long_label; - // Cmit channel info + short_label = label_buffer; + /* Commit channel info to LSL stream */ lsl_append_child_value(chn,"label", short_label); lsl_append_child_value(chn,"unit","microvolts"); lsl_append_child_value(chn,"type","EEG"); } - // Describe reference used + /* Describe reference used */ reference = (char*)DSI_Headset_GetReferenceString(h); ref = lsl_append_child(desc,"reference"); lsl_append_child_value(ref,"label", reference); - printf("REF: %s\n", reference); - - // Make a new outlet (chunking: default, buffering: 360 seconds) - return lsl_create_outlet(info,0,360); + fprintf(stdout, "REF: %s\n", reference); + + /* Make a new outlet (chunking: default, buffering: 360 seconds). */ + lsl_outlet outlet = lsl_create_outlet(info, 0, 360); + + /* Free streaminfo as it's no longer needed after creating the outlet */ + lsl_destroy_streaminfo(info); + + return outlet; } @@ -269,18 +662,19 @@ int GlobalHelp( int argc, const char * argv[] ) "\n" , argv[ 0 ] ); return 0; - } - +} -// These two functions are carried over from the Wearable Sensing example code -// and are Copyright (c) 2014-2016 Wearable Sensing LLC -// -// Helper function for figuring out command-line input flags like --port=COM4 -// or /port:COM4 (either syntax will work). Returns NULL if the specified -// option is absent. Returns a pointer to the argument value if the option -// is present (the pointer will point to '\0' if the argument value is empty -// or not supplied as part of the option string). +/* + * These functions are carried over from the Wearable Sensing example code + * and are Copyright (c) 2014-2025 Wearable Sensing LLC. + * + * Helper function for figuring out command-line input flags like --port=COM4 + * or /port:COM4 (either syntax will work). Returns NULL if the specified + * option is absent. Returns a pointer to the argument value if the option + * is present (the pointer will point to '\0' if the argument value is empty + * or not supplied as part of the option string). + */ const char * GetStringOpt( int argc, const char * argv[], const char * keyword1, const char * keyword2 ) { int i, j; @@ -313,8 +707,38 @@ int GetIntegerOpt( int argc, const char * argv[], const char * keyword1, const c int result; const char * stringValue = GetStringOpt( argc, argv, keyword1, keyword2 ); if( !stringValue || !*stringValue ) return defaultValue; - result = ( int ) strtol( stringValue, &end, 10 ); - if( !end || !*end ) return result; - fprintf( stderr, "WARNING: could not interpret \"%s\" as a valid integer value for the \"%s\" option - reverting to default value %s=%g\n", stringValue, keyword1, keyword1, ( double )defaultValue ); - return defaultValue; + result = (int)strtol( stringValue, &end, 10 ); + return result; +} + +/** + * PrintImpedances + * --------------- + * Callback function to collect impedance values from the DSI headset and push them to the LSL outlet in chunks. + * + * @param h Valid DSI headset handle. + * @param packetOffsetTime Unused packet offset time. + * @param outlet LSL outlet to push impedance data to. + * + * Buffers impedance values for each channel and pushes them to LSL when the buffer is full. + */ +void PrintImpedances( DSI_Headset h, double packetOffsetTime, void * outlet ) +{ + (void)packetOffsetTime; + ChunkBufferManager *manager = GetChunkBufferManager(h, &impedanceManager); + if (!manager || !manager->buffer) return; + + float* current_sample_ptr = &manager->buffer[manager->sample_index_in_chunk * manager->numberOfChannels]; + + for (unsigned int channelIndex = 0; channelIndex < manager->numberOfChannels; channelIndex++) { + current_sample_ptr[channelIndex] = (float) DSI_Source_GetImpedanceEEG( + DSI_Headset_GetSourceByIndex(h, channelIndex)); + } + + manager->sample_index_in_chunk++; + + if (manager->sample_index_in_chunk == CHUNK_SIZE) { + lsl_push_chunk_ft(outlet, manager->buffer, (size_t)(CHUNK_SIZE * manager->numberOfChannels), lsl_local_clock()); + manager->sample_index_in_chunk = 0; + } } diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..42d1db0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,77 @@ +cmake_minimum_required(VERSION 3.10) +project(dsi2lslGUI + LANGUAGES C CXX + VERSION 1.13.0) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +# Find an installed liblsl in paths set by the user (LSL_INSTALL_ROOT) +# and some default paths +find_package(LSL REQUIRED + HINTS ${LSL_INSTALL_ROOT} + "${CMAKE_CURRENT_LIST_DIR}/../../LSL/liblsl/build/" + "${CMAKE_CURRENT_LIST_DIR}/../../LSL/liblsl/out/build/x64-Release" + PATH_SUFFIXES share/LSL) +get_filename_component(LSL_PATH ${LSL_CONFIG} DIRECTORY) +message(STATUS "Found LSL lib in ${LSL_PATH}") +set(LSL_COMFY_DEFAULTS ON) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) +# Set output directories for the build artifacts +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$) + +# Sets variables for the directories used +# --------------------UPDATE base on your naming conventions, or updating version------------------------------------------- +SET(DSI-API "${CMAKE_CURRENT_SOURCE_DIR}/API/DSI_API_v1.20.3_06202025") +SET(LSL-CLI "${CMAKE_CURRENT_SOURCE_DIR}/CLI") +SET(LSL-GUI "${CMAKE_CURRENT_SOURCE_DIR}/GUI") +# -------------------------------------------------------------------------------------------------------------------------- + +# finds required packages +find_package(Qt5 REQUIRED COMPONENTS Widgets) +find_package(Threads REQUIRED) + +# creates the GUI .exe +add_executable(${PROJECT_NAME} MACOSX_BUNDLE WIN32 + ${LSL-GUI}/main.cpp + ${LSL-GUI}/mainwindow.cpp + ${LSL-GUI}/mainwindow.h + ${LSL-GUI}/mainwindow.ui +) + +# creates the LSL wearbale sensing module .exe +add_executable(dsi2lsl + ${LSL-CLI}/dsi2lsl.c + ${DSI-API}/DSI_API_Loader.c + ${DSI-API}/DSI.h +) + + +# the dependencies for LSL wearbale sensing module +target_link_libraries(dsi2lsl + PRIVATE + LSL::lsl + Threads::Threads +) +target_include_directories(dsi2lsl + PRIVATE + "${DSI-API}" +) + +# the dependencies for the GUI +target_link_libraries(${PROJECT_NAME} + PRIVATE + Qt5::Widgets + Threads::Threads + LSL::lsl +) + +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14) + +installLSLApp(${PROJECT_NAME}) + +LSLGenerateCPackConfig() diff --git a/GUI/mainwindow.cpp b/GUI/mainwindow.cpp index 7df7c2f..76f830c 100644 --- a/GUI/mainwindow.cpp +++ b/GUI/mainwindow.cpp @@ -1,18 +1,28 @@ -// Wearable Sensing LSL GUI -// Copyright (C) 2014-2020 Syntrogi Inc dba Intheon. +/* + * Wearable Sensing LSL GUI + * + * Please create a GitHub Issue or contact support@wearablesensing.com if you + * encounter any issues or would like to request new features. + */ #include "mainwindow.h" #include "ui_mainwindow.h" #include +#include +#include +#include +#include +#include #ifndef WIN32 #include #endif -// This program is a GUI for the dsi2lsl console application; -// it works by running dsi2lsl as a subprocess, and passes in -// the configuration as command-line arguments - +/* + * This program is a GUI for the dsi2lsl console application; + * it works by running dsi2lsl as a subprocess, and passes in + * the configuration as command-line arguments. + */ #ifdef WIN32 const QString program = "dsi2lsl.exe"; #else @@ -26,14 +36,21 @@ const QString reference = "--reference="; const QString defaultValule = "(use default)"; +/** + * Constructor for MainWindow + * It initializes the UI, sets up the environment, and prepares the streamer process. + * @param QWidget - The parent widget for the main window. + * @return void + */ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), - counter(0) + counter(0), + zCheckState(false) { ui->setupUi(this); - // Set LD_LIBRARY_PATH + /* Set LD_LIBRARY_PATH */ #ifndef WIN32 char cdir[256]; std:setenv("LD_LIBRARY_PATH",getcwd(cdir, 256), 1); @@ -49,38 +66,100 @@ MainWindow::MainWindow(QWidget *parent) : this->progressBar = new QProgressBar(this); ui->statusBar->addPermanentWidget(this->progressBar, 1); this->progressBar->setTextVisible(false); - - - // Create a brand new streamer this->streamer = new QProcess(this); this->streamer->setProcessChannelMode(QProcess::MergedChannels); + /* Connecting Impedance button */ + connect(ui->ZCheckBox, &QCheckBox::toggled, this, &MainWindow::onZCheckBoxToggled); + connect(ui->ResetZButton, &QPushButton::clicked, this, &MainWindow::onResetZButtonClicked); } - +/** + * Destructor for MainWindow + * It stops the streamer process if it is running and deletes the UI. + * @return void + */ MainWindow::~MainWindow() { this->on_buttonBox_rejected(); delete ui; } +/** + * This function is called when the user clicks the "Z" button. + * It toggles the state of the Z button and sends a command to the streamer process + * to check the impedance status. + * If the streamer is not running, it appends a message to the console. + * @param checked - The state of the Z checkbox (true if checked, false if unchecked). + * @return void + */ +void MainWindow::onZCheckBoxToggled(bool checked){ + this->zCheckState = checked; +} +void MainWindow::handleZCheckBoxToggled(){ + QByteArray command; + if(this->zCheckState){ + command = "checkZOn\n"; + this->ui->console->append("\n---------- Impedance On -----------\n"); + }else{ + command = "checkZOff\n"; + this->ui->console->append("\n---------- Impedance Off ----------\n"); + } + // Write the command to the process's standard input + this->streamer->write(command); +} + +/** + * This function is called when the user clicks the "Reset Z" button. + * It sends a command to the streamer process to reset the impedance status. + * @return void + */ +void MainWindow::onResetZButtonClicked(){ + if (this->streamer && this->streamer->state() == QProcess::Running) { + /* The command to send, including the newline character to simulate 'Enter'. */ + QByteArray command; + command = "resetZ\n"; + /* Write the command to the process's standard input. */ + this->streamer->write(command); + this->ui->console->append("\n---------- Reset ----------\n"); + + } else { + this->ui->console->append("Streamer is not running. Cannot send command."); + } +} +/** + * This function is called when the user clicks the "Start" button. + * It checks if the streamer is already running, and if so, it stops it. + * Then it sets up the input arguments for the streamer and starts it. + * It also connects the output of the streamer to a slot that writes to the console. + * + * @return void + */ void MainWindow::on_buttonBox_accepted() { - if(this->streamer != NULL) + if (this->streamer && this->streamer->state() == QProcess::Running) { this->on_buttonBox_rejected(); - - // Set input arguments to the streamer + } + /* Set input arguments to the streamer */ QStringList arguments = this->parseArguments(); this->streamer->start(program, arguments); connect(this->streamer, SIGNAL(readyReadStandardOutput()), this, SLOT(writeToConsole())); + handleZCheckBoxToggled(); /* Handle the Z checkbox state */ this->counter = 0; this->timerId = this->startTimer(1000); + this->ui->ZCheckBox->setEnabled(false); /* Enable the ZCheckBox */ } - +/** + * This function is called when the timer event occurs. + * It updates the progress bar and status bar message. + * If the streamer process is not running, it stops the timer and resets the UI. + * @param event - The timer event that triggered this function. + * @return void + */ void MainWindow::timerEvent(QTimerEvent *event) { if(this->streamer->state()==QProcess::NotRunning) @@ -88,7 +167,6 @@ void MainWindow::timerEvent(QTimerEvent *event) this->on_buttonBox_rejected(); return; } - if(!this->ui->statusBar->isVisible()) this->ui->statusBar->setVisible(true); this->counter += 33; @@ -98,16 +176,27 @@ void MainWindow::timerEvent(QTimerEvent *event) this->ui->statusBar->showMessage("Streaming..."); } - +/** + * This function reads the output from the streamer process and appends it to the console. + * It is called whenever there is new data available from the streamer. + * @return void + */ void MainWindow::writeToConsole() { while(this->streamer->canReadLine()){ - this->ui->console->append(this->streamer->readLine()); + QString line = this->streamer->readLine(); /* Read the line into a string */ + if (!line.contains("netinterfaces.cpp") && !line.contains("udp_server.cpp") && !line.contains("common.cpp") && !line.contains("api_config.cpp")) { /* Check if the line contains "netinterfaces.cpp" */ + this->ui->console->append(line); /* If it doesn't, add it to the console */ + } } } - +/** + * This function is called when the user clicks the "Stop" button. + * It stops the streamer process and resets the UI elements. + * @return void + */ void MainWindow::on_buttonBox_rejected() { if(this->streamer != NULL){ @@ -116,11 +205,16 @@ void MainWindow::on_buttonBox_rejected() this->killTimer(this->timerId); this->counter = 0; this->ui->statusBar->setVisible(false); + this->ui->ZCheckBox->setEnabled(true); /* Enable the ZCheckBox */ } } - +/** + * Parses the arguments from the GUI input fields and returns a QStringList + * containing the command-line arguments for the dsi2lsl process. + * @return QStringList - The list of arguments to be passed to the dsi2lsl process. + */ QStringList MainWindow::parseArguments() { QStringList arguments; @@ -135,4 +229,4 @@ QStringList MainWindow::parseArguments() arguments << (reference+this->ui->referenceLineEdit->text().simplified()).toStdString().c_str(); return arguments; -} +} \ No newline at end of file diff --git a/GUI/mainwindow.h b/GUI/mainwindow.h index f709a0f..4d379ea 100644 --- a/GUI/mainwindow.h +++ b/GUI/mainwindow.h @@ -1,5 +1,9 @@ -// Wearable Sensing LSL GUI -// Copyright (C) 2014-2020 Syntrogi Inc dba Intheon. +/* + * Wearable Sensing LSL GUI + * + * Please create a GitHub Issue or contact support@wearablesensing.com if you + * encounter any issues or would like to request new features. + */ #ifndef MAINWINDOW_H #define MAINWINDOW_H @@ -7,6 +11,10 @@ #include #include #include +#include +#include +#include +#include namespace Ui { @@ -28,6 +36,13 @@ private slots: void writeToConsole(); QStringList parseArguments(); void timerEvent(QTimerEvent *event); + + /* For checking impedance */ + void onZCheckBoxToggled(bool checked); + void handleZCheckBoxToggled(); + + /* For resetting impedance */ + void onResetZButtonClicked(); private: Ui::MainWindow *ui; @@ -35,6 +50,14 @@ private slots: int timerId; int counter; QProgressBar *progressBar; + + /* For checking impedance */ + QCheckBox *ZCheckBox; + bool zCheckState; + + /* For resetting impedance */ + QPushButton *resetZButton; /* Pointer to your resetZButton */ + }; -#endif // MAINWINDOW_H +#endif /* MAINWINDOW_H */ \ No newline at end of file diff --git a/GUI/mainwindow.ui b/GUI/mainwindow.ui index fdbda49..0489ed1 100644 --- a/GUI/mainwindow.ui +++ b/GUI/mainwindow.ui @@ -51,15 +51,44 @@ - - - <html><head/><body><p>Start/Stop streaming.</p></body></html> - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - + + + + + <html><head/><body><p>Start/Stop streaming.</p></body></html> + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + <html><head/><body><p>Resets Impedance</p></body></html> + + + Reset + + + + + + + + + + <html><head/><body><p>Checks Impedance</p></body></html> + + + Impedance + + + + + + @@ -91,6 +120,8 @@ + + diff --git a/README.md b/README.md index 419fade..b45ff95 100644 --- a/README.md +++ b/README.md @@ -1,328 +1,169 @@ -# Wearable Sensing LSL (dsi2lsl) - -This application supports several dry/wireless EEG headsets made by [Wearable Sensing](http://www.wearablesensing.com). - -You can find prebuilt binaries for Windows, Linux, and Mac OS on the Releases -page of this repository. For build instructions, see section [Building From Source](#building-dsi2lsl-from-source) below. - -*Note*: The program has been tested with the DSI-7 and the DSI-VR300 headsets, -but several other headset models should work, as well. - -## Usage - -To use this software, you do __not__ need the *DSI-Streamer* program, although it is -helpful to have at hand during headset setup (e.g., for checking impedances or -for troubleshooting). However, only one program can access the device at -a time, so keep in mind to turns the DSI Streamer off before using this application. - -Your headset, which is a Bluetooth device, has to be paired with your machine -in order to use it with dsi2lsl. The recommended procedure is to follow the -vendor instructions for this, which are different for the respective operating -systems. - -To check that your -headset is correctly paired, run the *DSI-Streamer* application by Wearable Sensing -and make sure that it can receive data (if it can not, dsi2lsl will also not work). -Note the port name that this program uses, which differs between OSes and machines -(e.g., it might be `COM4` on a Windows, but it could also be a much higher -port number if you work a lot with COM-port devices). The section -[Bluetooth Quick Reference](#bluetooth-quick-reference) -below has some additional information on pairing and looking up the port name. - -### Using The Graphical User Interface (GUI) - -Now that your headset is paired, and you have the port name, you can use -either the command-line interface or the graphical user interface. To use the -GUI, launch the program dsi2lslgui, which should open a window like this: - -![app-screen.png](app-screen.png) - -When filling in information, be sure to review the tooltips of the various -entry fields by hovering over them with the mouse. - -The only setting that you __must__ specify is the Port field, which defaults to blank. -Be sure to put here the correct port name. Follow the vendor's information on -how to retrieve this, or fall back to the [Bluetooth Quick Reference](#bluetooth-quick-reference) -section below. In short, on Windows this is typically a string like, e.g., `COM7`, -on Linux it is a device name such as `/dev/rfcomm0`, and on Mac OS, it is -a device name like `/dev/tty.DSI7-017-BluetoothSeria`. - -You can change the LSL Stream Name field freely, but this is not necessary for -correct operation. The stream name will be recorded to a file if you use the -Lab Recorder, so be aware to enter no personally identifiable information. Typically -one would enter the device name, or if you have multiple devices of the same type, -append a number or other unique identifier. - -Next, you can override the montage. The default "(use default)" is to record from all electrodes -and to use the reference that is available on the hardware (e.g., ear or mastoid, -etc.) as documented in your device manual. To override this, you use a syntax that is -interpreted by the headset software, and so, so for the most detailed information, -you can consult your vendor-provided documentation. In general, this entry field - ust be specified without any space characters. In the simplest case, this -is just a comma-separated list of channel names, e.g., `C3,C4,P3,P4`. However, -you can also define differential channels, for example -`F3,C3-F3,C4-CF`, or even scaled versions of channels, for example -`F3,C3-F3,C4-F4,Pz-P3/2-P4/2`. The special channel name `TRG` refers to the -trigger channel, if you use that (most users would likely rely instead on native -LSL events from their stimulus presentation program instead). - -The reference option follows essentially the same syntax, and you could, for example, -set `C3/2+C4/2` here, which will cause the average of these two channels to be -subtracted from all other channels, or use the special names `A1` and `A2` (ears), -or `M1` and `M2` (mastoids), if these leads are present on your device. - -The last box is not an edit field but the console output when the program is -running. To start data acquisition, assuming your headset is ready, click the -Start button, and to stop it again, use the Stop button. When data acquisition -is running the program will show a progress bar in the bottom section of the -window. If any errors or warnings are generated by the DSI API, you will see -these listed in the console (some bening messages may also appear there). You -can get additional information from your vendor on what these error messages -mean and how to remediate them. - - -#### Troubleshooting - -Occasionally you may get diagnostic messages about some packets CRC checks or -etc having failed, and from what we can tell, this is not uncommon with Bluetooth, -especially at greater distances to the bluetooth dongle or with a lot of background -interference. Data acquisition will continue despite these messages. If this -is very frequent, you may check with the vendor to make sure that you are have -the optimal Bluetooth setup. - -If the underyling driver program crashes, you will see that the animated -status bar disappears, which is a way to quickly confirm whether acquisition is -still running or not. - -Note that, when you get an fatal error at startup, you will see the help text -of the `dsi2lsl` program (which is the actual workhorse behind -the GUI) in the console, and to see the error that caused it, you have to scroll -up above that help text. - -In general we recommend first trying the DSI-Streamer to make sure that it is -receiving data. This will also tell you the names of the channels on the headset -and the names of the reference channels, in case you want to override it. -Then try the simplest setting in `dsi2lsl` that can work (i.e., leave everything -other than the port unspecified). - -The most common cause of errors is that the port is either incorrect, or not -specified, or that the headset is not paired, or not in the right state (e.g., -not discoverable). Other common errors are using a channel name that does not -exist on the headset in the montage or reference listing. To retry connecting -after an error, it is best to turn the headset off (6-second press on the power -button), and then back on and into pairing mode. - -You may also get a spurious error about your chosen com port not being available, -even though it worked previously. In this case, it is possible that either you -have the DSI-Streamer running, or two instances of `dsi2lsl`, or the program may -not have shut down correctly (you can press Stop again or close and reopen the -GUI to be 100% sure). This is because the DSI's COM port cannot be shared -between two applications. - - -#### Bluetooth Quick Reference - -Note that this information should only be considered supplementary -- if these -instructions do not work for you, check with your vendor for the latest -information on how to pair the device with your OS. - -Make sure that your headset is in discoverable mode (LED blinking blue); refer -the device manual for that. - -##### Windows 10 (1836) - -Navigate to the "Bluetooth & other devices" settings screen (e.g., by right-clicking -the bluetooth icon in your status bar and selecting Show Bluetooth Devices). - -Check if the device is already listed (it may not be reported under the exact -same name as the headset model, e.g., a DSI-VR300 might report as DK1-0006) and -has "Paired" written under it. If it is not, go to "Add Bluetooth or other -devices", select Bluetooth, and then wait for the device to show up. If you are -not sure what is what, try with the headset turned off to get a list of other -devices, and then turn on pairing on the headset. You should now see another -device appear. - -Once you have your headset added, which should now show as paired, go to -"More Bluetooth options", and under COM Ports, you should see the ports -assigned to each device, including the headset. Note the name of the outgoing -port (which will often be the lower port number). Also be sure to include the -full spelling, e.g., COM4 (you need to enter this in the software). - -##### Linux (Ubuntu) - -On Ubuntu, open a terminal and enter the following to install the bluetooth -package if it is not yet present: - -```bash -sudo apt-get install bluez bluez-tools -``` +# Wearable Sensing LSL (dsi2lsl) -Now, list all the devices that are present on the system using hcitool. +--- -``` bash -hcitool scan -``` +## Build from Source Guide +This documentation last updated July, 2025. +> [!IMPORTANT] +> This build guide has only been tested using Windows. -Note the xx:xx:xx:xx:xx:xx device address, and use it in the following command -to bind it to `/dev/rfcomm0`, or a different device number if you have multiple -bluetooth adapters in your machine. +This program will only work on Windows. -``` bash -rfcomm bind /dev/rfcomm0 xx:xx:xx:xx:xx:xx 1 -``` +This guide provides instructions for locally compiling and running the Wearable Sensing dsi2lsl plugin. Following these steps will allow you to set up the necessary environment and dependencies to build two executable files from the source code: -The `/dev/rfcomm0` here is the device name that should later be passed to the -dsi2lsl application. +- ```dsi2lsl.exe:``` A command-line application that runs in the terminal. +- ```dsi2lslGUI.exe:``` The graphical user interface (GUI) application. +### Requirements +Before you can configure the build, you must download the necessary files and install the required software depencdencies on your system. +#### 📥Required Downloads +Download the following packages and place them in a single parent folder for easy access: -##### Mac OS +- [App-WearableSensing (LSL Plugin)](https://github.com/labstreaminglayer/App-WearableSensing): +The source code of the plugin itself. Download the repositroy as a zip file and extract it into your designated folder. +- [DSI API](https://wearablesensing.com/files/DSI-API_Current.zip): The current official API from Wearable Sensing +- [LSL Library (v1.16.2)](https://github.com/sccn/liblsl/releases): The latest version of Lab Streaming Layer. -First, add the DSI device through the Bluetooth manager. - -Nex, find the port using the following command, and look for a device that seems -to match your headset model name. +#### 🛠️System Dependencies +Ensure the following software is installed on your computer. +- [Qt5 Framework](https://www.qt.io/download-dev): The framework used for the application's graphical user interface (GUI). This guide was written using Qt 5.15.2. +- [Visual Studio 2022 Community Edition](https://visualstudio.microsoft.com/downloads/): Provides the C/C++ compiler (MSVC) needed to build the code. During installation, select the "Desktop development with C++" workload. +- [CMake's Official Website](https://cmake.org/download/): The tool used to generate the projecet build files. CMake is included with the Visual Studio workload mentioned above, so this download is not necessary. -```bash -cd /dev -ls -l tty.* -``` - -For us this returned `/dev/tty.DSI7-017-BluetoothSeria`. This name is what you -would pass into the dsi2lsl application as the port name. - -# Usage (CLI) - -The app runs in the same way on the three platforms. Below we illustrate the -different options that can be used. With the exception of the option --help, all -the others should be given in --NAME=VALUE format. Use a terminal to run the -examples. - -Notes: -* Linux users: since the app uses the serial port to exchange data with the - device, your account may need to have superuser privileges (use sudo before - any command). -* Linux and Mac users may need to add the path the the folder containing the - binary and libraries to the system library path, if so use the following command - -``` bash -export LD_LIBRARY_PATH=/your/path/to/CLI:$LD_LIBRARY_PATH -``` +### Building the Project +There are two main steps to building the executables: Configuration, where CMake finds your tools and dependencies, and Build, where it compiles the code. + +#### Initial File Setup +Before opening the project in VS Code, you must adjust the folder structure. -### --port -Specify the serial port with the --port option. If not specified, the API will -look for an environment variable called `DSISerialPort`. This example uses the -serial port that the device is binded to on GNU/Linux, for other platforms see -the previous section. +After following the initial setup open your main project folder (e.g., ```lsl-wearablesensing```) in Visual Studio Code. Your project structure should look like this. -``` bash -./dsi2lsl --port=/dev/rfcomm0 +``` +lsl-wearablesensing + |--- App-WearableSensing + |--- .vscode/settings.json + |--- CLI + |--- GUI + |--- DSI-API + |--- LSL Library + |--- CMakeLists.txt ``` -### --lsl-stream -Specify the name of the LSL stream on the network. If omitted, the default name -`WS-default` will be used +Edit Project Names/Path: Inside ```CMakeLists.txt```, take a look at lines 24-28. Since the dependencies you download might be earlier or later versions ensure the naming is correct. -``` bash -./dsi2lsl --port=/dev/rfcomm0 --lsl-stream-name=mystream -``` +#### CMake Configuration +You need to tell CMake where to find the LSL and Qt5 libraries you downloaded. In VS Code, the easiest way to do this is by creating a workspace settings file. -### --montage -Specify the montage, a list of channel specifications, comma-separated without -spaces, (can also be space-delimited, but then you would need to enclose the -option in quotes on the command-line). If omitted all available channels will be -used. You can also use expressions such as `F3-F4` for differential channels, -as well as, e.g., `C3/2+C4/2` to average the signal from -two or more leads into a channel. +1. Create a folder named .vscode in the root of your project folder if it doesn't already exist. -``` bash -./dsi2lsl --port=/dev/rfcomm0 --lsl-stream-name=mystream --montage=F3,C3,P3,P4,C4,F4,Pz -``` +2. Inside the .vscode folder, create a new file named settings.json. + +3. Copy and paste the following configuration into your settings.json file (Remember that the path to these folders is different from user to user): -### --reference -Specify the reference, the name of sensor (or linear combination of sensors, -without spaces) to be used as reference. Defaults to a \"traditional\" -averaged-ears or averaged-mastoids reference if available, or the factory -reference (typically Pz) if these sensors are not available. -``` bash -./dsi2lsl --port=/dev/rfcomm0 --lsl-stream-name=mystream --montage=F3,C3,P3,P4,C4,F4,Pz --reference Pz +```settings.json +{ + "cmake.configureArgs": [ + "-DLSL_DIR=C:\\Users\\Name\\Documents\\lsl-wearablesensing\\LSL Library\\lib\\cmake\\LSL", + "-DQt5_DIR=C:/Qt5/5.15.2/msvc2019_64/lib/cmake/Qt5" + ] +} ``` +* Note Qt5 can also be named Qt, it depends on what you named the folder during your Qt installation. -# Building dsi2lsl From Source +#### Select the Compiler (Kit) 🧰 +Now, you'll tell CMake which compiler to use. -### All Operating Systems +1. Open the VS Code Command Palette by pressing ```Ctrl+Shift+P```. -Before you can build this software, you currently have to download the SDK -binaries and headers for the device. The are available [here](https://wearablesensing.com/files/DSI-API_Current.zip). -These binaries are only needed to build the `dsi2lsl` program (the `dsi2lslgui` -GUI application is a separate program that launches the former). +2. Type and select ```>CMake: Select a Kit```. -Also make sure that you have downloaded the liblsl.dll, .so, or .dylib for your -operating system (which you can find side by side with the application binaries -on the Releases page). Again, these binaries are only needed by the `dsi2lsl` -program, and should go, together with the SDK files, into the `CLI` folder. +3. Choose the 64-bit compiler that matches your Visual Studio installation. It will typically be listed as ```Visual Studio Community 2022 release - amd64```. -### GNU/Linux +#### Select the Build Variant ⚙️ +Next, set the build type to Release. This is important for creating the final two executables. -Open a terminal and navigate to the CLI/ folder. +1. Open the Command Palette ```(Ctrl+Shift+P)```. -```bash -cd /your/path/to/CLI -``` +2. Type and select ```>CMake: Select Variant```. -Compile the project using GCC (be sure you have the gcc package installed on -your system). Note that this command assumes that your liblsl is called `liblsl.so` -(and not, e.g., `liblsl64.so`). +3. Choose ```Release``` from the options. -```bash -gcc -DDSI_PLATFORM=-Linux-x86_64 -o "dsi2lsl" dsi2lsl.c DSI_API_Loader.c -ldl -llsl -``` +#### Run the Configuration Step ▶️ (CMake Cofigure) +With the kit and variant selected, you can now generate the project build files. -## Mac OS +1. Open the Command Palette ```(Ctrl+Shift+P)```. -Open a terminal and navigate to the CLI/ folder. +2. Type and select ```>CMake: Configure```. -```bash -cd /your/path/to/CLI -``` +3. If your configuration was successful, the terminal should output something similar to ```Build file shave been written to: build``` -Compile the project using GCC (be sure you have the gcc package installed on -your system). Note that this command assumes that your liblsl is called `liblsl64.dylib` -(and not, e.g., `liblsl.dylib`). +#### Build the Project 🚀 (CMake Build) +With the kit and variant selected, you can now generate the project build files. +1. Open the Command Palette ```(Ctrl+Shift+P)```. -```bash -gcc -DDSI_PLATFORM=-Darwin-x86_64 -o "dsi2lsl" dsi2lsl.c DSI_API_Loader.c -ldl -llsl64 -``` +2. Type and select ```>CMake: Build```. +3. This will compile the project. Once it has finished you fill find ```dsi2lsl.exe``` and ```dsi2lslGUI.exe``` inside the ```build/Release``` folder. -Open a terminal and navigate to the CLI/ folder. +## Running the GUI Application +The command-line ```dsi2lsl.exe``` can be run directly from the terminal. However, ```dsi2lslGUI.exe``` requires additional files to be present in the same directory before it will open. -```bash -cd /your/path/to/CLI +The inside of the ```lsl-wearablesensing/build/Release``` should currently look like this. ``` +dsi2lsl.exe +dsi2lslGUI.exe +``` + +#### A Tip for Finding Files +A useful search utility can make this process much easier. This guide used the [Everything search tool](https://www.voidtools.com/) to instantly locate the neccessary files. + +#### Gathering the Dependency Files +You need to copy specific files from the Qt, DSI, and LSL folders into your project's ```build\Release``` directory. It's critical to choose the files that match the compiler you used for the build. For this guide, we used the ```64-bit MSVC 2019 compiler```, so all the file paths will contain ```msvc2019_64```. -## Windows +(```Qt5Core.dll, Qt5Gui.dll, Qt5Widgets.dll, qwindows.dll```) will be located in your Qt installation directory. +``` +Qt5\5.15.2\msvc2019_64\bin\Qt5Core.dll +Qt5\5.15.2\msvc2019_64\bin\Qt5Gui.dll +Qt5\5.15.2\msvc2019_64\bin\Qt5Widgets.dll +Qt5\5.15.2\msvc2019_64\plugins\platforms\qwindows.dll +``` -Compile the project. We used the out of the box MinGW distribution of gcc as the -compiler (see a tutorial [here](http://www.mingw.org/wiki/howto_install_the_mingw_gcc_compiler_suite)), -although MS Visual C++ and others may work as well. Note that this command assumes -that your liblsl is called `liblsl32.dll` (and not, e.g., `liblsl.dll`). +(```libDSI-Windows-x86_64.dll```) will be located in the DSI API folder. +> [!IMPORTANT] +> You will need to rename the file ```libDSI-Windows-x86_64.dll``` to ```libDSI.dll```. -```bash -gcc -DDSI_PLATFORM=-Windows-x86_32 -o "dsi2lsl.exe" dsi2lsl.c DSI_API_Loader.c -ldl -llsl32 +(```lsl.dll```) will be located in the LSL Library folder. +``` +DSI_API_v1.20.3_06202025\libDSI.dll +liblsl-1.16.2-Win_amd64\bin\lsl.dll ``` +Copy and paste the dependency files from their respective downloaded folders to the ```Release``` folder. +#### Final Folder Structure +After copying all the files, your ```build/Release``` directory should look as followed: +``` +platforms + |--- qwindows.dll +dsi2lsl.exe +dsi2lslGUI.exe +libDSI.dll +lsl.dll +Qt5Core.dll +Qt5Gui.dll +Qt5Widgets.dll +``` +With this setup in place, your now ready to launch the ```dsi2lslGUI.exe```. + +## Alternative: Build using bat -# Building the GUI from Source +Open the ```build-mingw.bat``` and change the paths according to your file structure. -The GUI includes a Qt Creator project file, which you can use to build this -program on your operating system. Note that you will need to download and install -the Qt SDK for this, which will also install Qt Creator. After this, double-click -the `.pro` file and follow the instructions in the Qt Creator. You may need also -a Microsoft Visual Studio installation (which is available for free). Build using the -build button (green triangle). Note that the program requires the `dsi2lsl` binary -(the CLI program) and its libraries in the same folder as the `dsi2lslgui` file, -so after you have created your build, move the files into the same folder to -test the program. +Run the following code in the terminal + +``` +.\build-mingw.bat +``` +This will create a ```mingwbuild``` folder with the executable and dependencies. diff --git a/README_old.md b/README_old.md new file mode 100644 index 0000000..419fade --- /dev/null +++ b/README_old.md @@ -0,0 +1,328 @@ +# Wearable Sensing LSL (dsi2lsl) + +This application supports several dry/wireless EEG headsets made by [Wearable Sensing](http://www.wearablesensing.com). + +You can find prebuilt binaries for Windows, Linux, and Mac OS on the Releases +page of this repository. For build instructions, see section [Building From Source](#building-dsi2lsl-from-source) below. + +*Note*: The program has been tested with the DSI-7 and the DSI-VR300 headsets, +but several other headset models should work, as well. + +## Usage + +To use this software, you do __not__ need the *DSI-Streamer* program, although it is +helpful to have at hand during headset setup (e.g., for checking impedances or +for troubleshooting). However, only one program can access the device at +a time, so keep in mind to turns the DSI Streamer off before using this application. + +Your headset, which is a Bluetooth device, has to be paired with your machine +in order to use it with dsi2lsl. The recommended procedure is to follow the +vendor instructions for this, which are different for the respective operating +systems. + +To check that your +headset is correctly paired, run the *DSI-Streamer* application by Wearable Sensing +and make sure that it can receive data (if it can not, dsi2lsl will also not work). +Note the port name that this program uses, which differs between OSes and machines +(e.g., it might be `COM4` on a Windows, but it could also be a much higher +port number if you work a lot with COM-port devices). The section +[Bluetooth Quick Reference](#bluetooth-quick-reference) +below has some additional information on pairing and looking up the port name. + +### Using The Graphical User Interface (GUI) + +Now that your headset is paired, and you have the port name, you can use +either the command-line interface or the graphical user interface. To use the +GUI, launch the program dsi2lslgui, which should open a window like this: + +![app-screen.png](app-screen.png) + +When filling in information, be sure to review the tooltips of the various +entry fields by hovering over them with the mouse. + +The only setting that you __must__ specify is the Port field, which defaults to blank. +Be sure to put here the correct port name. Follow the vendor's information on +how to retrieve this, or fall back to the [Bluetooth Quick Reference](#bluetooth-quick-reference) +section below. In short, on Windows this is typically a string like, e.g., `COM7`, +on Linux it is a device name such as `/dev/rfcomm0`, and on Mac OS, it is +a device name like `/dev/tty.DSI7-017-BluetoothSeria`. + +You can change the LSL Stream Name field freely, but this is not necessary for +correct operation. The stream name will be recorded to a file if you use the +Lab Recorder, so be aware to enter no personally identifiable information. Typically +one would enter the device name, or if you have multiple devices of the same type, +append a number or other unique identifier. + +Next, you can override the montage. The default "(use default)" is to record from all electrodes +and to use the reference that is available on the hardware (e.g., ear or mastoid, +etc.) as documented in your device manual. To override this, you use a syntax that is +interpreted by the headset software, and so, so for the most detailed information, +you can consult your vendor-provided documentation. In general, this entry field + ust be specified without any space characters. In the simplest case, this +is just a comma-separated list of channel names, e.g., `C3,C4,P3,P4`. However, +you can also define differential channels, for example +`F3,C3-F3,C4-CF`, or even scaled versions of channels, for example +`F3,C3-F3,C4-F4,Pz-P3/2-P4/2`. The special channel name `TRG` refers to the +trigger channel, if you use that (most users would likely rely instead on native +LSL events from their stimulus presentation program instead). + +The reference option follows essentially the same syntax, and you could, for example, +set `C3/2+C4/2` here, which will cause the average of these two channels to be +subtracted from all other channels, or use the special names `A1` and `A2` (ears), +or `M1` and `M2` (mastoids), if these leads are present on your device. + +The last box is not an edit field but the console output when the program is +running. To start data acquisition, assuming your headset is ready, click the +Start button, and to stop it again, use the Stop button. When data acquisition +is running the program will show a progress bar in the bottom section of the +window. If any errors or warnings are generated by the DSI API, you will see +these listed in the console (some bening messages may also appear there). You +can get additional information from your vendor on what these error messages +mean and how to remediate them. + + +#### Troubleshooting + +Occasionally you may get diagnostic messages about some packets CRC checks or +etc having failed, and from what we can tell, this is not uncommon with Bluetooth, +especially at greater distances to the bluetooth dongle or with a lot of background +interference. Data acquisition will continue despite these messages. If this +is very frequent, you may check with the vendor to make sure that you are have +the optimal Bluetooth setup. + +If the underyling driver program crashes, you will see that the animated +status bar disappears, which is a way to quickly confirm whether acquisition is +still running or not. + +Note that, when you get an fatal error at startup, you will see the help text +of the `dsi2lsl` program (which is the actual workhorse behind +the GUI) in the console, and to see the error that caused it, you have to scroll +up above that help text. + +In general we recommend first trying the DSI-Streamer to make sure that it is +receiving data. This will also tell you the names of the channels on the headset +and the names of the reference channels, in case you want to override it. +Then try the simplest setting in `dsi2lsl` that can work (i.e., leave everything +other than the port unspecified). + +The most common cause of errors is that the port is either incorrect, or not +specified, or that the headset is not paired, or not in the right state (e.g., +not discoverable). Other common errors are using a channel name that does not +exist on the headset in the montage or reference listing. To retry connecting +after an error, it is best to turn the headset off (6-second press on the power +button), and then back on and into pairing mode. + +You may also get a spurious error about your chosen com port not being available, +even though it worked previously. In this case, it is possible that either you +have the DSI-Streamer running, or two instances of `dsi2lsl`, or the program may +not have shut down correctly (you can press Stop again or close and reopen the +GUI to be 100% sure). This is because the DSI's COM port cannot be shared +between two applications. + + +#### Bluetooth Quick Reference + +Note that this information should only be considered supplementary -- if these +instructions do not work for you, check with your vendor for the latest +information on how to pair the device with your OS. + +Make sure that your headset is in discoverable mode (LED blinking blue); refer +the device manual for that. + +##### Windows 10 (1836) + +Navigate to the "Bluetooth & other devices" settings screen (e.g., by right-clicking +the bluetooth icon in your status bar and selecting Show Bluetooth Devices). + +Check if the device is already listed (it may not be reported under the exact +same name as the headset model, e.g., a DSI-VR300 might report as DK1-0006) and +has "Paired" written under it. If it is not, go to "Add Bluetooth or other +devices", select Bluetooth, and then wait for the device to show up. If you are +not sure what is what, try with the headset turned off to get a list of other +devices, and then turn on pairing on the headset. You should now see another +device appear. + +Once you have your headset added, which should now show as paired, go to +"More Bluetooth options", and under COM Ports, you should see the ports +assigned to each device, including the headset. Note the name of the outgoing +port (which will often be the lower port number). Also be sure to include the +full spelling, e.g., COM4 (you need to enter this in the software). + +##### Linux (Ubuntu) + +On Ubuntu, open a terminal and enter the following to install the bluetooth +package if it is not yet present: + +```bash +sudo apt-get install bluez bluez-tools +``` + +Now, list all the devices that are present on the system using hcitool. + +``` bash +hcitool scan +``` + +Note the xx:xx:xx:xx:xx:xx device address, and use it in the following command +to bind it to `/dev/rfcomm0`, or a different device number if you have multiple +bluetooth adapters in your machine. + +``` bash +rfcomm bind /dev/rfcomm0 xx:xx:xx:xx:xx:xx 1 +``` + +The `/dev/rfcomm0` here is the device name that should later be passed to the +dsi2lsl application. + + +##### Mac OS + +First, add the DSI device through the Bluetooth manager. + +Nex, find the port using the following command, and look for a device that seems +to match your headset model name. + +```bash +cd /dev +ls -l tty.* +``` + +For us this returned `/dev/tty.DSI7-017-BluetoothSeria`. This name is what you +would pass into the dsi2lsl application as the port name. + +# Usage (CLI) + +The app runs in the same way on the three platforms. Below we illustrate the +different options that can be used. With the exception of the option --help, all +the others should be given in --NAME=VALUE format. Use a terminal to run the +examples. + +Notes: +* Linux users: since the app uses the serial port to exchange data with the + device, your account may need to have superuser privileges (use sudo before + any command). +* Linux and Mac users may need to add the path the the folder containing the + binary and libraries to the system library path, if so use the following command + +``` bash +export LD_LIBRARY_PATH=/your/path/to/CLI:$LD_LIBRARY_PATH +``` + +### --port +Specify the serial port with the --port option. If not specified, the API will +look for an environment variable called `DSISerialPort`. This example uses the +serial port that the device is binded to on GNU/Linux, for other platforms see +the previous section. + +``` bash +./dsi2lsl --port=/dev/rfcomm0 +``` + +### --lsl-stream +Specify the name of the LSL stream on the network. If omitted, the default name +`WS-default` will be used + +``` bash +./dsi2lsl --port=/dev/rfcomm0 --lsl-stream-name=mystream +``` + +### --montage +Specify the montage, a list of channel specifications, comma-separated without +spaces, (can also be space-delimited, but then you would need to enclose the +option in quotes on the command-line). If omitted all available channels will be +used. You can also use expressions such as `F3-F4` for differential channels, +as well as, e.g., `C3/2+C4/2` to average the signal from +two or more leads into a channel. + +``` bash +./dsi2lsl --port=/dev/rfcomm0 --lsl-stream-name=mystream --montage=F3,C3,P3,P4,C4,F4,Pz +``` + +### --reference +Specify the reference, the name of sensor (or linear combination of sensors, +without spaces) to be used as reference. Defaults to a \"traditional\" +averaged-ears or averaged-mastoids reference if available, or the factory +reference (typically Pz) if these sensors are not available. + +``` bash +./dsi2lsl --port=/dev/rfcomm0 --lsl-stream-name=mystream --montage=F3,C3,P3,P4,C4,F4,Pz --reference Pz +``` + + +# Building dsi2lsl From Source + +### All Operating Systems + +Before you can build this software, you currently have to download the SDK +binaries and headers for the device. The are available [here](https://wearablesensing.com/files/DSI-API_Current.zip). +These binaries are only needed to build the `dsi2lsl` program (the `dsi2lslgui` +GUI application is a separate program that launches the former). + +Also make sure that you have downloaded the liblsl.dll, .so, or .dylib for your +operating system (which you can find side by side with the application binaries +on the Releases page). Again, these binaries are only needed by the `dsi2lsl` +program, and should go, together with the SDK files, into the `CLI` folder. + +### GNU/Linux + +Open a terminal and navigate to the CLI/ folder. + +```bash +cd /your/path/to/CLI +``` + +Compile the project using GCC (be sure you have the gcc package installed on +your system). Note that this command assumes that your liblsl is called `liblsl.so` +(and not, e.g., `liblsl64.so`). + +```bash +gcc -DDSI_PLATFORM=-Linux-x86_64 -o "dsi2lsl" dsi2lsl.c DSI_API_Loader.c -ldl -llsl +``` + +## Mac OS + +Open a terminal and navigate to the CLI/ folder. + +```bash +cd /your/path/to/CLI +``` + +Compile the project using GCC (be sure you have the gcc package installed on +your system). Note that this command assumes that your liblsl is called `liblsl64.dylib` +(and not, e.g., `liblsl.dylib`). + + +```bash +gcc -DDSI_PLATFORM=-Darwin-x86_64 -o "dsi2lsl" dsi2lsl.c DSI_API_Loader.c -ldl -llsl64 +``` + + +Open a terminal and navigate to the CLI/ folder. + +```bash +cd /your/path/to/CLI +``` + +## Windows + +Compile the project. We used the out of the box MinGW distribution of gcc as the +compiler (see a tutorial [here](http://www.mingw.org/wiki/howto_install_the_mingw_gcc_compiler_suite)), +although MS Visual C++ and others may work as well. Note that this command assumes +that your liblsl is called `liblsl32.dll` (and not, e.g., `liblsl.dll`). + +```bash +gcc -DDSI_PLATFORM=-Windows-x86_32 -o "dsi2lsl.exe" dsi2lsl.c DSI_API_Loader.c -ldl -llsl32 +``` + +# Building the GUI from Source + +The GUI includes a Qt Creator project file, which you can use to build this +program on your operating system. Note that you will need to download and install +the Qt SDK for this, which will also install Qt Creator. After this, double-click +the `.pro` file and follow the instructions in the Qt Creator. You may need also +a Microsoft Visual Studio installation (which is available for free). Build using the +build button (green triangle). Note that the program requires the `dsi2lsl` binary +(the CLI program) and its libraries in the same folder as the `dsi2lslgui` file, +so after you have created your build, move the files into the same folder to +test the program. + diff --git a/build-mingw.bat b/build-mingw.bat new file mode 100644 index 0000000..fdbc334 --- /dev/null +++ b/build-mingw.bat @@ -0,0 +1,104 @@ +:: To run this .bat use .\build-mingw.bat +:: Make sure mingw64 is installed and the paths are set correctly (Very Important!!!) + +@echo off +setlocal + +:: Adjust path to your 64-bit MinGW-w64 bin directory +set PATH=C:\ProgramData\mingw64\mingw64\bin;%PATH% + +:: Output folders - create mingwbuild folder +set OUT=mingwbuild +if not exist %OUT% mkdir %OUT% + +:: LSL lib and include paths +set LSL_INC="YOUR_PATH_TO_LIBLSL_INCLUDE" +set LSL_LIB="YOUR_PATH_TO_LIBLSL_LIB" +set QT_INC="YOUR_PATH_TO_QT5_MINGW64_INCLUDE" +set QT_LIB="YOUR_PATH_TO_QT5_MINGW_LIB" +set QT_BIN="YOUR_PATH_TO_QT5_MINGW64_BIN" + +:: Add Qt bin to PATH for tools +set PATH=%QT_BIN%;%PATH% + +:: Build dsi2lsl (console app) +echo Building dsi2lsl... +gcc CLI\dsi2lsl.c ^ + DSI_API_v1.20.3_06202025\DSI_API_Loader.c ^ + -I DSI_API_v1.20.3_06202025 ^ + -I %LSL_INC% ^ + -L %LSL_LIB% -llsl ^ + -o %OUT%\dsi2lsl.exe + +if %ERRORLEVEL% neq 0 ( + echo Failed to build dsi2lsl! + pause + exit /b 1 +) + +:: Create output directories for Qt tools +if not exist %OUT%\ui mkdir %OUT%\ui +if not exist %OUT%\moc mkdir %OUT%\moc + +:: Generate Qt UIC file (converts .ui to .h) +echo Generating Qt UIC files... +uic.exe GUI\mainwindow.ui -o %OUT%\ui\ui_mainwindow.h + +if %ERRORLEVEL% neq 0 ( + echo UIC failed! Check if Qt tools are in PATH. + pause + exit /b 1 +) + +:: Generate Qt MOC files +echo Generating Qt MOC files... +moc.exe GUI\mainwindow.h -o %OUT%\moc\moc_mainwindow.cpp + +if %ERRORLEVEL% neq 0 ( + echo MOC failed! Check if Qt tools are in PATH. + pause + exit /b 1 +) + +:: Build GUI app +echo Building GUI... +g++ GUI\main.cpp GUI\mainwindow.cpp %OUT%\moc\moc_mainwindow.cpp ^ + -I GUI -I %OUT%\ui -I %LSL_INC% ^ + -I %QT_INC% -I %QT_INC%\QtCore -I %QT_INC%\QtGui -I %QT_INC%\QtWidgets ^ + -L %LSL_LIB% -llsl ^ + -L %QT_LIB% -lQt5Core -lQt5Gui -lQt5Widgets ^ + -mwindows ^ + -o %OUT%\dsi2lslGUI.exe + +if %ERRORLEVEL% neq 0 ( + echo Failed to build GUI! + pause + exit /b 1 +) + +:: Copy MinGW runtime DLLs (needed for distribution) +echo Copying MinGW runtime DLLs... +copy "YOUR_PATH_TO_MINGW64_BIN_LIBGCC_S_SEH_1.DLL" "%OUT%\" >nul +copy "YOUR_PATH_TO_MINGW64_BIN_LIBSTDC++_6.DLL" "%OUT%\" >nul +copy "YOUR_PATH_TO_MINGW64_BIN_LIBWINPTHREAD_1.DLL" "%OUT%\" >nul + +:: Copy required DLLs +echo Copying required DLLs... +copy "%QT_BIN%\Qt5Core.dll" "%OUT%\" >nul +copy "%QT_BIN%\Qt5Gui.dll" "%OUT%\" >nul +copy "%QT_BIN%\Qt5Widgets.dll" "%OUT%\" >nul +copy "YOUR_PATH_TO_LIBLSL__BIN_LSL.DLL" "%OUT%\" >nul +copy "YOUR_PATH_TO_LIBDSI.DLL" "%OUT%\" >nul + +:: Copy Qt platform plugins (REQUIRED for Qt GUI apps!) +echo Copying Qt platform plugins... +if not exist %OUT%\platforms mkdir %OUT%\platforms +copy "YOUR_PATH_TO_QT5_MINGW64_PLUGINS_PLATFORMS_QWINDOWS.dll" "%OUT%\platforms\" >nul + +echo. +echo Build completed successfully! +echo Executables are in the %OUT% folder: +echo - %OUT%\dsi2lsl.exe +echo - %OUT%\dsi2lslGUI.exe +echo. +pause \ No newline at end of file