diff --git a/configure.ac b/configure.ac index b23d033..dbc7adb 100644 --- a/configure.ac +++ b/configure.ac @@ -160,6 +160,64 @@ AC_SUBST([TIA_LIBS], "$LIBS") LIBS=$save_LIBS +# BrainProducts RDA (Recorder/RecView) support +save_LIBS=$LIBS +AC_ARG_WITH([barv], AC_HELP_STRING([--with-barv], + [Support BrainProducts RDA (Recorder/RecView) backend @<:@default=check@:>@]), + [], [with_barv=check]) +AS_IF([test "x$with_barv" != xno], + [AC_SEARCH_LIBS([pthread_create], [pthread], [barv_support=yes], + [barv_support=no; if test "x$with_barv" != xcheck; then + AC_MSG_FAILURE([the pthread library is required for BrainProducts RDA (Recorder/RecView) support]) + fi])], [barv_support=no]) +AM_CONDITIONAL([BARV_SUPPORT], [test "x$barv_support" = xyes]) +AC_SUBST([BARV_LIBS], "$LIBS") +LIBS=$save_LIBS + +# BBT support +save_LIBS=$LIBS +AC_ARG_WITH([bbt], AC_HELP_STRING([--with-bbt], + [Support for BBT backend @<:@default=no@:>@]), + [], [with_bbt=no]) +AS_IF([test "x$with_bbt" != xno], + AC_SEARCH_LIBS([str2ba], [bluetooth], [bbt_support=yes], + [bbt_support=no; if test "x$with_bbt" != xcheck; then + AC_MSG_FAILURE([bluetooth library required for BBT support]) + fi]), [bbt_support=no]) +AM_CONDITIONAL([BBT_SUPPORT], [test "x$bbt_support" = xyes]) +AC_SUBST([BBT_LIBS], "$LIBS") +LIBS=$save_LIBS + +# gTecNet support +save_LIBS=$LIBS +AC_ARG_WITH([gtecnet], AC_HELP_STRING([--with-gtecnet], + [Support for gTecNet backend @<:@default=no@:>@]), + [], [with_gtecnet=no]) +AS_IF([test "x$with_gtecnet" != xno], + AC_SEARCH_LIBS([gtecnal_StartAcquisition], [gtecnalang], [gtecnet_support=yes], + [gtecnet_support=no; if test "x$with_gtecnet" != xcheck; then + AC_MSG_FAILURE([libgtecnalang library required for gtecnet support]) + fi]), [gtecnet_support=no]) +AM_CONDITIONAL([GTECNET_SUPPORT], [test "x$gtecnet_support" = xyes]) +AC_SUBST([GTECNET_LIBS], "$LIBS") +LIBS=$save_LIBS + + +# Arduino support +save_LIBS=$LIBS +AC_ARG_WITH([arduino], AC_HELP_STRING([--with-arduino], + [Support for Arduino backend @<:@default=no@:>@]), + [], [with_arduino=no]) +AS_IF([test "x$with_arduino" != xno], + AC_SEARCH_LIBS([pthread_create], [pthread], [arduino_support=yes], + [arduino_support=no; if test "x$with_arduino" != xcheck; then + AC_MSG_FAILURE([pthread library required for arduino support]) + fi]), [arduino_support=no]) +AM_CONDITIONAL([ARDUINO_SUPPORT], [test "x$arduino_support" = xyes]) +AC_SUBST([ARDUINO_LIBS], "$LIBS") +LIBS=$save_LIBS + + AX_DEFINE_DIR([LIBDIR], [libdir], [Installed library path]) AX_DEFINE_DIR([SYSCONFDIR], [sysconfdir], [System configuration files path]) @@ -193,5 +251,8 @@ Configuration summary gTec support : $gtec_support Neurosky support : $neurosky_support TobiIA support : $tia_support + BARV support : $barv_support + BBT support : $bbt_support + gTecNet support : $gtecnet_support + Arduino support : $arduino_support " - diff --git a/doc/Makefile.am b/doc/Makefile.am index cdade38..7138e61 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -11,7 +11,8 @@ MANPAGES = egd_get_data.3 egd_get_available.3 \ EXTRA_DIST = $(MANPAGES) \ eegdev-biosemi.5 eegdev-datafile.5 \ - eegdev-neurosky.5 eegdev-gtec.5 eegdev-tobiia.5 + eegdev-neurosky.5 eegdev-gtec.5 eegdev-tobiia.5 \ + eegdev-bbt.5 eegdev-barv.5 man_MANS = if BUILD_CORELIB @@ -38,3 +39,19 @@ endif if TIA_SUPPORT man_MANS += eegdev-tobiia.5 endif + +if BBT_SUPPORT +man_MANS += eegdev-bbt.5 +endif + +if BARV_SUPPORT +man_MANS += eegdev-barv.5 +endif + +if GTECNET_SUPPORT +man_MANS += eegdev-gtecnet.5 +endif + +if ARDUINO_SUPPORT +man_MANS += eegdev-arduino.5 +endif diff --git a/doc/eegdev-arduino.5 b/doc/eegdev-arduino.5 new file mode 100644 index 0000000..35dfeaf --- /dev/null +++ b/doc/eegdev-arduino.5 @@ -0,0 +1,24 @@ +.\"Copyright 2012 (c) EPFL +.TH EEGDEV-ARDUINO 5 2016 "EPFL" "EEGDEV library manual" +.SH NAME +eegdev-arduino - eegdev backend for an Arduino board, single channel A0 analog input +.SH DESCRIPTION +.LP +The \fBarduino\fP plugin implements the eegdev backend for acquistion from a single analog +input of an Arduino Mega board. Data acquisition is USB serial. +.SH CONFIGURATION +.LP +This plugin supports two options. The default value will be used +if none of the configuration files nor the configuration string (see +\fBegd_open\fP(3)) specify the option. +.TP +.B port +Serial port to read from. The default value is /dev/ttyACM0. +.TP +.B refmv +Reference voltage of the Arduino board. This is also the maximum voltage that can be measured. It has to be set also on the Arduino board side. +.TP +.SH "SEE ALSO" +.BR egd_open (3), +.BR eegdev-options (5) + diff --git a/doc/eegdev-barv.5 b/doc/eegdev-barv.5 new file mode 100644 index 0000000..51ab060 --- /dev/null +++ b/doc/eegdev-barv.5 @@ -0,0 +1,35 @@ +.\"Copyright 2014 (c) EPFL +.TH EEGDEV-BARV 5 2014 "EPFL" "EEGDEV library manual" +.SH NAME +eegdev-barv - eegdev backend for BrainAmp RDA server +.SH DESCRIPTION +.LP +The \fBbarv\fP plugin implements the backend for the eegdev library for +BrainAmp Recorder and RecView RDA servers. +.SH CONFIGURATION +.LP +This plugin supports two options. The default values will be used +if none of the configuration files nor the configuration string (see +\fBegd_open\fP(3)) specify the option. +.TP +.B host +IP address of the machine running the Recorder/RecView software and doing +the actual data acquisition. The default value is 10.66.99.11. +.TP +.B port +Port number of the acquisition socket. This option is critical as it will +specify automatically whether eegdev will receive data from the Recorder in +float format (51234), in integer format (51244) or from the RecView (51254). +The default value is 51254. +.SH FILES +.IP "/etc/eegdev/eegdev.conf" 4 +.PD +Shared configuration file. +.IP "/etc/eegdev/biosemi.conf" 4 +.PD +Configuration file loaded when the plugin is used. The settings specified +here overrides the settings in the shared configuration file. +.SH "SEE ALSO" +.BR egd_open (3), +.BR eegdev-options (5) + diff --git a/doc/eegdev-bbt.5 b/doc/eegdev-bbt.5 new file mode 100644 index 0000000..9e8f568 --- /dev/null +++ b/doc/eegdev-bbt.5 @@ -0,0 +1,24 @@ +.\"Copyright 2012 (c) EPFL +.TH EEGDEV-BBT 5 2012 "EPFL" "EEGDEV library manual" +.SH NAME +eegdev-bbt - eegdev backend for BitBrain Technologies dry electrode cap +.SH DESCRIPTION +.LP +The \fBbbt\fP plugin implements the backend for the eegdev library for +a custom BitBrain Technologies dry electrode cap, custom made for CNBI EPFL +and NISSAN.The cap supports 16 EEG channels (contact CNBI for electrode placement +in the 10-20 system), 6 EMG channels and 1 trigger input channel. Data acquisition +is wireless (bluetooth). +.SH CONFIGURATION +.LP +This plugin supports a single option. The default value will be used +if none of the configuration files nor the configuration string (see +\fBegd_open\fP(3)) specify the option. +.TP +.B address +Device bluetooth address of the BBT cap. The default value is 00:06:66:A0:4D:B7. +.TP +.SH "SEE ALSO" +.BR egd_open (3), +.BR eegdev-options (5) + diff --git a/doc/eegdev-gtecnet.5 b/doc/eegdev-gtecnet.5 new file mode 100644 index 0000000..e8a6859 --- /dev/null +++ b/doc/eegdev-gtecnet.5 @@ -0,0 +1,63 @@ +.\"Copyright 2015 (c) EPFL +.TH EEGDEV-GTEC 5 2015 "EPFL" "EEGDEV library manual" +.SH NAME +eegdev-gtecnet - eegdev backend for g.tec g.NEEDaccess server software. +.SH DESCRIPTION +.LP +The \fBgtecnet\fP plugin implements the backend for the eegdev library for +the g.NEEDaccess server which is able to stream data from following g.tec EEG amplifiers: g.USBamp, g.HIamp and g.Nautilus. +\fBAttention:\fP so far only the g.HIamp is implemented. +.SH CONFIGURATION +.LP +This plugin supports several options. The default values will be used +if neither a configuration file nor the configuration string (see +\fBegd_open\fP(3)) specifies the options. +.TP +.B hostIP +Network IP of the g.NEEDaccess server. Default value: "192.168.1.1". +.TP +.B hostport +Communication port on the g.NEEDaccess server. Default value: "50223". +.TP +.B localIP +Network IP of the local computer. Default value: "192.168.1.10". +.TP +.B localport +Communication port on the g.NEEDaccess server. Make sure that the port is open -> check iptables. Default value: "50220". +.TP +.B samplerate +Desired sampling frequency used to acquire the data. Supported sampling rates are: + +g.USBamp [32;64;128;256;512;600;1200;2400;4800;9600;19200;38400] +.br +g.HIamp [256;512;600;1200;2400;4800;9600;19200;38400] +.br +g.Nautilus [250;500] + +Default value: "512". +.TP +.B bandpass +Filter ID of desired hardware bandpass filter. \fBAttention:\fP Filter ID depends on cutoff frequencies, sampling rate and filter order. Please use the +table provided at /mnt/shared/software/third/gtec/g.NEEDaccess_DataServer/g.NEEDaccess - Device Filter Lists.xlsx. The option "no filtering" is encoded by the ID "-1". Default value: "32" (= 512 Hz -> 0.01 - 100 Hz). +.TP +.B notch +Filter ID of desired hardware bandpass filter. \fBAttention:\fP Filter ID depends on central frequency, sampling rate and filter order. Please use the +table provided at /mnt/shared/software/third/gtec/g.NEEDaccess_DataServer/g.NEEDaccess - Device Filter Lists.xlsx. The option "no filtering" is encoded by the ID "-1". Default value: "4" (= 512 Hz -> 48 - 52 Hz). +.TP +.B usedefmap +Boolean flag determining whether the default (most common) CNBI electrode configurations should be used for each device. "1" (true) enforces default maps (per CNBI conventions) and "0" labels the channels arithmetically (1:N). This option only affects the channel labels as appearing in the eegview scope and elsewhere. Default value: "1" (use default CNBI 10-20 configurations). + + +.SH FILES +.IP "/etc/eegdev/eegdev.conf" 4 +.PD +Shared configuration file. +.IP "/etc/eegdev/gtecnet.conf" 4 +.PD +Configuration file loaded when the \fBgtecnet\fP plugin is used. The +settings specified here overrides the settings in the shared configuration +file. +.SH "SEE ALSO" +.BR egd_open (3), +.BR eegdev-options (5) + diff --git a/doc/eegdev-gtecnet.5~ b/doc/eegdev-gtecnet.5~ new file mode 100644 index 0000000..e8a6859 --- /dev/null +++ b/doc/eegdev-gtecnet.5~ @@ -0,0 +1,63 @@ +.\"Copyright 2015 (c) EPFL +.TH EEGDEV-GTEC 5 2015 "EPFL" "EEGDEV library manual" +.SH NAME +eegdev-gtecnet - eegdev backend for g.tec g.NEEDaccess server software. +.SH DESCRIPTION +.LP +The \fBgtecnet\fP plugin implements the backend for the eegdev library for +the g.NEEDaccess server which is able to stream data from following g.tec EEG amplifiers: g.USBamp, g.HIamp and g.Nautilus. +\fBAttention:\fP so far only the g.HIamp is implemented. +.SH CONFIGURATION +.LP +This plugin supports several options. The default values will be used +if neither a configuration file nor the configuration string (see +\fBegd_open\fP(3)) specifies the options. +.TP +.B hostIP +Network IP of the g.NEEDaccess server. Default value: "192.168.1.1". +.TP +.B hostport +Communication port on the g.NEEDaccess server. Default value: "50223". +.TP +.B localIP +Network IP of the local computer. Default value: "192.168.1.10". +.TP +.B localport +Communication port on the g.NEEDaccess server. Make sure that the port is open -> check iptables. Default value: "50220". +.TP +.B samplerate +Desired sampling frequency used to acquire the data. Supported sampling rates are: + +g.USBamp [32;64;128;256;512;600;1200;2400;4800;9600;19200;38400] +.br +g.HIamp [256;512;600;1200;2400;4800;9600;19200;38400] +.br +g.Nautilus [250;500] + +Default value: "512". +.TP +.B bandpass +Filter ID of desired hardware bandpass filter. \fBAttention:\fP Filter ID depends on cutoff frequencies, sampling rate and filter order. Please use the +table provided at /mnt/shared/software/third/gtec/g.NEEDaccess_DataServer/g.NEEDaccess - Device Filter Lists.xlsx. The option "no filtering" is encoded by the ID "-1". Default value: "32" (= 512 Hz -> 0.01 - 100 Hz). +.TP +.B notch +Filter ID of desired hardware bandpass filter. \fBAttention:\fP Filter ID depends on central frequency, sampling rate and filter order. Please use the +table provided at /mnt/shared/software/third/gtec/g.NEEDaccess_DataServer/g.NEEDaccess - Device Filter Lists.xlsx. The option "no filtering" is encoded by the ID "-1". Default value: "4" (= 512 Hz -> 48 - 52 Hz). +.TP +.B usedefmap +Boolean flag determining whether the default (most common) CNBI electrode configurations should be used for each device. "1" (true) enforces default maps (per CNBI conventions) and "0" labels the channels arithmetically (1:N). This option only affects the channel labels as appearing in the eegview scope and elsewhere. Default value: "1" (use default CNBI 10-20 configurations). + + +.SH FILES +.IP "/etc/eegdev/eegdev.conf" 4 +.PD +Shared configuration file. +.IP "/etc/eegdev/gtecnet.conf" 4 +.PD +Configuration file loaded when the \fBgtecnet\fP plugin is used. The +settings specified here overrides the settings in the shared configuration +file. +.SH "SEE ALSO" +.BR egd_open (3), +.BR eegdev-options (5) + diff --git a/doc/egd_open.3 b/doc/egd_open.3 new file mode 100644 index 0000000..c5773c7 --- /dev/null +++ b/doc/egd_open.3 @@ -0,0 +1,79 @@ +.\"Copyright 2011 (c) EPFL +.TH EGD_OPEN 3 2010 "EPFL" "EEGDEV library manual" +.SH NAME +egd_open - Open an EEG device +.SH SYNOPSIS +.LP +.B #include +.sp +.BI "struct eegdev* egd_open(const char* " devstring ");" +.br +.SH DESCRIPTION +.LP +\fBegd_open\fP() opens a EEG device according to the \fIdevstring\fP +description. If \fIdevstring\fP is NULL, the first device supported by the +library will be opened. +.LP +The \fIdevstring\fP argument specifies the type of EEG device required to be +opened and the parameters parameters which the system should be configured +with (overriding defaults settings). The syntax of the string is defined in +\fBeegdev-open-options\fP(5). +.LP +The syntax may change in future releases and programs should not rely on it +to configure the device. It is provided only to users of the programs to +select the EEG acquisition device and configure it. Programs using the +eegdev library should pass the string provided by the users untouched. +.LP +\fIdevstring\fP is not the only way to provides configuration information +about the device to open. The configuration is read from the configuration +files. The final values of the settings provided to the opening function are +defined (and overriden) in the following order: +.IP " *" 3 +default values +.IP " *" 3 +shared configuration file +.IP " *" 3 +device specific configuration file +.IP " *" 3 +\fIdevstring\fP +.LP +This order indicates that a setting value specified in \fIdevstring\fP +will always override any setting value defined by other mean. +.SH "RETURN VALUE" +.LP +The function returns a pointer to the opened EEG device in case of success. +Otherwise NULL is returned \fIerrno\fP is set accordingly. +.SH ERRORS +.LP +\fBegd_open\fP() will fail if: +.TP +.B ENOSYS +the device part of \fIdevstring\fP does not refer to a device supported by +any of the installed eegdev plugin modules. +.TP +.B EINVAL +one of the option specified in \fIdevstring\fP is unknown. +.TP +.B ENODEV +The specified device is not connected. +.TP +.B EBUSY +The specified device is already in use. +.TP +.B ECHILD +The specified device needs an auxiliary child process whose executable +file cannot be found. +.SH ENVIRONMENT +.IP "\fBEEGDEV_PLUGINS_DIR\fP" 4 +.PD +This variable controls which folder should be search to find plugin modules. +If unset, they will be searched in the subfolder \fBeegdev\fP of the +installation folder of the libraries. +.IP "\fBEEGDEV_CONF_DIR\fP" 4 +.PD +This variable controls which folder should be search to find the +configuration files. If unset, they will be searched in +\fB/usr/local/etc/eegdev\fP. +.SH "SEE ALSO" +.BR egd_close (3), +.BR eegdev-open-options (5) diff --git a/src/core/conffile.h b/src/core/conffile.h index 5e696cd..68e4bb3 100644 --- a/src/core/conffile.h +++ b/src/core/conffile.h @@ -1,7 +1,7 @@ /* Copyright (C) 2012 EPFL (Ecole Polytechnique Fédérale de Lausanne) + Copyright (C) 2012 Nicolas Bourdaud Laboratory CNBI (Chair in Non-Invasive Brain-Machine Interface) - Nicolas Bourdaud This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -19,7 +19,14 @@ #ifndef CONFFILE_H #define CONFFILE_H -#define NTOK 2 +#include + +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +#define NTOK 10 #define TOKEN_MAXLEN 64 struct cfdata { struct egdi_config* cf; @@ -30,4 +37,23 @@ struct cfdata { }; -#endif //CONFFILE_h +static inline +const char* cfd_push_string(struct cfdata* pp, const char* str) +{ + + if ((strlen(str)>TOKEN_MAXLEN-1) || (pp->itok == NTOK)) + return NULL; + + strcpy(pp->tokbuff[pp->itok], str); + return pp->tokbuff[pp->itok++]; +} + + +static inline +void cfd_pop_string(struct cfdata* pp, int numel) +{ + pp->itok -= numel; +} + + +#endif //CONFFILE_H diff --git a/src/core/conffile.l b/src/core/conffile.l index 3f3aec3..2004273 100644 --- a/src/core/conffile.l +++ b/src/core/conffile.l @@ -28,8 +28,8 @@ #include #include -#include "conffile.tab.h" #include "conffile.h" +#include "conffile.tab.h" %} %% @@ -37,7 +37,7 @@ struct cfdata* pp = yyextra; %} -[a-zA-Z0-9_.+-/]* { +[a-zA-Z0-9_.+-/:]* { if (strlen(yytext)>TOKEN_MAXLEN-1) return -1; diff --git a/src/core/conffile.y b/src/core/conffile.y index 1133eab..d4743c3 100644 --- a/src/core/conffile.y +++ b/src/core/conffile.y @@ -17,9 +17,11 @@ along with this program. If not, see . */ %define api.pure +%no-lines %name-prefix "cff_" %defines "conffile.tab.h" %parse-param { struct cfdata *pp } +%lex-param { yyscan_t cfscaninfo } %{ #if HAVE_CONFIG_H # include @@ -28,6 +30,12 @@ #include #include #include "configuration.h" +#include "conffile.h" +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif +#include "configuration.h" %} %union value { @@ -37,7 +45,7 @@ %{ #include "conffile.lex.h" #include "conffile.h" -#define YYLEX_PARAM pp->scaninfo +#define cfscaninfo pp->scaninfo static int yyerror(struct cfdata *pp, const char* s); %} diff --git a/src/core/confline.h b/src/core/confline.h index df1410e..b4bf61b 100644 --- a/src/core/confline.h +++ b/src/core/confline.h @@ -19,7 +19,14 @@ #ifndef CONFLINE_H #define CONFLINE_H -#define NTOK 2 +#include + +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif + +#define NTOK 10 #define TOKEN_MAXLEN 64 struct cfldata { struct egdi_config* cf; diff --git a/src/core/confline.l b/src/core/confline.l index 8cd6562..4472f97 100644 --- a/src/core/confline.l +++ b/src/core/confline.l @@ -28,8 +28,8 @@ #include #include -#include "confline.tab.h" #include "confline.h" +#include "confline.tab.h" %} %% @@ -37,7 +37,7 @@ struct cfldata* pp = yyextra; %} -[a-zA-Z0-9_.+-/]* { +[a-zA-Z0-9_.+-/:]* { if (strlen(yytext)>TOKEN_MAXLEN-1) return -1; diff --git a/src/core/confline.y b/src/core/confline.y index 1412d0d..46437c7 100644 --- a/src/core/confline.y +++ b/src/core/confline.y @@ -17,9 +17,11 @@ along with this program. If not, see . */ %define api.pure +%no-lines %name-prefix "cfl_" %defines "confline.tab.h" %parse-param { struct cfldata *pp } +%lex-param { yyscan_t cflscaninfo } %{ #if HAVE_CONFIG_H # include @@ -28,6 +30,12 @@ #include #include #include "configuration.h" +#include "confline.h" +#ifndef YY_TYPEDEF_YY_SCANNER_T +#define YY_TYPEDEF_YY_SCANNER_T +typedef void* yyscan_t; +#endif +#include "configuration.h" %} %union value { @@ -37,7 +45,7 @@ %{ #include "confline.lex.h" #include "confline.h" -#define YYLEX_PARAM pp->scaninfo +#define cflscaninfo pp->scaninfo static int yyerror(struct cfldata *pp, const char* s); %} diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index de92b9d..963bf7d 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -34,3 +34,27 @@ pkglib_LTLIBRARIES += tobiia.la endif tobiia_la_SOURCES = tobiia.c device-helper.h device-helper.c tobiia_la_LIBADD = $(TIA_LIBS) $(GNULIB) $(LIBSOCKET) $(GETADDRINFO_LIB) + +if BARV_SUPPORT +pkglib_LTLIBRARIES += barv.la +endif +barv_la_SOURCES = barv.c RecorderRDA.h device-helper.h device-helper.c +barv_la_LIBADD = $(BARV_LIBS) $(GNULIB) $(LIBSOCKET) $(GETADDRINFO_LIB) + +if BBT_SUPPORT +pkglib_LTLIBRARIES += bbt.la +endif +bbt_la_SOURCES = bbt.c device-helper.h device-helper.c +bbt_la_LIBADD = $(BBT_LIBS) $(GNULIB) + +if GTECNET_SUPPORT +pkglib_LTLIBRARIES += gtecnet.la +endif +gtecnet_la_SOURCES = gtecnet.c device-helper.h device-helper.c +gtecnet_la_LIBADD = $(GTECNET_LIBS) $(GNULIB) + +if ARDUINO_SUPPORT +pkglib_LTLIBRARIES += arduino.la +endif +arduino_la_SOURCES = arduino.c device-helper.h device-helper.c +arduino_la_LIBADD = $(ARDUINO_LIBS) $(GNULIB) diff --git a/src/plugins/RecorderRDA.h b/src/plugins/RecorderRDA.h new file mode 100644 index 0000000..6d79aa3 --- /dev/null +++ b/src/plugins/RecorderRDA.h @@ -0,0 +1,98 @@ +#ifndef _INC_RECORDERRDA +#define _INC_RECORDERRDA +// MODULE: RDA.h +//: written by: Henning Nordholz +//+ date: 14-Nov-00 +//+ +//+ Description: +//+ Vision Recorder +//. Remote Data Access (RDA) structs and constants + +#include + +#pragma pack(1) +#ifndef ULONG +typedef uint32_t ULONG; +#endif + +#ifndef GUID +typedef unsigned char GUID[16]; +#endif + +//#define GUID_RDAHeader {0x4358458e, 0xc996, 0x4c86, 0xaf, 0x4a, 0x98, 0xbb, 0xf6, 0xc9, 0x14, 0x50} +const GUID GUID_RDAHeader = {0x4358458e, 0xc996, 0x4c86, 0xaf, 0x4a, 0x98, 0xbb, 0xf6, 0xc9, 0x14, 0x50}; + +// All numbers are sent in little endian format. + +// Unique identifier for messages sent to clients +// {4358458E-C996-4C86-AF4A-98BBF6C91450} +// As byte array (16 bytes): 8E45584396C9864CAF4A98BBF6C91450 +//DEFINE_GUID(GUID_RDAHeader, +//0x4358458e, 0xc996, 0x4c86, 0xaf, 0x4a, 0x98, 0xbb, 0xf6, 0xc9, 0x14, 0x50); + +typedef struct +//; A single marker in the marker array of RDA_MessageData +{ + ULONG nSize; // Size of this marker. + ULONG nPosition; // Relative position in the data block. + ULONG nPoints; // Number of points of this marker + int32_t nChannel; // Associated channel number (-1 = all channels). + char sTypeDesc[1]; // Type, description in ASCII delimited by '\0'. +} RDA_Marker; + + +typedef struct +//; Message header +{ + GUID guid; // Always GUID_RDAHeader + ULONG nSize; // Size of the message block in bytes including this header + ULONG nType; // Message type. +} RDA_MessageHeader; + + +// **** Messages sent by the RDA server to the clients. **** +typedef struct +//; Setup / Start infos, Header -> nType = 1 +{ + RDA_MessageHeader header; //Trying to replicate struct inheritance + ULONG nChannels; // Number of channels + double dSamplingInterval; // Sampling interval in microseconds + double dResolutions[1]; // Array of channel resolutions -> double dResolutions[nChannels] + // coded in microvolts. i.e. RealValue = resolution * A/D value + char sChannelNames[1]; // Channel names delimited by '\0'. The real size is + // larger than 1. +} RDA_MessageStart; + + +typedef struct +//; Block of 16-bit data, Header -> nType = 2, sent only from port 51234 +{ + RDA_MessageHeader header; //Trying to replicate struct inheritance + ULONG nBlock; // Block number, i.e. acquired blocks since acquisition started. + ULONG nPoints; // Number of data points in this block + ULONG nMarkers; // Number of markers in this data block + short nData[1]; // Data array -> short nData[nChannels * nPoints], multiplexed + RDA_Marker Markers[1]; // Array of markers -> RDA_Marker Markers[nMarkers] +} RDA_MessageData; + +typedef struct +//; Data acquisition has been stopped. // Header -> nType = 3 +{ + RDA_MessageHeader header; //Trying to replicate struct inheritance +} RDA_MessageStop; + +typedef struct +//; Block of 32-bit floating point data, Header -> nType = 4, sent only from port 51244 +{ + RDA_MessageHeader header; //Trying to replicate struct inheritance + ULONG nBlock; // Block number, i.e. acquired blocks since acquisition started. + ULONG nPoints; // Number of data points in this block + ULONG nMarkers; // Number of markers in this data block + float fData[1]; // Data array -> float fData[nChannels * nPoints], multiplexed + RDA_Marker Markers[1]; // Array of markers -> RDA_Marker Markers[nMarkers] +} RDA_MessageData32; +#pragma pack() +// **** End Messages sent by the RDA server to the clients. **** + + +#endif //_INC_RECORDERRDA diff --git a/src/plugins/arduino.c b/src/plugins/arduino.c new file mode 100644 index 0000000..f3f6acf --- /dev/null +++ b/src/plugins/arduino.c @@ -0,0 +1,295 @@ +/* + Copyright (C) 2010-2012 EPFL (Ecole Polytechnique Fédérale de Lausanne) + Laboratory CNBI (Chair in Non-Invasive Brain-Machine Interface) + Nicolas Bourdaud + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ +#if HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct arduino_eegdev { + struct devmodule dev; + struct termios tty; + pthread_t thread_id; + int pid; + float refmv; + pthread_mutex_t acqlock; + unsigned int runacq; + char* pname; +}; + +#define get_arduino(dev_p) ((struct arduino_eegdev*)(dev_p)) + +#define NCH 1 + +static const char arduinolabel[NCH][8] = { + "EMG1" +}; +static const char arduinounit[] = "mV"; +static const char arduinotransducter[] = "EMG electrode"; + +static const union gval arduino_scales[EGD_NUM_DTYPE] = { + [EGD_INT32] = {.valint32_t = 1}, + [EGD_FLOAT] = {.valfloat = 1.0f}, // in uV + [EGD_DOUBLE] = {.valdouble = 1.0} // in uV +}; +static const int arduino_provided_stypes[] = {EGD_SENSOR}; + +enum {OPT_PORT, OPT_REFMV,NUMOPT}; +static const struct egdi_optname arduino_options[] = { + [OPT_PORT] = {.name = "port", .defvalue = "/dev/ttyACM0"}, + [OPT_REFMV] = {.name = "refmv", .defvalue = "5000"}, + {.name = NULL} +}; + +static void* arduino_read_fn(void* arg) +{ + struct arduino_eegdev* arduinodev = arg; + const struct core_interface* restrict ci = &arduinodev->dev.ci; + int runacq; + + fprintf(stdout,"REF = %f\n",arduinodev->refmv); + + /* Allocate memory for read buffer */ + char header; + char lowByte; + char highByte; + float* fsample = (float*)malloc(1*sizeof(float)); + + struct timeval start, end; + while (1) { + pthread_mutex_lock(&(arduinodev->acqlock)); + runacq = arduinodev->runacq; + pthread_mutex_unlock(&(arduinodev->acqlock)); + if (!runacq) + break; + + /* *** READ *** */ + gettimeofday(&start,NULL); + // Read 1-byte header + int n = read(arduinodev->pid, &header , 1); + if(n!=1) + fprintf(stdout,"Error reading: %s\n",strerror(errno)); + if(header != '\t') + continue; + // Read highByte + n = read(arduinodev->pid, &highByte , 1); + if(n!=1) + fprintf(stdout,"Error reading: %s\n",strerror(errno)); + // Read lowhByte + n = read(arduinodev->pid, &lowByte , 1); + if(n!=1) + fprintf(stdout,"Error reading: %s\n",strerror(errno)); + gettimeofday(&end,NULL); + float ElapsedTime = (float)1000*(end.tv_sec-start.tv_sec)+(end.tv_usec-start.tv_usec)/1000; + fprintf(stdout,"One frame/sample in %f milliseconds\n", ElapsedTime); + + // Convert the two measurement bytes to int + int number = lowByte | highByte << 8; + //Convert int to float applying the scaling + fsample[0] = ( arduinodev->refmv)*((float)(number)/1023.0f); //in mV + fprintf(stdout,"%f\n",1000.0*fsample[0]); + + // Update the eegdev structure with the new data + if (ci->update_ringbuffer(&(arduinodev->dev), fsample, NCH*sizeof(float))) + break; + + } + return NULL; +error: + ci->report_error(&(arduinodev->dev), EIO); + return NULL; +} + + +static +int arduino_set_capability(struct arduino_eegdev* arduinodev) +{ + struct systemcap cap = { + .sampling_freq = 256, + .type_nch = {[EGD_SENSOR] = NCH}, + .device_type = "Arduino", + .device_id = "Arduino" + }; + struct devmodule* dev = &(arduinodev->dev); + + dev->ci.set_cap(dev, &cap); + dev->ci.set_input_samlen(dev, NCH*sizeof(int32_t)); + return 0; +} + + +/****************************************************************** + * Arduino methods implementation * + ******************************************************************/ +static +int arduino_open_device(struct devmodule* dev, const char* optv[]) +{ + + struct arduino_eegdev* arduinodev = get_arduino(dev); + + // Get reference value from argument + arduinodev->refmv = (float)atoi(optv[OPT_REFMV]); + + //Open the serial port + arduinodev->pname = optv[OPT_PORT]; + //arduinodev->pid = open(arduinodev->pname, O_RDWR | O_NOCTTY | O_SYNC); + arduinodev->pid = open(arduinodev->pname, O_RDWR | O_NOCTTY); + + if (arduinodev->pid < 0) + { + fprintf(stdout, "Error opening serial %s port!\n",arduinodev->pname); + exit(EXIT_FAILURE); + } + fprintf(stdout, "Connected to port: %s\n",arduinodev->pname); + + memset(&(arduinodev->tty), 0, sizeof(arduinodev->tty)); + + /* Error Handling */ + if ( tcgetattr ( arduinodev->pid, &(arduinodev->tty) ) != 0 ) { + fprintf(stdout,"Error %d from tcgetattr %s:\n",errno, strerror(errno)); + } + + /* Set Baud Rate */ + cfsetospeed(&(arduinodev->tty), (speed_t)B9600); + cfsetispeed(&(arduinodev->tty), (speed_t)B9600); + + /* Setting other Port Stuff */ + arduinodev->tty.c_cflag &= ~PARENB; // Make 8n1 + arduinodev->tty.c_cflag &= ~CSTOPB; + arduinodev->tty.c_cflag &= ~CSIZE; + arduinodev->tty.c_cflag |= CS8; + + arduinodev->tty.c_cflag &= ~CRTSCTS; // no flow control + //arduinodev->tty.c_cc[VMIN] = 1; // read doesn't block + arduinodev->tty.c_cc[VMIN] = 0; // read blocks???? + arduinodev->tty.c_cc[VTIME] = 2; // 0.5 seconds read timeout + arduinodev->tty.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines + + /* Make raw */ + cfmakeraw(&(arduinodev->tty)); + + /* Flush Port, then applies attributes */ + tcflush( arduinodev->pid, TCIFLUSH ); + if ( tcsetattr ( arduinodev->pid, TCSANOW, &(arduinodev->tty)) != 0) { + fprintf(stdout, "Error %d from tcsetattr\n",errno); + } + + //Set capabilities + arduino_set_capability(arduinodev); + + + pthread_mutex_init(&(arduinodev->acqlock), NULL); + arduinodev->runacq = 1; + + if ((pthread_create(&(arduinodev->thread_id), NULL, + arduino_read_fn, arduinodev))) + goto error; + + return 0; + +error: + return -1; +} + + +static +int arduino_close_device(struct devmodule* dev) +{ + struct arduino_eegdev* arduinodev = get_arduino(dev); + + pthread_mutex_lock(&(arduinodev->acqlock)); + arduinodev->runacq = 0; + pthread_mutex_unlock(&(arduinodev->acqlock)); + + pthread_join(arduinodev->thread_id, NULL); + pthread_mutex_destroy(&(arduinodev->acqlock)); + + //Close the serial port here + close(arduinodev->pid); + return 0; +} + + +static +int arduino_set_channel_groups(struct devmodule* dev, unsigned int ngrp, + const struct grpconf* grp) +{ + unsigned int i; + struct selected_channels* selch; + + if (!(selch = dev->ci.alloc_input_groups(dev, ngrp))) + return -1; + + for (i=0; i ringbuffer) + selch[i].in_offset = grp[i].index*sizeof(int32_t); + selch[i].inlen = grp[i].nch*sizeof(int32_t); + selch[i].bsc = 0; + selch[i].typein = EGD_FLOAT; + selch[i].sc = arduino_scales[grp[i].datatype]; + selch[i].typeout = grp[i].datatype; + selch[i].iarray = grp[i].iarray; + selch[i].arr_offset = grp[i].arr_offset; + } + + return 0; +} + + +static void arduino_fill_chinfo(const struct devmodule* dev, int stype, + unsigned int ich, struct egd_chinfo* info) +{ + (void)dev; + (void)stype; + + info->isint = 0; + info->dtype = EGD_FLOAT; + info->min.valdouble = -512.0 * arduino_scales[EGD_DOUBLE].valdouble; + info->max.valdouble = 511.0 * arduino_scales[EGD_DOUBLE].valdouble; + info->label = arduinolabel[ich]; + info->unit = arduinounit; + info->transducter = arduinotransducter; +} + + +API_EXPORTED +const struct egdi_plugin_info eegdev_plugin_info = { + .plugin_abi = EEGDEV_PLUGIN_ABI_VERSION, + .struct_size = sizeof(struct arduino_eegdev), + .open_device = arduino_open_device, + .close_device = arduino_close_device, + .set_channel_groups = arduino_set_channel_groups, + .fill_chinfo = arduino_fill_chinfo, + .supported_opts = arduino_options +}; + diff --git a/src/plugins/barv.c b/src/plugins/barv.c new file mode 100644 index 0000000..5fc34a6 --- /dev/null +++ b/src/plugins/barv.c @@ -0,0 +1,685 @@ +/* + Copyright (C) 2010-2014 EPFL (Ecole Polytechnique Fédérale de Lausanne) + Laboratory CNBI (Chair in Non-Invasive Brain-Machine Interface) + Nicolas Bourdaud + Serafeim Perdikis + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ +#if HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "RecorderRDA.h" +#include "device-helper.h" + +struct barv_eegdev { + struct devmodule dev; + int datafd; + int fs; + unsigned int nch; + struct egdi_chinfo* chmap; + pthread_t thid; + pthread_mutex_t mtx; + bool cap_set; + int port; +}; + +#define get_barv(dev_p) ((struct barv_eegdev*)(dev_p)) + + +#define DEFAULTHOST "10.66.99.11" +#define DEFAULTPORT "51254" +/****************************************************************** + * Elements missing from old API * + ******************************************************************/ + + +struct egdi_signal_info { + const char *unit, *transducer, *prefiltering; + int isint, bsc, dtype, mmtype; + double scale; + union gval min, max; +}; + + +struct egdi_chinfo { + const char *label; + const struct egdi_signal_info* si; + int stype; +}; + +/* EGDCAP_NOCP_*: use pointer directly (do not copy data) */ +#define EGDCAP_NOCP_CHMAP 0x00000001 +#define EGDCAP_NOCP_DEVID 0x00000002 +#define EGDCAP_NOCP_DEVTYPE 0x00000004 +#define EGDCAP_NOCP_CHLABEL 0x00000008 + +struct blockmapping { + int nch; + int num_skipped; + int skipped_stype; + const struct egdi_chinfo* chmap; + const struct egdi_signal_info* default_info; +}; + +struct plugincap { + unsigned int sampling_freq; + int flags; + unsigned int num_mappings; + const struct blockmapping* mappings; + const char* device_type; + const char* device_id; +}; + +//struct egdi_optname { +// const char *name, *defvalue; +//}; + +static inline +void egdi_set_gval(union gval* dst, int type, double val) +{ + if (type == EGD_INT32) + dst->valint32_t = val; + else if (type == EGD_FLOAT) + dst->valfloat = val; + else if (type == EGD_DOUBLE) + dst->valdouble = val; +} + + +/****************************************************************** + * BARV internals * + ******************************************************************/ + +static const char* labeltemplate[EGD_NUM_STYPE] = { + [EGD_EEG] = "eeg:%u", + [EGD_SENSOR] = "sensor:%u", + [EGD_TRIGGER] = "trigger:%u" +}; +static const char analog_unit[] = "uV"; +static const char trigger_unit[] = "Boolean"; +static const char analog_transducter[] = "Active Electrode"; +static const char trigger_transducter[] = "Triggers and Status"; +static const char trigger_prefiltering[] = "No filtering"; +static const char barv_device_type[] = "BrainAmp Recorder/RecView"; + + +static +const struct egdi_signal_info barv_siginfo[2] = { + { + .unit = "uV", .transducer = "Active electrode Float", + .isint = 0, .bsc = 0, .dtype = EGD_FLOAT, + .mmtype = EGD_FLOAT, .min = {.valfloat=-16384.0}, + .max = {.valfloat=16384.0} + },{ + .unit = "uV", .transducer = "Triggers and Status", + .isint = 1, .bsc = 0, .dtype = EGD_INT32, + .mmtype = EGD_INT32, .min = {.valint32_t=-8388608}, + .max = {.valint32_t=8388608} + } +}; + + +enum {OPT_HOST, OPT_PORT, NUMOPT}; +static const struct egdi_optname barv_options[] = { + [OPT_HOST] = {.name = "host", .defvalue = NULL}, + [OPT_PORT] = {.name = "port", .defvalue = "51254"}, + [NUMOPT] = {.name = NULL} +}; + + +static +int barv_set_capability(struct barv_eegdev* barvdev, const char* guid, unsigned int sf) +{ + struct blockmapping barv_mapping = {.num_skipped = 0}; + barv_mapping.nch = barvdev->nch; // Account for trigger channel + barv_mapping.chmap = barvdev->chmap; + barv_mapping.default_info = barv_siginfo; + + struct plugincap cap = { + .sampling_freq = sf, + .num_mappings = 1, + .mappings = &barv_mapping, + .device_type = barv_device_type, + .device_id = guid, + .flags = EGDCAP_NOCP_CHMAP | EGDCAP_NOCP_DEVID | EGDCAP_NOCP_DEVTYPE | EGDCAP_NOCP_CHLABEL | + EGD_CAP_FS | EGD_CAP_TYPELIST + }; + + // And now downgrade to the stupid older interface + struct devmodule* dev = &barvdev->dev; + + struct systemcap capold = {.type_nch = {0}}; + + for (unsigned int i=0; inch; i++) + capold.type_nch[barvdev->chmap[i].stype]++; + capold.sampling_freq = sf; + capold.device_type = barv_device_type; + capold.device_id = guid; + + dev->ci.set_cap(dev, &capold); + dev->ci.set_input_samlen(dev, ((int)barv_mapping.nch)*sizeof(int32_t)); + return 0; +} + +int GetServerMessage(int socket, RDA_MessageHeader **ppHeader) +// Get message from server, if available +// returns 0 if no data, -1 if error, 1 if ok. +{ + RDA_MessageHeader header; + char* pData = (char*)&header; + int nLength = 0; + bool bFirstRecv = true; + int nResult = 0; + + // Retrieve header. + while(nLength < sizeof(header)) + { + int nReqLength = sizeof(header) - nLength; + nResult = recv(socket, (char*)pData, nReqLength, 0); + bFirstRecv = false; + nLength += nResult; + pData += nResult; + } + + *ppHeader = (RDA_MessageHeader*)malloc(header.nSize); + + if (!*ppHeader) return -1; + memcpy(*ppHeader, &header, sizeof(header)); + pData = (char*)*ppHeader + sizeof(header); + nLength = 0; + int nDatasize = header.nSize - sizeof(header); + + // Retrieve rest of block. + while(nLength < nDatasize) + { + int nReqLength = nDatasize - nLength; + nResult = recv(socket, (char*)pData, nReqLength, 0); + if (nResult < 0) return nResult; + nLength += nResult; + pData += nResult; + } + + return 1; +} + +static +void* barv_read_fn(void *data) +{ + struct barv_eegdev* tdev = data; + const struct core_interface* restrict ci = &tdev->dev.ci; + + RDA_MessageHeader* pHeader = NULL; + int nResult = -1; + RDA_MessageStart* pMsgStart = NULL; + RDA_MessageData32* pMsgData32 = NULL; + RDA_MessageData* pMsgData = NULL; + while(1){ + // Read data here + if(nResult = GetServerMessage(tdev->datafd, &pHeader) > 0){ + + switch(pHeader->nType){ + + case 1: + { + // Start receiving and setup + pMsgStart = (RDA_MessageStart*)pHeader; + // Retrieve number of channels. + unsigned int nChannels = pMsgStart->nChannels; + tdev->nch = nChannels; + // Retrieve sampling frequency + int SamplingFreq = (int)((1000000.0f/(pMsgStart->dSamplingInterval))); + pthread_mutex_lock(&tdev->mtx); + tdev->fs = (int)SamplingFreq; + struct egdi_chinfo *newchmap; + newchmap = malloc(((tdev->nch)+1)*sizeof(struct egdi_chinfo)); //Add an extra ch for the trigger + tdev->chmap = newchmap; + // Channel infos, set pointer to start of channel string. + char* pszChannelNames = (char*)pMsgStart->dResolutions + nChannels * sizeof(pMsgStart->dResolutions[0]); + + struct egdi_signal_info* siginf = malloc((nChannels+1)*sizeof(struct egdi_signal_info)); + + for (ULONG i = 0; i < nChannels; i++) + { + // Retrieve channel names and resolutions. + char* curLbl = (char*)malloc((strlen(pszChannelNames)+1)*sizeof(char)); + memcpy(curLbl,pszChannelNames,strlen(pszChannelNames)+1); + + // Set channel labels + tdev->chmap[i].label = curLbl; + + // Set channel type + tdev->chmap[i].stype = EGD_EEG; + + // Set channel signal info + siginf[i] = barv_siginfo[0]; + siginf[i].scale = pMsgStart->dResolutions[i];//Scale individually for each channel + tdev->chmap[i].si = &siginf[i]; + + // Set pointer to next entry + pszChannelNames += strlen(pszChannelNames) + 1; + } + + + // Now augment channels by one to accommodate trigger channel + tdev->nch = tdev->nch + 1; + + // Add info for trigger channel + char* trigStr = "trigger"; + char* trigLbl = (char*)malloc((strlen(trigStr)+1)*sizeof(char)); + memcpy(trigLbl,trigStr,strlen(trigStr)+1); + tdev->chmap[nChannels].label = trigLbl; + tdev->chmap[nChannels].stype = EGD_TRIGGER; + siginf[nChannels] = barv_siginfo[1]; + siginf[nChannels].scale = 1.0; + tdev->chmap[nChannels].si = &siginf[nChannels]; + + // Set device capabilities + barv_set_capability(tdev, pHeader->guid, (unsigned int)SamplingFreq); + tdev->cap_set = true; + pthread_mutex_unlock(&tdev->mtx); + // Free the message memory allocated in GetServerMessage + free(pHeader); + break; + } + case 2: + { + pMsgData = (RDA_MessageData*)pHeader; + unsigned int nCh = tdev->nch -1; + ULONG nBlocksize = pMsgData->nPoints * nCh * sizeof(pMsgData->nData[0]); + + if(nBlocksize == 0) continue; + + ULONG nLength = 0; + int32_t myMarkers[pMsgData->nPoints]; + for(ULONG p=0;pnPoints;p++){ + myMarkers[p]=0; + } + + if(pMsgData->nMarkers > 0){ + // Position of first marker, immediately after the data block. + for(ULONG i=0;inMarkers;i++){ + RDA_Marker* pMarker = (RDA_Marker*)((char*)pMsgData->nData + + pMsgData->nPoints * nCh * sizeof(pMsgData->nData[0])+ nLength); + nLength+=pMarker->nSize; + int32_t MarkerVal = 0; + // Supporting only markers of type "Stimulus","Response","SyncStatus" + if(strcmp(pMarker->sTypeDesc,"Stimulus") == 0){ + MarkerVal = atoi(pMarker->sTypeDesc +10); + }else if(strcmp(pMarker->sTypeDesc,"Response") == 0){ + if(strcmp(pMarker->sTypeDesc + 9,"R128") == 0){ + MarkerVal = 100; // Ignore potential other "responses" + } + }else if(strcmp(pMarker->sTypeDesc,"SyncStatus") == 0){ + // Assume that if it is not off, it is on and we are fine + if(strcmp(pMarker->sTypeDesc + 11,"Sync Off") == 0){ + MarkerVal = 1000; // Ignore potential other "SyncStatus" + } + } + for(ULONG p=0;pnPoints;p++){ + // For any channel (usually trigs are for all, 0 or -1) + myMarkers[pMarker->nPosition + p] += MarkerVal; + } + } + } + + //Re-multiplex data to add trigger channel + int dLength = (nCh+1) * pMsgData->nPoints * sizeof(int32_t); + char* newData = (char*)malloc(dLength); + + float* newDataFloat = (float*)newData; + + for(ULONG point=0;pointnPoints;point++){ + for(ULONG pch=0;pchnData) + (point*nCh+pch)); + float thisNum32 = ((float)sourceNum)*((float)tdev->chmap[pch].si->scale); + *newDataFloat = thisNum32; + newDataFloat++; + } + // Copy trigger channel for this time point, as is (int32_t) + memcpy(newDataFloat,&myMarkers[point],sizeof(int32_t)); + newDataFloat++; + } + + if (ci->update_ringbuffer(&(tdev->dev), (float*)newData, dLength)) + break; + + free(newData); + // Free the message memory allocated in GetServerMessage + free(pHeader); + break; + } + case 3: + { + // Free the message memory allocated in GetServerMessage + free(pHeader); + break; + } + case 4: + { + pMsgData32 = (RDA_MessageData32*)pHeader; + unsigned int nCh = tdev->nch -1; + ULONG nBlocksize = pMsgData32->nPoints * nCh * sizeof(pMsgData32->fData[0]); + if(nBlocksize == 0) continue; + + ULONG nLength = 0; + int32_t myMarkers[pMsgData32->nPoints]; + for(ULONG p=0;pnPoints;p++){ + myMarkers[p]=0; + } + + if(pMsgData32->nMarkers > 0){ + // Position of first marker, immediately after the data block. + for(ULONG i=0;inMarkers;i++){ + RDA_Marker* pMarker = (RDA_Marker*)((char*)pMsgData32->fData + + pMsgData32->nPoints * nCh * sizeof(pMsgData32->fData[0])+ nLength); + //nLength+=pMarker->nSize; Their fucking RDA is buggy, sending always 44 for RecView + // Thus, I replace it with manual measurement of MarkerSize + int RealLength = 16; // 4 integers of 4 bytes each, in the beginning + char* MarkerType = pMarker->sTypeDesc; + RealLength+=strlen(MarkerType)+1; + char* MarkerDesc = MarkerType + strlen(MarkerType)+1; + RealLength+=strlen(MarkerDesc)+1; + nLength+=RealLength; + int32_t MarkerVal = 0; + // Supporting only markers of type "Stimulus","Response","SyncStatus" + if(strcmp(pMarker->sTypeDesc,"Stimulus") == 0){ + MarkerVal = atoi(pMarker->sTypeDesc +10); + }else if(strcmp(pMarker->sTypeDesc,"Response") == 0){ + if(strcmp(pMarker->sTypeDesc + 9,"R128") == 0){ + MarkerVal = 100; // Ignore potential other "responses" + } + }else if(strcmp(pMarker->sTypeDesc,"SyncStatus") == 0){ + if(strcmp(pMarker->sTypeDesc + 11,"Sync Off") == 0){ + MarkerVal = 2000; // Off is a problem, mark with 2000 + }else if(strcmp(pMarker->sTypeDesc + 11,"Sync On") == 0){ + MarkerVal = 1000; // On is ok, mark with 1000 + } + } + + for(ULONG p=0;pnPoints;p++){ + // For any channel (usually trigs are for all, 0 or -1) + myMarkers[pMarker->nPosition + p] += MarkerVal; + } + } + } + + //Re-multiplex data to add trigger channel + int dLength = (nCh+1) * pMsgData32->nPoints * sizeof(int32_t); + char* newData = (char*)malloc(dLength); + + float* newDataFloat = (float*)newData; + + for(ULONG point=0;pointnPoints;point++){ + for(ULONG pch=0;pchfData) + (point*nCh+pch)); + float thisNum32 = ((float)sourceNum)*((float)tdev->chmap[pch].si->scale); + *newDataFloat = thisNum32; + newDataFloat++; + } + // Copy trigger channel for this time point, as is (int32_t) + //*newDataFloat = myMarkers[point]; + memcpy(newDataFloat,&myMarkers[point],sizeof(int32_t)); + newDataFloat++; + } + + if (ci->update_ringbuffer(&(tdev->dev), (float*)newData, dLength)) + break; + free(newData); + // Free the message memory allocated in GetServerMessage + free(pHeader); + break; + } + } + + } + } + // We can reach here only if there was an error previously + ci->report_error(&tdev->dev, errno); + return NULL; +} + + + +/***************************************************************** + * barv misc * + *****************************************************************/ +static +int parse_url(const char* url, char* host, unsigned short *port) +{ + + if (!sscanf(url, "%[^][:]:%hu", host, port) + && !sscanf(url, "%[:0-9a-f]", host) + && !sscanf(url, "[%[:0-9a-f]]:%hu", host, port)) { + fprintf(stderr, "Cannot parse address\n"); + return -1; + } + + return 0; +} + + +static +int connect_server(const char *host, unsigned int short port) +{ + struct addrinfo *ai, *res, hints = {.ai_socktype = SOCK_STREAM}; + int fd, error, family, socktype, proto; + char portnum[8]; + + // Name resolution + snprintf(portnum, sizeof(portnum), "%u", port); + if ((error = getaddrinfo(host, portnum, &hints, &res))) { + fprintf(stderr, "failed: %s\n", gai_strerror(error)); + return -1; + } + + // Create and connect socket (loop over all possible addresses) + for (ai=res; ai != NULL; ai = ai->ai_next) { + family = ai->ai_family; + socktype = ai->ai_socktype | SOCK_CLOEXEC; + proto = ai->ai_protocol; + + if ((fd = socket(family, socktype, proto)) < 0 + || connect(fd, res->ai_addr, res->ai_addrlen)) { + if (fd >= 0) + close(fd); + fd = -1; + } else + break; + } + + freeaddrinfo(res); + return fd; +} + +static +int init_data_com(struct barv_eegdev* tdev, const char* host, int port) +{ + struct timespec tim; + tim.tv_sec = 0; + tim.tv_nsec = 500000000; + struct devmodule* dev = &tdev->dev; + + tdev->datafd = connect_server(host, port); + tdev->port = port; + if (tdev->datafd < 0) { + close(tdev->datafd); + tdev->datafd = -1; + return -1; + } + + // Give it some time + nanosleep(&tim,NULL); + + int threadretval = pthread_create(&tdev->thid, NULL, barv_read_fn, tdev); + if (threadretval < 0) { + close(tdev->datafd); + tdev->datafd = -1; + return -1; + } + + return 0; +} + +/****************************************************************** + * BARV methods implementation * + ******************************************************************/ + +static +int barv_close_device(struct devmodule* dev) +{ + struct barv_eegdev* tdev = get_barv(dev); + unsigned int i; + + // Free channels metadata + free(tdev->chmap); + + // Destroy data connection + if (tdev->datafd >= 0) { + pthread_cancel(tdev->thid); + pthread_join(tdev->thid, NULL); + close(tdev->datafd); + } +} + +static +int barv_open_device(struct devmodule* dev, const char* optv[]) +{ + fprintf(stdout,"Opening barv plugin!!!!\n"); + + struct barv_eegdev* tdev = get_barv(dev); + unsigned short port = atoi(optv[OPT_PORT]); + const char *url = optv[OPT_HOST]; + size_t hostlen = url ? strlen(url) : 0; + char hoststring[hostlen + 1]; + char* host = url ? hoststring : NULL; + + fprintf(stdout,"Host = %s\n",host); + fprintf(stdout,"Port = %d\n",port); + + tdev->datafd = -1; + pthread_mutex_lock(&tdev->mtx); + tdev->cap_set = false; + pthread_mutex_unlock(&tdev->mtx); + + if ( (url && parse_url(url, host, &port)) + || init_data_com(tdev, host, port) ) + { + barv_close_device(dev); + return -1; + } + + // Wait here until we are sure all is set and running + // This is due to the fact that I need to read the first + // message in order to setup the device + bool allset = false; + while(true){ + pthread_mutex_lock(&tdev->mtx); + allset = tdev->cap_set; + pthread_mutex_unlock(&tdev->mtx); + if(allset) { + break; + } + } + + return 0; +} + +static +int barv_set_channel_groups(struct devmodule* dev, unsigned int ngrp, + const struct grpconf* grp) +{ + struct barv_eegdev* tdev = get_barv(dev); + pthread_mutex_lock(&tdev->mtx); + struct selected_channels* selch; + int i, nsel = 0; + + // Downgrade from egdi_chinfo to egdich struct + struct egdich oldchmap[tdev->nch]; + for(unsigned int i=0;inch;i++){ + oldchmap[i].label = tdev->chmap[i].label; + oldchmap[i].stype = tdev->chmap[i].stype; + oldchmap[i].dtype = tdev->chmap[i].si->dtype; + oldchmap[i].data = (void*)tdev->chmap[i].si; + } + nsel = egdi_split_alloc_chgroups(dev, oldchmap, + ngrp, grp, &selch); + for (i=0; imtx); + return (nsel < 0) ? -1 : 0; +} + + +static +void barv_fill_chinfo(const struct devmodule* dev, int stype, + unsigned int ich, struct egd_chinfo* info) +{ + struct barv_eegdev* tdev = get_barv(dev); + pthread_mutex_lock(&tdev->mtx); + + info->label = tdev->chmap[ich].label; + pthread_mutex_unlock(&tdev->mtx); + if (stype != EGD_TRIGGER) { + info->isint = 0; + info->dtype = EGD_FLOAT; + info->min.valfloat = -16384.0; + info->max.valfloat = +16384.0; + info->unit = analog_unit; + info->transducter = analog_transducter; + info->prefiltering = "Unknown"; + } else { + info->isint = 1; + info->dtype = EGD_INT32; + info->min.valint32_t = -8388608; + info->max.valint32_t = 8388607; + info->unit = trigger_unit; + info->transducter = trigger_transducter; + info->prefiltering = trigger_prefiltering; + } +} + +API_EXPORTED +const struct egdi_plugin_info eegdev_plugin_info = { + .plugin_abi = EEGDEV_PLUGIN_ABI_VERSION, + .struct_size = sizeof(struct barv_eegdev), + .open_device = barv_open_device, + .close_device = barv_close_device, + .set_channel_groups = barv_set_channel_groups, + .fill_chinfo = barv_fill_chinfo, + .supported_opts = barv_options +}; + diff --git a/src/plugins/bbt.c b/src/plugins/bbt.c new file mode 100644 index 0000000..dd0dff8 --- /dev/null +++ b/src/plugins/bbt.c @@ -0,0 +1,474 @@ +/* + Copyright (C) 2010-2012 EPFL (Ecole Polytechnique Fédérale de Lausanne) + Laboratory CNBI (Chair in Non-Invasive Brain-Machine Interface) + Nicolas Bourdaud + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ +#if HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "device-helper.h" +struct bbt_eegdev { + struct devmodule dev; + int fs; + unsigned int nch; + struct egdich* chmap; + pthread_t thid; + pthread_mutex_t mtx; + FILE *rfcomm; + unsigned int runacq; + char bt_addr[18]; +}; + +#define DEFAULT_ADDRESS "00:06:66:A0:4D:B7" +#define NUM_CHANNELS_LOOP 23 +#define SAMPLING_RATE 256 +#define BLOCK_SIZE 1 + +#define BYTES_PER_INT 4 +#define BYTES_PER_EEG_EMG_SAMPLE 3 +#define BYTES_PER_DIG_BAT_EMPTY 1 + +#define EEG_CHANNELS 16 +#define EMG_CHANNELS 6 +#define DIGITAL_CHANNELS 3 +#define BATERY_CHANNELS 3 +#define EMPTY_CHANNELS 0 + +#define get_bbt(dev_p) ((struct bbt_eegdev*)(dev_p)) + + +/****************************************************************** + * BBT internals * + ******************************************************************/ + + +static const char bbtlabeleeg[EEG_CHANNELS][8] = { + "Fz", "FC3", "FCz", "FC4", "C3", "Cz", "C4", + "CP3", "CPz", "CP4", "P5 ", "POz", "P6 ", "O1 ", + "O2 ", "EEG_Ax" +}; +static const char bbtlabelemg[EMG_CHANNELS][8] = { + "EXG1", "EXG2", "EXG3", + "EXG4", "EXG5", "EXG6" +}; +static const char bbtlabeltri[8] = "Trigger"; + +static const char bbtunit[] = "uV"; +static const char bbttransducter[] = "Active electrode"; +static const char bbtunit_trigger[] = "Boolean"; +static const char bbttransducter_trigger[] = "Triggers and Status"; +static const char bbtprefiltering[] = "None"; + +static const union gval bbt_scales[EGD_NUM_DTYPE] = { + [EGD_INT32] = {.valint32_t = 1}, + [EGD_FLOAT] = {.valfloat = 1.0f}, // in uV + [EGD_DOUBLE] = {.valdouble = 1.0f} // in uV +}; + +enum {OPT_ADDRESS, NUMOPT}; +static const struct egdi_optname bbt_options[] = { + [OPT_ADDRESS] = {.name = "address", .defvalue = DEFAULT_ADDRESS}, + [NUMOPT] = {.name = NULL} +}; + + +static +void parse_bbt_options(const char* optv[], struct devmodule* dev) +{ + struct bbt_eegdev* tdev = get_bbt(dev); + strcpy(tdev->bt_addr,optv[OPT_ADDRESS]); +} + +static +void* bbt_read_fn(void *data) +{ + struct bbt_eegdev* tdev = data; + const struct core_interface* restrict ci = &tdev->dev.ci; + + int length_data_raw = BLOCK_SIZE * + (EEG_CHANNELS*BYTES_PER_EEG_EMG_SAMPLE + EMG_CHANNELS*BYTES_PER_EEG_EMG_SAMPLE + + DIGITAL_CHANNELS*BYTES_PER_DIG_BAT_EMPTY + BATERY_CHANNELS*BYTES_PER_DIG_BAT_EMPTY + + EMPTY_CHANNELS*BYTES_PER_DIG_BAT_EMPTY); + + char* data_raw = (char*)malloc(length_data_raw); + + char* _mpEegEmgBytes = (char*) malloc( (EEG_CHANNELS + EMG_CHANNELS) * BLOCK_SIZE * BYTES_PER_INT); + char* _mpDigitalBytes = (char*) malloc( DIGITAL_CHANNELS * BLOCK_SIZE * 1); + char* _mpBateryBytes = (char*) malloc( BATERY_CHANNELS * BLOCK_SIZE * 1); + + unsigned int* auxc = (unsigned int*) (_mpEegEmgBytes); + char* auxDig = _mpDigitalBytes; + char* auxBat = _mpBateryBytes; + + unsigned int extensorSigno = 0; + int valorExtendido = 0; + int mascara = 0x00800000; + int hexaExtender = 0xFF000000; + + int length_data_float = (NUM_CHANNELS_LOOP) * BLOCK_SIZE * sizeof(float); + float* dataDst = malloc(length_data_float); + float* auxdst; + + int bytes_read; + int lastIndex = 0; + + + for(int i = 0; i < (EEG_CHANNELS + EMG_CHANNELS) * BLOCK_SIZE * BYTES_PER_INT; i++) + { + _mpEegEmgBytes[i] = 0; + } + + for(int i = 0; i < DIGITAL_CHANNELS * BLOCK_SIZE * 1; i++) + { + _mpDigitalBytes[i] = 0; + } + + for(int i = 0; i < BATERY_CHANNELS * BLOCK_SIZE * 1; i++) + { + _mpBateryBytes[i] = 0; + } + + fseek(tdev->rfcomm, 0, SEEK_END); + + + while(1){ + + // Read the last part. If missed data, bad luck :( + //fseek(tdev->rfcomm, -length_data_raw, SEEK_END); + bytes_read = fread(data_raw, sizeof(char), length_data_raw, tdev->rfcomm); + + lastIndex = 0; + for (int j = 0; j < BLOCK_SIZE; j++) + { + //Read EEG and EMG + int indexEegEmg = (EEG_CHANNELS + EMG_CHANNELS)*4*j; + for (int i = 0; i < EEG_CHANNELS*BYTES_PER_EEG_EMG_SAMPLE + EMG_CHANNELS*BYTES_PER_EEG_EMG_SAMPLE; i++) + { + _mpEegEmgBytes[indexEegEmg] = data_raw[lastIndex + i + 2]; + + _mpEegEmgBytes[indexEegEmg + 1] = data_raw[lastIndex + i + 1]; + + _mpEegEmgBytes[indexEegEmg + 2] = data_raw[lastIndex + i]; + i+=2; + indexEegEmg += 4; + + } + + lastIndex += EEG_CHANNELS*BYTES_PER_EEG_EMG_SAMPLE + EMG_CHANNELS*BYTES_PER_EEG_EMG_SAMPLE; + + //Read Digital + int indexDigital = DIGITAL_CHANNELS*1*j; + for (int i = 0; i < DIGITAL_CHANNELS*BYTES_PER_DIG_BAT_EMPTY; i++) + { + _mpDigitalBytes[indexDigital + i] = data_raw[lastIndex + i]; + } + + lastIndex += DIGITAL_CHANNELS*BYTES_PER_DIG_BAT_EMPTY; + + //Read Batery + int indexBat = BATERY_CHANNELS*1*j; + for (int i = 0; i < BATERY_CHANNELS*BYTES_PER_DIG_BAT_EMPTY; i++) + { + _mpBateryBytes[indexBat + i] = data_raw[lastIndex + i]; + } + + lastIndex += BATERY_CHANNELS*BYTES_PER_DIG_BAT_EMPTY; + + //Skip Empty channels + lastIndex += EMPTY_CHANNELS*BYTES_PER_DIG_BAT_EMPTY; + } + + + auxc = (unsigned int*)(_mpEegEmgBytes); + auxDig = _mpDigitalBytes; + auxBat = _mpBateryBytes; + auxdst = dataDst; + + + auxdst = dataDst; + + for(int k = 0; k < BLOCK_SIZE; k++) + { + //Sign extend and conversion of eeg data + for(int l = 0; l < EEG_CHANNELS; l++) + { + + extensorSigno = *auxc; + valorExtendido = *auxc; + extensorSigno = extensorSigno & mascara; + if (extensorSigno != 0) + valorExtendido = valorExtendido | hexaExtender; + + *auxdst = (((2.5/25.7)/0x780000)*((double)valorExtendido))*10e5; + auxdst++; + + auxc++; + } + //Sign extend and conversion of emg data + for(int l = 0; l < EMG_CHANNELS; l++) + { + extensorSigno = *auxc; + valorExtendido = *auxc; + extensorSigno = extensorSigno & mascara; + if (extensorSigno != 0) + valorExtendido = valorExtendido | hexaExtender; + + *auxdst = (((2.5/5.94)/0x780000)*((double)valorExtendido))*10e5; + + auxc++; + auxdst++; + } + + //Fill trigger channel + *auxdst = (double) ((auxDig[0] + 2 * auxDig[1] + 4 * auxDig[2]) / 0x7F); + auxDig=auxDig+3; + + auxdst++; + }//end for k + + + ci->update_ringbuffer(&(tdev->dev), dataDst, length_data_float); + + } + // We can reach here only if there was an error previously + ci->report_error(&tdev->dev, errno); + return NULL; +} + + +static +int bbt_set_capability(struct bbt_eegdev* bbtdev) +{ + struct bbt_eegdev* tdev = get_bbt(bbtdev); + tdev->chmap = malloc(NUM_CHANNELS_LOOP*sizeof(*tdev->chmap)); + + //EEG + for (int i=0; ichmap[i].dtype = EGD_FLOAT; + tdev->chmap[i].stype = EGD_EEG; + } + + //EMG + for (int i=EEG_CHANNELS; ichmap[i].dtype = EGD_FLOAT; + tdev->chmap[i].stype = EGD_SENSOR; + } + + //TRIGGER + tdev->chmap[22].dtype = EGD_FLOAT; + tdev->chmap[22].stype = EGD_TRIGGER; + + struct systemcap cap = {.type_nch = {0}}; + + for (int i=0; ichmap[i].stype]++; + + // Fill the capabilities metadata + cap.sampling_freq = SAMPLING_RATE; + cap.device_type = "BBT"; + cap.device_id = tdev->bt_addr; + + struct devmodule* dev = &tdev->dev; + + dev->ci.set_cap(dev, &cap); + dev->ci.set_input_samlen(dev, NUM_CHANNELS_LOOP*sizeof(float)); + + return 0; +} + + + + +static +int init_data_com(struct devmodule* dev, const char* optv[]) +{ + parse_bbt_options(optv, dev); + struct bbt_eegdev* tdev = get_bbt(dev); + + struct timespec tim; + tim.tv_sec = 0; + tim.tv_nsec = 500000000; + + // Set capabilities + bbt_set_capability(tdev); + + struct sockaddr_rc addr = { 0 }; + int s, status; + + // allocate a socket + s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + fcntl(s, F_SETFD, fcntl(s, F_GETFD)|FD_CLOEXEC); + + // set the connection parameters (who to connect to) + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + addr.rc_channel = (uint8_t) 1; + str2ba( tdev->bt_addr, &addr.rc_bdaddr ); + + // connect to server + if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + fprintf(stderr, "Couldn't connect to the device\n"); + close(s); + return -1; + } + tdev->rfcomm = fdopen(s,"r+"); + if (!tdev->rfcomm) { + fprintf(stderr, "Couldn't open the device\n"); + return -1; + } else { + fprintf(stdout, "Connected to BBT cap\n"); + } + + if (fwrite("s", sizeof(char), 1, tdev->rfcomm) < 0) { + fprintf(stderr, "Couldn't write through bluetooth\n"); + return -1; + } + + // Give it some time + nanosleep(&tim,NULL); + + int threadretval = pthread_create(&tdev->thid, NULL, bbt_read_fn, tdev); + + if (threadretval < 0) { + fclose(tdev->rfcomm); + tdev->rfcomm = NULL; + return -1; + } + tdev->runacq = 1; + return 0; +} +/****************************************************************** + * BBT methods implementation * + ******************************************************************/ + +static +int bbt_close_device(struct devmodule* dev) +{ + printf("Closing bbt device\n"); + struct bbt_eegdev* tdev = get_bbt(dev); + tdev->runacq = 0; + + + if (fwrite("p", sizeof(char), 1, tdev->rfcomm) < 0) { + fprintf(stderr, "Couldn't write stop message through bluetooth\n"); + return -1; + } + + // Destroy data connection + if (tdev->rfcomm >= 0) { + pthread_cancel(tdev->thid); + pthread_join(tdev->thid, NULL); + fclose(tdev->rfcomm); + } + printf("Closed successfully\n"); + + // Free channels metadata + free(tdev->chmap); + return 0; +} +static +int bbt_open_device(struct devmodule* dev, const char* optv[]) +{ + if (init_data_com(dev, optv)) + { + bbt_close_device(dev); + return -1; + } + + return 0; + +} + +static +int bbt_set_channel_groups(struct devmodule* dev, unsigned int ngrp, + const struct grpconf* grp) +{ + + struct bbt_eegdev* tdev = get_bbt(dev); + struct selected_channels* selch; + int i, nsel = 0; + + nsel = egdi_split_alloc_chgroups(dev, tdev->chmap, ngrp, grp, &selch); + for (i=0; iisint = 0; + info->dtype = EGD_FLOAT; + info->min.valfloat = -16384.0; + info->max.valfloat = +16384.0; + info->unit = bbtunit; + info->transducter = bbttransducter; + info->prefiltering = bbtprefiltering; + info->label = bbtlabeleeg[ich]; + } else if (stype == EGD_SENSOR) { + info->isint = 0; + info->dtype = EGD_FLOAT; + info->min.valfloat = -16384.0; + info->max.valfloat = +16384.0; + info->unit = bbtunit; + info->transducter = bbttransducter; + info->prefiltering = bbtprefiltering; + info->label = bbtlabelemg[ich]; + } else { + info->isint = 1; + info->dtype = EGD_INT32; + info->min.valint32_t = -8388608; + info->max.valint32_t = 8388607; + info->unit = bbtunit_trigger; + info->transducter = bbttransducter; + info->prefiltering = bbtprefiltering; + info->label = bbtlabeltri; + } +} + +API_EXPORTED +const struct egdi_plugin_info eegdev_plugin_info = { + .plugin_abi = EEGDEV_PLUGIN_ABI_VERSION, + .struct_size = sizeof(struct bbt_eegdev), + .open_device = bbt_open_device, + .close_device = bbt_close_device, + .set_channel_groups = bbt_set_channel_groups, + .fill_chinfo = bbt_fill_chinfo, + .supported_opts = bbt_options +}; + diff --git a/src/plugins/biosemi.c b/src/plugins/biosemi.c index 4ec9c7c..a3e9085 100644 --- a/src/plugins/biosemi.c +++ b/src/plugins/biosemi.c @@ -141,10 +141,9 @@ static label4_t eeg64label[] = { static label4_t eeg32label[] = { "Fp1", "AF3", "F7", "F3", "FC1", "FC5", "T7", "C3", - "CP1", "CP5", "P7", "P3", "P9", "Pz", "PO3", "O1", - "Oz", "O2", "PO4", "P4", "P8", "CP6", "CP2", "C4", - "T8", "FC6", "FC2", "F4", "F8", "AF4", "FP2", "Fz", - "Cz" + "CP1", "CP5", "P7", "P3", "Pz", "PO3", "O1", "Oz", + "O2", "PO4", "P4", "P8", "CP6", "CP2", "C4", "T8", + "FC6", "FC2", "F4", "F8", "AF4", "FP2", "Fz", "Cz" }; @@ -641,13 +640,14 @@ int act2_set_channel_groups(struct devmodule* dev, unsigned int ngrp, static void act2_fill_chinfo(const struct devmodule* dev, int stype, unsigned int ich, struct egd_chinfo* info) { + struct act2_eegdev* a2dev = get_act2(dev); if (stype != EGD_TRIGGER) { info->isint = 0; info->dtype = EGD_DOUBLE; info->min.valdouble = -262144.0; info->max.valdouble = 262143.96875; info->label = (stype == EGD_EEG) ? - eeg64label[ich] : sensorlabel[ich]; + a2dev->eeglabel[ich] : sensorlabel[ich]; info->unit = analog_unit; info->transducter = analog_transducter; info->prefiltering = get_act2(dev)->prefiltering; diff --git a/src/plugins/fileout.c b/src/plugins/fileout.c index 341ac56..6fe73cb 100644 --- a/src/plugins/fileout.c +++ b/src/plugins/fileout.c @@ -67,7 +67,7 @@ static const unsigned int dattab[EGD_NUM_DTYPE] = { static const char xdfout_device_type[] = "Data file"; static const char eegch_regex[] = "^(" - "(N|Fp|AF|F|FT|FC|A|T|C|TP|CP|P|PO|O|I)(z|[[:digit:]][[:digit:]]?)" + "(N|Fp|AF|F|FT|FC|A|T|C|TP|CP|P|PO|O|I|AFF|FTT|FCC|TPP|CPP|POO|AFp|FFT|FFC|TTP|CCP|PPO|OI)(z|[[:digit:]][[:digit:]]?)" "|([ABCDEF][[:digit:]][[:digit:]]?)" "|((EEG|[Ee]eg)[-:]?[[:digit:]]*)" ")"; diff --git a/src/plugins/gtec.c b/src/plugins/gtec.c index 6f498f2..baf5d18 100644 --- a/src/plugins/gtec.c +++ b/src/plugins/gtec.c @@ -39,6 +39,8 @@ #define ELT_SAMSIZE (ELT_NCH*sizeof(float)) #define NUMELT_MAX 4 #define PREFILT_STR_SIZE 64 + + struct gtec_acq_element { char devname[16]; void* buff; @@ -59,7 +61,7 @@ struct gtec_eegdev { pthread_cond_t bfullcond; unsigned int num_elt; struct gtec_acq_element elt[NUMELT_MAX]; - + int fs; struct egdich* chmap; char devid[NUMELT_MAX*16]; @@ -105,8 +107,8 @@ void add_dtime_ns(struct timespec* ts, long delta_ns) * gtec metadata * *****************************************************************/ static const char* labeltemplate[EGD_NUM_STYPE] = { - [EGD_EEG] = "eeg:%u", - [EGD_SENSOR] = "sensor:%u", + [EGD_EEG] = "eeg:%u", + [EGD_SENSOR] = "sensor:%u", [EGD_TRIGGER] = "trigger:%u" }; static const char analog_unit[] = "uV"; @@ -118,12 +120,12 @@ static const char gtec_device_type[] = "gTec g.USBamp"; enum {OPT_DEVID, OPT_HP, OPT_LP, OPT_NOTCH, OPT_FS, NUMOPT}; static const struct egdi_optname gtec_options[] = { - [OPT_DEVID] = {.name = "deviceid", .defvalue = NULL}, - [OPT_HP] = {.name = "highpass", .defvalue = "0.1"}, - [OPT_LP] = {.name = "lowpasspass", .defvalue = "-1"}, - [OPT_NOTCH] = {.name = "notch", .defvalue = "50"}, - [OPT_FS] = {.name = "samplerate", .defvalue = "512"}, - [NUMOPT] = {.name = NULL} + [OPT_DEVID] = {.name = "deviceid", .defvalue = NULL}, + [OPT_HP] = {.name = "highpass", .defvalue = "0.1"}, + [OPT_LP] = {.name = "lowpass", .defvalue = "-1"}, + [OPT_NOTCH] = {.name = "notch", .defvalue = "50"}, + [OPT_FS] = {.name = "samplerate", .defvalue = "512"}, + [NUMOPT] = {.name = NULL} }; @@ -165,10 +167,10 @@ int gtec_open_devices(struct gtec_eegdev* gtdev, const char* devid) continue; dname = devlist[j]; connected = !anydev; - if (GT_OpenDevice(dname)) + if (GT_OpenDevice(dname)) opened = 1; } - + if (!opened) { error = connected ? EBUSY : ENODEV; break; @@ -215,7 +217,7 @@ static float valabs(float f) {return (f >= 0.0f) ? f : -f;} //avoid include libm -static +static int gtec_find_bpfilter(const char* devname, gt_usbamp_config* conf, float fl, float fh, float order, struct filtparam* filtprm) @@ -239,7 +241,7 @@ int gtec_find_bpfilter(const char* devname, gt_usbamp_config* conf, if (filt == NULL) return -1; GT_GetBandpassFilterList(devname, fs, filt, nfilt*sizeof(*filt)); - + // Test matching score of each filter for (i=0; ienable_sc = GT_FALSE; conf->mode = GT_MODE_NORMAL; conf->num_analog_in = 16; - + // Set all common reference and ground for (i=0; icommon_ground[i] = GT_TRUE; @@ -351,7 +353,7 @@ int gtec_setup_conf(const char* devname, gt_usbamp_config* conf, if (gtec_find_bpfilter(devname, conf, gopt->hp, gopt->lp, 2, &bpprm) || gtec_find_notchfilter(devname, conf, gopt->notch, ¬chprm)) return -1; - + // Setup prefiltering string if (bpprm.fl) snprintf(hpstr, sizeof(hpstr), "%.2f", bpprm.fl); @@ -387,7 +389,7 @@ int gtec_configure_device(struct gtec_eegdev *gtdev, .digital_out = {GT_FALSE, GT_FALSE, GT_FALSE, GT_FALSE} }; gtdev->fs = gopt->fs; - + nch = gtdev->num_elt*ELT_NCH; gtdev->chmap = malloc(nch*sizeof(*gtdev->chmap)); for (i=0; inotch = 0.0; else gopt->notch = atof(optv[OPT_NOTCH]); - + if (gopt->lp < 0) gopt->lp = 0.4*((double)gopt->fs); } @@ -449,7 +451,7 @@ void gtec_callback(void* data) void* restrict buffer = gtdev->buffer; int sizetot, size, buflen = gtdev->buflen; const char* devname = gtdev->elt[0].devname; - + // Transfer data to ringbuffer by chunks of buflen bytes max sizetot = GT_GetSamplesAvailable(devname); while (sizetot > 0) { @@ -475,7 +477,7 @@ size_t gtec_sync_buffer(struct gtec_acq_element* elt, size_t bsize) size_t minsize = SIZE_MAX, maxsize = 0; unsigned int i, nelt = gtdev->num_elt; pthread_mutex_t* bfulllock = &(gtdev->bfulllock); - + elt->bsize = bsize; pthread_rwlock_unlock(rwlock); @@ -492,7 +494,7 @@ size_t gtec_sync_buffer(struct gtec_acq_element* elt, size_t bsize) if (minsize > 0) { char* buffer = gtdev->buffer; ci->update_ringbuffer(&(gtdev->dev), buffer, nelt*minsize); - + pthread_mutex_lock(bfulllock); // Empty from the common buffer the data sent @@ -502,13 +504,13 @@ size_t gtec_sync_buffer(struct gtec_acq_element* elt, size_t bsize) gtdev->elt[i].bsize -= minsize; } if (minsize != maxsize) - memmove(buffer, buffer+nelt*minsize, + memmove(buffer, buffer+nelt*minsize, nelt*(maxsize - minsize)); // unblock element whose buffer was full - if (maxsize == gtdev->buflen/nelt) + if (maxsize == gtdev->buflen/nelt) pthread_cond_broadcast(&(gtdev->bfullcond)); - + pthread_mutex_unlock(bfulllock); } @@ -550,7 +552,7 @@ void gtec_callback_masterslave(void* data) while ((sizetot > 0) && (bsize < buflen)) { size = (sizetot < buflen-bsize) ? sizetot : buflen-bsize; size = GT_GetData(devname, ebuff, size); - if (size <= 0) + if (size <= 0) break; // Update the common buffer with the new data @@ -559,7 +561,7 @@ void gtec_callback_masterslave(void* data) memcpy(rbuff+pos, ebuff+i, ELT_SAMSIZE); } bsize += size; - + // Synchronize with the other elements bsize = gtec_sync_buffer(elt, bsize); @@ -591,7 +593,7 @@ int gtec_start_device_acq(struct gtec_eegdev* gtdev) eltbuflen = ELT_SAMSIZE*(size_t)(0.1 * (double)gtdev->fs); buff = malloc(2*num*eltbuflen); gtdev->runacq = 1; - + // Setup synchronization primitives pthread_mutex_init(&(gtdev->bfulllock), NULL); pthread_cond_init(&(gtdev->bfullcond), NULL); @@ -621,7 +623,7 @@ int gtec_start_device_acq(struct gtec_eegdev* gtdev) } GT_StartAcquisition(gtdev->elt[i].devname); } - + return 0; } @@ -656,11 +658,11 @@ int gtec_stop_device_acq(struct gtec_eegdev* gtdev) /****************************************************************** * gTec methods implementation * ******************************************************************/ -static +static int gtec_close_device(struct devmodule* dev) { struct gtec_eegdev* gtdev = get_gtec(dev); - + gtec_stop_device_acq(gtdev); destroy_gtecdev(gtdev); @@ -668,7 +670,7 @@ int gtec_close_device(struct devmodule* dev) } -static +static int gtec_set_channel_groups(struct devmodule* dev, unsigned int ngrp, const struct grpconf* grp) { @@ -685,12 +687,12 @@ int gtec_set_channel_groups(struct devmodule* dev, unsigned int ngrp, } -static +static void gtec_fill_chinfo(const struct devmodule* dev, int stype, unsigned int ich, struct egd_chinfo* info) { struct gtec_eegdev* gtdev = get_gtec(dev); - + snprintf(gtdev->labeltmp, sizeof(gtdev->labeltmp), labeltemplate[stype], ich+1); info->label = gtdev->labeltmp; diff --git a/src/plugins/gtecnet.c b/src/plugins/gtecnet.c new file mode 100644 index 0000000..60d19e6 --- /dev/null +++ b/src/plugins/gtecnet.c @@ -0,0 +1,525 @@ +/* + Copyright (C) 2010-2012 EPFL (Ecole Polytechnique Fédérale de Lausanne) + Laboratory CNBI (Chair in Non-Invasive Brain-Machine Interface) + Nicolas Bourdaud + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ +#if HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "device-helper.h" + +struct gtecnet_eegdev { + struct devmodule dev; + struct egdich* chmap; + + pthread_t thid; + pthread_mutex_t mtx; + + socketd_t sockcmd; + socketd_t socklisten; + socketd_t sockdata; + + DevConfig* devconf; + + void* Transceiver; + + float* databuffer; + + char SessionID[5]; + char ConnectedDevices[10000]; //Allocate enough memory + char DeviceType[20]; + char DeviceName[20]; + char ScanInfo[10000]; //Allocate enough memory + char* host_ip; + char* local_ip; + + int NchannelsALL; + int NchannelsEEG; + int NchannelsEXG; + int NchannelsTRIG; + int DataPointsPerScan; + int ScanCount; + int SamplingRate; + int ScansPerFrame; + int host_port; + int local_port; + int notchFilter; + int BPFilter; + int usedefmap; + + unsigned int runacq; +}; + + +#define NUMELEM(x) (sizeof(x) / sizeof(x[0])) +#define get_gtecnet(dev_p) ((struct gtecnet_eegdev*)(dev_p)) + + +//------------------------------------- +// Hardcoded definitions +//------------------------------------- + +#define DEFAULT_HOST_IP "192.168.1.1" +#define DEFAULT_HOST_PORT "50223" + +#define DEFAULT_LOCAL_IP "192.168.1.101" +#define DEFAULT_LOCAL_PORT "50220" + +#define DEFAULT_SAMPLING_RATE "512" + +#define DEFAULT_NOTCH "4" // 512 Hz -> 48 - 52 Hz +#define DEFAULT_BANDPASS "32" // 512 Hz -> 0.01 - 100 Hz + +#define DEFAULT_USEDEFMAP "1" + +/****************************************************************** + * g.NEEDaccess metadata * + ******************************************************************/ +static const char gtecnetlabelHIamp[81][10] = { + "FP1","FPz","FP2","AF7","AF3","AF4","AF8","F7","F5","F3", + "F1","Fz","F2","F4","F6","F8","FT7","FC5","FC3","FC1", + "FCz","FC2","FC4","FC6","FT8","T7","C5","C3","C1","Cz", + "C2","C4","C6","T8","TP7","CP5","CP3","CP1","CPz","CP2", + "CP4","CP6","TP8","P7","P5","P3","P1","Pz","P2","P4", + "P6","P8","PO7","PO3","POz","PO4","PO8","O1","Oz","O2", + "F9","F10","REF1","REF2", + "EXG1","EXG2","EXG3","EXG4","EXG5","EXG6", "EXG7","EXG8","EXG9","EXG10","EXG11","EXG12","EXG13","EXG14","EXG15","EXG16", + "TRIG" +}; + +static const char gtecnetlabelUSBamp[17][10] = { + "Fz","FC3","FC1","FCz","FC2","FC4","C3","C1","Cz","C2","C4","CP3","CP1","CPz","CP2","CP4","TRIG" +}; + +static const char gtecnetlabelNautilus[33][10] = { + "FP1","FP2","AF3","AF4","F7","F3","Fz","F4","F8","FC5", + "FC1","FC2","FC6","T7","C3","Cz","C4","T8","CP5","CP1", + "CP2","CP6","P7","P3","Pz","P4","P8","PO7","PO3","PO4", + "PO8","Oz", "TRIG" +}; + +static const char gtecnetlabelGen[81][10] = { + "1","2","3","4","5","6","7","8","9","10", + "11","12","13","14","15","16","17","18","19","20", + "21","22","23","24","25","26","27","28","29","30", + "31","32","33","34","35","36","37","38","39","40", + "41","42","43","44","45","46","47","48","49","50", + "51","52","53","54","55","56","57","58","59","60", + "61","62","63","64","65","66","67","68","69","70", + "71","72","73","74","75","76","77","78","79","80", + "TRIG" +}; + +static const char gtecnetunit[] = "uV"; +static const char gtecnettransducter[] = "Active electrode"; +static const char gtecnetunit_trigger[] = "Boolean"; +static const char gtecnettransducter_trigger[] = "Triggers and Status"; +static const int gtecnet_provided_stypes[] = {EGD_EEG}; + +enum {OPT_HOSTIP, OPT_HOSTPORT, OPT_LOCALIP, OPT_LOCALPORT, OPT_SAMPLERATE, OPT_NOTCH, OPT_BANDPASS, OPT_USEDEFMAP, NUMOPT}; +static const struct egdi_optname gtecnet_options[] = { + [OPT_HOSTIP] = {.name = "hostIP", .defvalue = DEFAULT_HOST_IP}, + [OPT_HOSTPORT] = {.name = "hostport", .defvalue = DEFAULT_HOST_PORT}, + [OPT_LOCALIP] = {.name = "localIP", .defvalue = DEFAULT_LOCAL_IP}, + [OPT_LOCALPORT] = {.name = "localport", .defvalue = DEFAULT_LOCAL_PORT}, + [OPT_SAMPLERATE] = {.name = "samplerate", .defvalue = DEFAULT_SAMPLING_RATE}, + [OPT_NOTCH] = {.name = "notch", .defvalue = DEFAULT_NOTCH}, + [OPT_BANDPASS] = {.name = "bandpass", .defvalue = DEFAULT_BANDPASS}, + [OPT_USEDEFMAP] = {.name = "usedefmap", .defvalue = DEFAULT_USEDEFMAP}, + [NUMOPT] = {.name = NULL} +}; + + +static +void parse_gtecnet_options(const char* optv[], struct devmodule* dev) +{ + struct gtecnet_eegdev* tdev = get_gtecnet(dev); + + tdev->host_ip = optv[OPT_HOSTIP]; + tdev->host_port = atoi(optv[OPT_HOSTPORT]); + + tdev->local_ip = optv[OPT_LOCALIP]; + tdev->local_port = atoi(optv[OPT_LOCALPORT]); + + tdev->SamplingRate = atoi(optv[OPT_SAMPLERATE]); + tdev->BPFilter = atoi(optv[OPT_BANDPASS]); + tdev->notchFilter = atoi(optv[OPT_NOTCH]); + tdev->usedefmap = atoi(optv[OPT_USEDEFMAP]); +} + + +static +void* gtecnet_read_fn(void *data) +{ + struct gtecnet_eegdev* tdev = data; + const struct core_interface* restrict ci = &tdev->dev.ci; + + tdev->databuffer = (float*)malloc(tdev->ScansPerFrame * tdev->DataPointsPerScan * sizeof(float)); + + //Enable DataReadyEventThreshold -- this will allow me to read data in the frame size (frame/reading rate) I want + int DataReadyEventThreshouldOutput = gtecnal_DataReadyEventThreshold(tdev->Transceiver, tdev->SessionID, tdev->ScansPerFrame); + + // Start acquisition + int StartAcqOutput = gtecnal_StartAcquisition(tdev->Transceiver, tdev->SessionID); + + // Start streaming + int StartStreaming = gtecnal_StartStreaming(tdev->Transceiver, tdev->SessionID); + + bool DataIsReady; + struct timeval start, end; + while(true){ + if(tdev->runacq){ + //gettimeofday(&start,NULL); + DataIsReady = gtecnal_WaitForDataReadyEvent(tdev->Transceiver, tdev->SessionID, 50000); + if (!DataIsReady) + fprintf(stdout,"Waiting for data ready event timed out...\n"); + + int ScansRead = gtecnal_ReadDataFrame(tdev->Transceiver, tdev->databuffer, tdev->SessionID, &tdev->sockdata, tdev->ScansPerFrame, tdev->DataPointsPerScan); + //gettimeofday(&end,NULL); + //float ElapsedTime = (float)1000*(end.tv_sec-start.tv_sec)+(end.tv_usec-start.tv_usec)/1000; + //fprintf(stdout,"\n%f milliseconds for %d frames\n",ElapsedTime,ScansRead); + ci->update_ringbuffer(&(tdev->dev), tdev->databuffer, ScansRead * tdev->DataPointsPerScan * sizeof(float)); + }else{ + // Exit reading thread + pthread_exit(NULL); + } + } + // We can reach here only if there was an error previously + ci->report_error(&tdev->dev, errno); + return NULL; +} + +static +int gtecnet_set_capability(struct gtecnet_eegdev* gtecnetdev) +{ + struct gtecnet_eegdev* tdev = get_gtecnet(gtecnetdev); + tdev->chmap = malloc((tdev->NchannelsALL)*sizeof(*tdev->chmap)); + + //EEG + for (int i=0; iNchannelsEEG; i++) { + tdev->chmap[i].dtype = EGD_FLOAT; + tdev->chmap[i].stype = EGD_EEG; + } + + //EXG + for (int i=tdev->NchannelsEEG; iNchannelsEEG+tdev->NchannelsEXG; i++) { + tdev->chmap[i].dtype = EGD_FLOAT; + tdev->chmap[i].stype = EGD_SENSOR; + } + + //TRIGGER + tdev->chmap[tdev->NchannelsEEG+tdev->NchannelsEXG].dtype = EGD_FLOAT; //EGD_INT32? + tdev->chmap[tdev->NchannelsEEG+tdev->NchannelsEXG].stype = EGD_TRIGGER; + + + struct systemcap cap = {.type_nch = {0}}; + + for (int i=0; iNchannelsALL; i++) + cap.type_nch[tdev->chmap[i].stype]++; + + // Fill the capabilities metadata + cap.sampling_freq = tdev->SamplingRate; + cap.device_type = tdev->DeviceType; + cap.device_id = tdev->DeviceName; + + struct devmodule* dev = &tdev->dev; + + dev->ci.set_cap(dev, &cap); + dev->ci.set_input_samlen(dev, tdev->NchannelsALL * sizeof(float)); + + return 0; +} + +static +int gtecnet_close_device(struct devmodule* dev) +{ + struct gtecnet_eegdev* tdev = get_gtecnet(dev); + + // Try to stop requests for data + tdev->runacq = 0; + + // Give some time the reading to finish + sleep(1); + + //Stop streaming + int StopStreamOutput = gtecnal_StopStreaming(tdev->Transceiver, tdev->SessionID); + + // Stop acquisition + int StopAcqOutput = gtecnal_StopAcquisition(tdev->Transceiver, tdev->SessionID); + + // Close acquisition session + int CloseAcqSesOutput = gtecnal_CloseDAQSession(tdev->Transceiver, tdev->SessionID); + + // Disconnect from server + int DisconnectServerOutput = gtecnal_Disconnect(tdev->Transceiver, tdev->SessionID); + + // Close connections + gtecnal_CloseNetworkConnection(&tdev->socklisten); + gtecnal_CloseNetworkConnection(&tdev->sockdata); + gtecnal_CloseNetworkConnection(&tdev->sockcmd); + + // Delete the transceiver + gtecnal_deleteTransceiver(tdev->Transceiver); + + //Free allocated memory + free(tdev->devconf); + free(tdev->databuffer); + //Free channels metadata + free(tdev->chmap); + return 0; +} + + +static +int init_data_com(struct devmodule* dev, const char* optv[]) +{ + + parse_gtecnet_options(optv,dev); + + struct gtecnet_eegdev* tdev = get_gtecnet(dev); + + + tdev->sockcmd = 0; + tdev->socklisten = 0; + tdev->sockdata = 0; + + tdev->ScanCount = 1; + + // Connect to server socket + int EstablishNetworkConnectionOutput = gtecnal_EstablishNetworkConnection(&tdev->sockcmd, tdev->host_ip, tdev->host_port); + + // Create transceiver + tdev->Transceiver = gtecnal_newTransceiver(&tdev->sockcmd); + + // Get session ID + gtecnal_GetSessionID(tdev->Transceiver, tdev->SessionID); + + // Get connected devices + int GetDevInfoOutput = gtecnal_GetConnectedDevices(tdev->Transceiver, tdev->SessionID, 0, tdev->ConnectedDevices); + + // Establish listening socket + int ListenOnNetworkOutput = gtecnal_ListenOnNetwork(&tdev->socklisten, tdev->local_port); + + // Inform server for accepting endpoint + int SetupStreamingOutput = gtecnal_SetupStreaming(tdev->Transceiver, tdev->SessionID, tdev->local_ip, tdev->local_port); + + //Accept incoming connection + tdev->sockdata = gtecnal_AcceptOnNetwork(tdev->socklisten, TCP_RECV_BUF_SIZE); + if(tdev->sockdata == SOCKET_ERROR){ + fprintf(stderr,"Failed to connect, closing sockets!\n"); + gtecnal_CloseNetworkConnection(&tdev->socklisten); + gtecnal_CloseNetworkConnection(&tdev->sockcmd); + } + + // Open acquisition session + int OpenAcqSesOutput = gtecnal_OpenDAQSession(tdev->Transceiver, tdev->SessionID, tdev->ConnectedDevices, 1, 1,tdev->DeviceType,tdev->DeviceName); + + // Specify number and type of channels, hardcoded according to CNBI conventions + if(strcmp(tdev->DeviceType,"gUSBamp")==0){ + tdev->NchannelsALL = 17; + tdev->NchannelsEEG = 16; + tdev->NchannelsEXG = 0; + tdev->NchannelsTRIG = 1; + }else if(strcmp(tdev->DeviceType,"gHIamp")==0){ + tdev->NchannelsALL = 81; + tdev->NchannelsEEG = 64; + tdev->NchannelsEXG = 16; + tdev->NchannelsTRIG = 1; + }else if(strcmp(tdev->DeviceType,"gNautilus")==0){ + tdev->NchannelsALL = 33; + tdev->NchannelsEEG = 32; + tdev->NchannelsEXG = 0; + tdev->NchannelsTRIG = 1; + }else{ + fprintf(stderr,"Unkown, unsupported device, crashing!\n"); + exit(EXIT_FAILURE); + } + + //Configure the device (with default settings of the device used + selected sampling rate) + if(strcmp(tdev->DeviceType,"gUSBamp")==0){ + tdev->devconf = (gUSBampConfig*)malloc(sizeof(gUSBampConfig)); // Careful, must be freed in the end + gtecnal_SetDefaultgUSBamp((gUSBampConfig*)tdev->devconf,tdev->SamplingRate); // TO REVIEW + }else if(strcmp(tdev->DeviceType,"gHIamp")==0){ + tdev->devconf = (gHIampConfig*)malloc(sizeof(gHIampConfig)); // Careful, must be freed in the end + gtecnal_SetDefaultgHIamp((gHIampConfig*)tdev->devconf,tdev->SamplingRate,tdev->BPFilter,tdev->notchFilter); + }else if(strcmp(tdev->DeviceType,"gNautilus")==0){ + tdev->devconf = (gNautilusConfig*)malloc(sizeof(gNautilusConfig)); // Careful, must be freed in the end + gtecnal_SetDefaultgNautilus((gNautilusConfig*)tdev->devconf,tdev->SamplingRate); + }else{ + fprintf(stderr,"Unkown, unsupported device, crashing!\n"); + exit(EXIT_FAILURE); + } + + char UsedDevConf[100000]; + int ConfDevOutput = gtecnal_SetConfiguration(tdev->Transceiver, tdev->SessionID, tdev->ConnectedDevices, 1, (DevConfig*)tdev->devconf, UsedDevConf); + + if (ConfDevOutput != 0) + exit(EXIT_FAILURE); /* indicate failure.*/ + + char GetConfig[100000]; + int GetConfigurationOutput = gtecnal_GetConfiguration(tdev->Transceiver, tdev->SessionID, 1, GetConfig); + + // Get data info + int foundChannelsALL = 0; + gtecnal_GetDataInfo(tdev->Transceiver, tdev->SessionID, tdev->ScanInfo, &tdev->DataPointsPerScan, &foundChannelsALL, &tdev->ScanCount); + + // Globalize Sampling Rate and calculate ScansPerFrame + tdev->SamplingRate = tdev->devconf->sample_rate; + if( ( (tdev->SamplingRate % 10) == 0 )) { // This means the SF is multiple of 10 rather than of 2 + if( tdev->SamplingRate <= 500) { + tdev->ScansPerFrame = (int)(tdev->SamplingRate/2); // Replace 16 Hz for 10 Hz when SF is multiple of 10 + } else { + tdev->ScansPerFrame = (int)(tdev->SamplingRate/2); // Be more conservative (5 Hz) for high sampling rates + } + } else { // Sampling rate is a multiple of 2 Hz and for the gTec devices goes only up to 512 Hz + tdev->ScansPerFrame = (int)(tdev->SamplingRate/16);// 16 Hz for multiples of 2 + } + + int retSetCap = gtecnet_set_capability(tdev); + + tdev->runacq = 1; + // Open Reading thread + int threadretval = pthread_create(&tdev->thid, NULL, gtecnet_read_fn, tdev); + + if (threadretval < 0) { + gtecnet_close_device(dev); + //Check if I need to destroy anything in case of failure + fprintf(stderr,"Open failed\n"); + return -1; + } + return 0; +} +/****************************************************************** + * g.NEEDaccess methods implementation * + ******************************************************************/ + + + +static +int gtecnet_open_device(struct devmodule* dev, const char* optv[]) +{ + if (init_data_com(dev,optv)) + { + gtecnet_close_device(dev); + return -1; + } + + return 0; +} + + + +static +int gtecnet_set_channel_groups(struct devmodule* dev, unsigned int ngrp, + const struct grpconf* grp) +{ + + struct gtecnet_eegdev* gtecnetdev = get_gtecnet(dev); + struct selected_channels* selch; + int i, nsel = 0; + + nsel = egdi_split_alloc_chgroups(dev, gtecnetdev->chmap, + ngrp, grp, &selch); + for (i=0; imtx); + + if(tdev->usedefmap){ + if(strcmp(tdev->DeviceType,"gUSBamp")==0){ + if(stype == EGD_EEG) + info->label = gtecnetlabelUSBamp[ich]; + if(stype == EGD_SENSOR) + info->label = gtecnetlabelUSBamp[ich + tdev->NchannelsEEG]; + if(stype == EGD_TRIGGER) + info->label = gtecnetlabelUSBamp[ich + tdev->NchannelsEEG + tdev->NchannelsEXG]; + }else if(strcmp(tdev->DeviceType,"gHIamp")==0){ + if(stype == EGD_EEG) + info->label = gtecnetlabelHIamp[ich]; + if(stype == EGD_SENSOR) + info->label = gtecnetlabelHIamp[ich + tdev->NchannelsEEG]; + if(stype == EGD_TRIGGER) + info->label = gtecnetlabelHIamp[ich + tdev->NchannelsEEG + tdev->NchannelsEXG]; + }else if(strcmp(tdev->DeviceType,"gNautilus")==0){ + if(stype == EGD_EEG) + info->label = gtecnetlabelNautilus[ich]; + if(stype == EGD_SENSOR) + info->label = gtecnetlabelNautilus[ich + tdev->NchannelsEEG]; + if(stype == EGD_TRIGGER) + info->label = gtecnetlabelNautilus[ich + tdev->NchannelsEEG + tdev->NchannelsEXG]; + }else{ + info->label = gtecnetlabelGen[ich]; + } + }else{ + info->label = gtecnetlabelGen[ich]; + } + pthread_mutex_unlock(&tdev->mtx); + if (stype != EGD_TRIGGER) { + info->isint = 0; + info->dtype = EGD_FLOAT; + info->min.valfloat = -16384.0; + info->max.valfloat = +16384.0; + info->unit = gtecnetunit; + info->transducter = gtecnettransducter; + info->prefiltering = "Unknown"; + } else { + info->isint = 0; + info->dtype = EGD_FLOAT; + info->min.valint32_t = -8388608; + info->max.valint32_t = 8388607; + info->unit = gtecnetunit_trigger; + info->transducter = gtecnettransducter_trigger; + info->prefiltering = "No Filtering"; + } +} + +API_EXPORTED +const struct egdi_plugin_info eegdev_plugin_info = { + .plugin_abi = EEGDEV_PLUGIN_ABI_VERSION, + .struct_size = sizeof(struct gtecnet_eegdev), + .open_device = gtecnet_open_device, + .close_device = gtecnet_close_device, + .set_channel_groups = gtecnet_set_channel_groups, + .fill_chinfo = gtecnet_fill_chinfo, + .supported_opts = gtecnet_options +}; +