From 6cb3f6c150c6cdfec09f80184906f7bb632e8e7d Mon Sep 17 00:00:00 2001 From: Serafeim Perdikis Date: Tue, 10 Mar 2015 12:39:13 +0100 Subject: [PATCH 01/22] Final version extended with plugins BARV BBT and GTECENET --- configure.ac | 46 ++- doc/Makefile.am | 15 +- doc/eegdev-barv.5 | 35 ++ doc/eegdev-bbt.5 | 19 ++ doc/eegdev-gtecnet.5 | 63 ++++ doc/eegdev-gtecnet.5~ | 63 ++++ doc/egd_open.3 | 79 +++++ src/plugins/Makefile.am | 18 + src/plugins/RecorderRDA.h | 98 ++++++ src/plugins/barv.c | 683 ++++++++++++++++++++++++++++++++++++++ src/plugins/bbt.c | 461 +++++++++++++++++++++++++ src/plugins/gtec.c | 70 ++-- src/plugins/gtecnet.c | 529 +++++++++++++++++++++++++++++ 13 files changed, 2143 insertions(+), 36 deletions(-) create mode 100644 doc/eegdev-barv.5 create mode 100644 doc/eegdev-bbt.5 create mode 100644 doc/eegdev-gtecnet.5 create mode 100644 doc/eegdev-gtecnet.5~ create mode 100644 doc/egd_open.3 create mode 100644 src/plugins/RecorderRDA.h create mode 100644 src/plugins/barv.c create mode 100644 src/plugins/bbt.c create mode 100644 src/plugins/gtecnet.c diff --git a/configure.ac b/configure.ac index b23d033..6ea1ba6 100644 --- a/configure.ac +++ b/configure.ac @@ -160,6 +160,48 @@ 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 + AX_DEFINE_DIR([LIBDIR], [libdir], [Installed library path]) AX_DEFINE_DIR([SYSCONFDIR], [sysconfdir], [System configuration files path]) @@ -193,5 +235,7 @@ 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 " - diff --git a/doc/Makefile.am b/doc/Makefile.am index cdade38..adda807 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,15 @@ 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 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..ea2a4aa --- /dev/null +++ b/doc/eegdev-bbt.5 @@ -0,0 +1,19 @@ +.\"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 does not support any options. +.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..a18cd90 --- /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 [256;512] + +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..a18cd90 --- /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 [256;512] + +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..e5ba515 --- /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/lib/x86_64-linux-gnu/etc/eegdev\fP. +.SH "SEE ALSO" +.BR egd_close (3), +.BR eegdev-open-options (5) diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index de92b9d..f93ab4f 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -34,3 +34,21 @@ 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) 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/barv.c b/src/plugins/barv.c new file mode 100644 index 0000000..561e370 --- /dev/null +++ b/src/plugins/barv.c @@ -0,0 +1,683 @@ +/* + 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; + 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[]) +{ + 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..22ef6fa --- /dev/null +++ b/src/plugins/bbt.c @@ -0,0 +1,461 @@ +/* + 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[24]; +}; + +#define DEFAULT_BBTDEV "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 bbtlabel[24][NUM_CHANNELS_LOOP] = { + "01. Fz ", "02. FC3", "03. FCz", "04. FC4", "05. C3 ", "06. Cz ", "07. C4 ", + "08. CP3", "09. CPz", "10. CP4", "11. P5 ", "12. POz", "13. P6 ", "14. O1 ", + "15. O2 ", "16. Ear", "17. EX1", "18. EX2", "19. EX3", "20. EX4", "21. EX5", "22. EX6", "23. Tri" +}; + +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 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 +}; +static const int bbt_provided_stypes[] = {EGD_EEG}; + +static const struct egdi_optname bbt_options[] = { + {.name = "baddr", .defvalue = DEFAULT_BBTDEV}, + {.name = NULL} +}; + + +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, const char* baddr) +{ + 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 = baddr; + + 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 bbt_eegdev* tdev) +{ + struct timespec tim; + tim.tv_sec = 0; + tim.tv_nsec = 500000000; + struct devmodule* dev = &tdev->dev; + + + struct sockaddr_rc addr = { 0 }; + int s, status; + char dest[18] = DEFAULT_BBTDEV; + + + bbt_set_capability(tdev, DEFAULT_BBTDEV); + + + // 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( dest, &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 = -1; + 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; + // Free channels metadata + + + free(tdev->chmap); + + + 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"); + return 0; +} +static +int bbt_open_device(struct devmodule* dev, const char* optv[]) +{ + + struct bbt_eegdev* tdev = get_bbt(dev); + + if (init_data_com(tdev)) + { + 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* bbtdev = get_bbt(dev); + struct selected_channels* selch; + int i, nsel = 0; + + nsel = egdi_split_alloc_chgroups(dev, bbtdev->chmap, + ngrp, grp, &selch); + for (i=0; imtx); + + info->label = bbtlabel[ich]; + //printf("Label %d: %s\n", ich, info->label); + pthread_mutex_unlock(&tdev->mtx); + if (stype != EGD_TRIGGER) { + info->isint = 0; + info->dtype = EGD_DOUBLE; + info->min.valfloat = -16384.0; + info->max.valfloat = +16384.0; + info->unit = bbtunit; + info->transducter = bbttransducter; + info->prefiltering = "Unknown"; + } 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_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 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/gtec.c b/src/plugins/gtec.c index 6f498f2..ceeb731 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 = "lowpasspass", .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..6affe5a --- /dev/null +++ b/src/plugins/gtecnet.c @@ -0,0 +1,529 @@ +/* + 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","TP7","PO3","POz","PO4","PO8","O1","Oz","O2", + "TP9","TP10","A1","A2", + "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] = { + "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", "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)); + + // Start streaming + int StartStreaming = gtecnal_StartStreaming(tdev->Transceiver, tdev->SessionID); + + //Flush buffer + struct timeval start, end; + while(true){ + gettimeofday(&start,NULL); + 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; + if(ElapsedTime > (1000*((float)ScansRead/(float)tdev->SamplingRate) - 2.0f) ){ + break; + } + } + + while(true){ + if(tdev->runacq){ + //gettimeofday(&start,NULL); + int ScansRead = gtecnal_ReadDataFrame(tdev->Transceiver, tdev->databuffer, tdev->SessionID, &tdev->sockdata, tdev->ScansPerFrame, tdev->DataPointsPerScan); + ci->update_ringbuffer(&(tdev->dev), tdev->databuffer, ScansRead * tdev->DataPointsPerScan * sizeof(float)); + //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); + }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); // TO REVIEW + }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); + + // Check that the channels found are as many as expected + if(tdev->NchannelsALL != foundChannelsALL){ + fprintf(stderr,"%d channels expected, %d found. Crashing...\n",tdev->NchannelsALL,foundChannelsALL); + exit(EXIT_FAILURE); + } + + // Globalize Sampling Rate and calculate ScansPerFrame + tdev->SamplingRate = tdev->devconf->sample_rate; + tdev->ScansPerFrame = (int)(tdev->SamplingRate/16);// This is a hard decision to always read frames of size SF/16 (aka, 16Hz reading) + + int retSetCap = gtecnet_set_capability(tdev); + + //DataReadyEvent set always to ScansPerFrame (could be a bug on their side) + int DataReadyEventThreshouldOutput = gtecnal_DataReadyEventThreshold(tdev->Transceiver, tdev->SessionID, tdev->ScansPerFrame); + + // Start acquisition + int StartAcqOutput = gtecnal_StartAcquisition(tdev->Transceiver, tdev->SessionID); + + 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 +}; + From ce34a0cf68cc407293dc5cb1d9a333c1ed3e41fb Mon Sep 17 00:00:00 2001 From: Serafeim Perdikis Date: Mon, 2 Nov 2015 23:30:21 +0100 Subject: [PATCH 02/22] Applied Dejan Latinovic's patch as workaround for the bug in bison that id not allow eegdev to compile as of Ubuntu 13.04. --- doc/egd_open.3 | 2 +- src/core/conffile.h | 32 +++++++++++++++++++++++++++++--- src/core/conffile.l | 2 +- src/core/conffile.y | 10 +++++++++- src/core/confline.h | 9 ++++++++- src/core/confline.l | 2 +- src/core/confline.y | 10 +++++++++- 7 files changed, 58 insertions(+), 9 deletions(-) diff --git a/doc/egd_open.3 b/doc/egd_open.3 index e5ba515..c5773c7 100644 --- a/doc/egd_open.3 +++ b/doc/egd_open.3 @@ -73,7 +73,7 @@ installation folder of the libraries. .PD This variable controls which folder should be search to find the configuration files. If unset, they will be searched in -\fB/usr/lib/x86_64-linux-gnu/etc/eegdev\fP. +\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..f175205 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" %} %% 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..b9e3801 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" %} %% 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); %} From c90840aed81a231cf5be48b57be0d9e701dc4802 Mon Sep 17 00:00:00 2001 From: Simis Date: Tue, 3 Nov 2015 13:52:26 +0100 Subject: [PATCH 03/22] Fixes to gtecnet plugin. FInal fixes based on GTec support feedback pending. gHIamp is usable with this plugin, despite a weird slow reading until the first buffer overflow. gNautilus (problem with XML configuration) an gUSBamp (unresolved) currently not working with this plugin --- src/plugins/gtecnet.c | 45 ++++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/src/plugins/gtecnet.c b/src/plugins/gtecnet.c index 6affe5a..348fac6 100644 --- a/src/plugins/gtecnet.c +++ b/src/plugins/gtecnet.c @@ -185,30 +185,25 @@ void* gtecnet_read_fn(void *data) const struct core_interface* restrict ci = &tdev->dev.ci; tdev->databuffer = (float*)malloc(tdev->ScansPerFrame * tdev->DataPointsPerScan * sizeof(float)); + + //Disable DataReadyEventThreshold + 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); - //Flush buffer - struct timeval start, end; - while(true){ - gettimeofday(&start,NULL); - 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; - if(ElapsedTime > (1000*((float)ScansRead/(float)tdev->SamplingRate) - 2.0f) ){ - break; - } - } - + struct timeval start, end; while(true){ if(tdev->runacq){ - //gettimeofday(&start,NULL); + gettimeofday(&start,NULL); 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)); - //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); }else{ // Exit reading thread pthread_exit(NULL); @@ -374,7 +369,7 @@ int init_data_com(struct devmodule* dev, const char* optv[]) 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); // TO REVIEW + gtecnal_SetDefaultgNautilus((gNautilusConfig*)tdev->devconf,tdev->SamplingRate); }else{ fprintf(stderr,"Unkown, unsupported device, crashing!\n"); exit(EXIT_FAILURE); @@ -394,23 +389,17 @@ int init_data_com(struct devmodule* dev, const char* optv[]) gtecnal_GetDataInfo(tdev->Transceiver, tdev->SessionID, tdev->ScanInfo, &tdev->DataPointsPerScan, &foundChannelsALL, &tdev->ScanCount); // Check that the channels found are as many as expected - if(tdev->NchannelsALL != foundChannelsALL){ - fprintf(stderr,"%d channels expected, %d found. Crashing...\n",tdev->NchannelsALL,foundChannelsALL); - exit(EXIT_FAILURE); - } + //if(tdev->NchannelsALL != foundChannelsALL){ + // fprintf(stderr,"%d channels expected, %d found. Crashing...\n",tdev->NchannelsALL,foundChannelsALL); + // exit(EXIT_FAILURE); + //} // Globalize Sampling Rate and calculate ScansPerFrame tdev->SamplingRate = tdev->devconf->sample_rate; tdev->ScansPerFrame = (int)(tdev->SamplingRate/16);// This is a hard decision to always read frames of size SF/16 (aka, 16Hz reading) - + int retSetCap = gtecnet_set_capability(tdev); - //DataReadyEvent set always to ScansPerFrame (could be a bug on their side) - int DataReadyEventThreshouldOutput = gtecnal_DataReadyEventThreshold(tdev->Transceiver, tdev->SessionID, tdev->ScansPerFrame); - - // Start acquisition - int StartAcqOutput = gtecnal_StartAcquisition(tdev->Transceiver, tdev->SessionID); - tdev->runacq = 1; // Open Reading thread int threadretval = pthread_create(&tdev->thid, NULL, gtecnet_read_fn, tdev); From 20cd822b67cf7d5cd4a422e4b8b173a9474cc980 Mon Sep 17 00:00:00 2001 From: Simis Date: Tue, 3 Nov 2015 13:53:46 +0100 Subject: [PATCH 04/22] Fixed bug on gHIamp channel labels. Changed TP7 to the correct PO7 --- src/plugins/gtecnet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/gtecnet.c b/src/plugins/gtecnet.c index 348fac6..f2f868c 100644 --- a/src/plugins/gtecnet.c +++ b/src/plugins/gtecnet.c @@ -111,7 +111,7 @@ static const char gtecnetlabelHIamp[81][10] = { "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","TP7","PO3","POz","PO4","PO8","O1","Oz","O2", + "P6","P8","PO7","PO3","POz","PO4","PO8","O1","Oz","O2", "TP9","TP10","A1","A2", "EXG1","EXG2","EXG3","EXG4","EXG5","EXG6", "EXG7","EXG8","EXG9","EXG10","EXG11","EXG12","EXG13","EXG14","EXG15","EXG16", "TRIG" From e6eb3e4103fa6b36e14e6245dccfb9489a6d6347 Mon Sep 17 00:00:00 2001 From: Simis Date: Tue, 3 Nov 2015 13:59:06 +0100 Subject: [PATCH 05/22] Fixed biosemi bug: Incorrectly the 64 label map was always used. in fill channel info function --- src/plugins/biosemi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/biosemi.c b/src/plugins/biosemi.c index 4ec9c7c..f80a63a 100644 --- a/src/plugins/biosemi.c +++ b/src/plugins/biosemi.c @@ -641,13 +641,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; From 98831de092a6deca2c4d216324c930268841e5d3 Mon Sep 17 00:00:00 2001 From: Simis Date: Tue, 3 Nov 2015 14:00:22 +0100 Subject: [PATCH 06/22] Ficed bug: Option lowpasspass changed to the correct lowpass --- src/plugins/gtec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/gtec.c b/src/plugins/gtec.c index ceeb731..baf5d18 100644 --- a/src/plugins/gtec.c +++ b/src/plugins/gtec.c @@ -122,7 +122,7 @@ 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_LP] = {.name = "lowpass", .defvalue = "-1"}, [OPT_NOTCH] = {.name = "notch", .defvalue = "50"}, [OPT_FS] = {.name = "samplerate", .defvalue = "512"}, [NUMOPT] = {.name = NULL} From 5b55fffd68563f4fcbb1e4fb9bf92f3c1cb333cd Mon Sep 17 00:00:00 2001 From: Serafeim Perdikis Date: Wed, 4 Nov 2015 00:56:47 +0100 Subject: [PATCH 07/22] Made device bluetooth address an option ratehr than hardcoded. Updated doc for bbt plugin. To betesetd by Lucian --- doc/eegdev-bbt.5 | 7 ++++++- src/plugins/bbt.c | 41 ++++++++++++++++++++++++----------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/doc/eegdev-bbt.5 b/doc/eegdev-bbt.5 index ea2a4aa..9e8f568 100644 --- a/doc/eegdev-bbt.5 +++ b/doc/eegdev-bbt.5 @@ -11,7 +11,12 @@ in the 10-20 system), 6 EMG channels and 1 trigger input channel. Data acquisiti is wireless (bluetooth). .SH CONFIGURATION .LP -This plugin does not support any options. +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), diff --git a/src/plugins/bbt.c b/src/plugins/bbt.c index 22ef6fa..181078f 100644 --- a/src/plugins/bbt.c +++ b/src/plugins/bbt.c @@ -46,10 +46,10 @@ struct bbt_eegdev { pthread_mutex_t mtx; FILE *rfcomm; unsigned int runacq; - char bt_addr[24]; + char bt_addr[18]; }; -#define DEFAULT_BBTDEV "00:06:66:A0:4D:B7" +#define DEFAULT_ADDRESS "00:06:66:A0:4D:B7" #define NUM_CHANNELS_LOOP 23 #define SAMPLING_RATE 256 #define BLOCK_SIZE 1 @@ -90,12 +90,21 @@ static const union gval bbt_scales[EGD_NUM_DTYPE] = { }; static const int bbt_provided_stypes[] = {EGD_EEG}; +enum {OPT_ADDRESS, NUMOPT}; static const struct egdi_optname bbt_options[] = { - {.name = "baddr", .defvalue = DEFAULT_BBTDEV}, - {.name = NULL} + [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) { @@ -254,7 +263,7 @@ void* bbt_read_fn(void *data) static -int bbt_set_capability(struct bbt_eegdev* bbtdev, const char* baddr) +int bbt_set_capability(struct bbt_eegdev* bbtdev) { struct bbt_eegdev* tdev = get_bbt(bbtdev); tdev->chmap = malloc(NUM_CHANNELS_LOOP*sizeof(*tdev->chmap)); @@ -283,7 +292,7 @@ int bbt_set_capability(struct bbt_eegdev* bbtdev, const char* baddr) // Fill the capabilities metadata cap.sampling_freq = SAMPLING_RATE; cap.device_type = "BBT"; - cap.device_id = baddr; + cap.device_id = tdev->bt_addr; struct devmodule* dev = &tdev->dev; @@ -297,20 +306,20 @@ int bbt_set_capability(struct bbt_eegdev* bbtdev, const char* baddr) static -int init_data_com(struct bbt_eegdev* tdev) +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; - struct devmodule* dev = &tdev->dev; - struct sockaddr_rc addr = { 0 }; int s, status; - char dest[18] = DEFAULT_BBTDEV; - - bbt_set_capability(tdev, DEFAULT_BBTDEV); + bbt_set_capability(tdev); // allocate a socket @@ -321,7 +330,7 @@ int init_data_com(struct bbt_eegdev* tdev) memset(&addr, 0, sizeof(addr)); addr.rc_family = AF_BLUETOOTH; addr.rc_channel = (uint8_t) 1; - str2ba( dest, &addr.rc_bdaddr ); + str2ba( tdev->bt_addr, &addr.rc_bdaddr ); // connect to server if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { @@ -349,7 +358,7 @@ int init_data_com(struct bbt_eegdev* tdev) if (threadretval < 0) { fclose(tdev->rfcomm); - tdev->rfcomm = -1; + tdev->rfcomm = NULL; return -1; } tdev->runacq = 1; @@ -389,9 +398,7 @@ static int bbt_open_device(struct devmodule* dev, const char* optv[]) { - struct bbt_eegdev* tdev = get_bbt(dev); - - if (init_data_com(tdev)) + if (init_data_com(dev, optv)) { bbt_close_device(dev); return -1; From 5e1c17d9f4cf4c748fab3b42361ebce7f26ae2c5 Mon Sep 17 00:00:00 2001 From: Serafeim Perdikis Date: Thu, 5 Nov 2015 12:46:44 +0100 Subject: [PATCH 08/22] Added semicolon : as possible text in bison parser, so that bluetooth addresses can be parsed for bbt and beurosky plugins --- src/core/conffile.l | 2 +- src/core/confline.l | 2 +- src/plugins/bbt.c | 4 ---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/core/conffile.l b/src/core/conffile.l index f175205..2004273 100644 --- a/src/core/conffile.l +++ b/src/core/conffile.l @@ -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/confline.l b/src/core/confline.l index b9e3801..4472f97 100644 --- a/src/core/confline.l +++ b/src/core/confline.l @@ -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/plugins/bbt.c b/src/plugins/bbt.c index 181078f..890fdd2 100644 --- a/src/plugins/bbt.c +++ b/src/plugins/bbt.c @@ -101,7 +101,6 @@ 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]); } @@ -308,7 +307,6 @@ int bbt_set_capability(struct bbt_eegdev* bbtdev) static int init_data_com(struct devmodule* dev, const char* optv[]) { - parse_bbt_options(optv, dev); struct bbt_eegdev* tdev = get_bbt(dev); @@ -321,7 +319,6 @@ int init_data_com(struct devmodule* dev, const char* optv[]) bbt_set_capability(tdev); - // allocate a socket s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); fcntl(s, F_SETFD, fcntl(s, F_GETFD)|FD_CLOEXEC); @@ -397,7 +394,6 @@ int bbt_close_device(struct devmodule* dev) static int bbt_open_device(struct devmodule* dev, const char* optv[]) { - if (init_data_com(dev, optv)) { bbt_close_device(dev); From 77c2fc76a14eeaccd5434ce622047dbabc95abfa Mon Sep 17 00:00:00 2001 From: Serafeim Perdikis Date: Thu, 5 Nov 2015 17:08:20 +0100 Subject: [PATCH 09/22] Moved set_capabilities of BBT plugin just before the reding thred is launched --- src/plugins/bbt.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/plugins/bbt.c b/src/plugins/bbt.c index 890fdd2..1650d0e 100644 --- a/src/plugins/bbt.c +++ b/src/plugins/bbt.c @@ -317,8 +317,6 @@ int init_data_com(struct devmodule* dev, const char* optv[]) struct sockaddr_rc addr = { 0 }; int s, status; - bbt_set_capability(tdev); - // allocate a socket s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); fcntl(s, F_SETFD, fcntl(s, F_GETFD)|FD_CLOEXEC); @@ -351,6 +349,9 @@ int init_data_com(struct devmodule* dev, const char* optv[]) // Give it some time nanosleep(&tim,NULL); + // Set capabilities + bbt_set_capability(tdev); + int threadretval = pthread_create(&tdev->thid, NULL, bbt_read_fn, tdev); if (threadretval < 0) { @@ -372,8 +373,6 @@ int bbt_close_device(struct devmodule* dev) struct bbt_eegdev* tdev = get_bbt(dev); tdev->runacq = 0; // Free channels metadata - - free(tdev->chmap); From d624ea7ac4dfef8c476652a052434368f5425009 Mon Sep 17 00:00:00 2001 From: Simis Date: Mon, 9 Nov 2015 13:12:55 +0100 Subject: [PATCH 10/22] Eliminated another buf in eeglabel32 for the biosemi plugin: channel P9 removed from label list --- src/plugins/biosemi.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/plugins/biosemi.c b/src/plugins/biosemi.c index f80a63a..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" }; From 2c5c3263ce3f51887d28400c78898352832d2a99 Mon Sep 17 00:00:00 2001 From: Serafeim Perdikis Date: Sat, 14 Nov 2015 12:07:51 +0100 Subject: [PATCH 11/22] Changed the labels of the BBT plugin so that when saved files are loaded by the eegdev datafile plugin, their type is correctly interpreted. Datafile (fileout.c) uses a regex interpretation of the labels, as there is no other way to transmit the information of the channel type. Labels should not be arithmetically enumerated --- src/plugins/bbt.c | 62 ++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/src/plugins/bbt.c b/src/plugins/bbt.c index 1650d0e..3606e87 100644 --- a/src/plugins/bbt.c +++ b/src/plugins/bbt.c @@ -72,23 +72,28 @@ struct bbt_eegdev { ******************************************************************/ -static const char bbtlabel[24][NUM_CHANNELS_LOOP] = { - "01. Fz ", "02. FC3", "03. FCz", "04. FC4", "05. C3 ", "06. Cz ", "07. C4 ", - "08. CP3", "09. CPz", "10. CP4", "11. P5 ", "12. POz", "13. P6 ", "14. O1 ", - "15. O2 ", "16. Ear", "17. EX1", "18. EX2", "19. EX3", "20. EX4", "21. EX5", "22. EX6", "23. Tri" +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 }; -static const int bbt_provided_stypes[] = {EGD_EEG}; enum {OPT_ADDRESS, NUMOPT}; static const struct egdi_optname bbt_options[] = { @@ -269,7 +274,7 @@ int bbt_set_capability(struct bbt_eegdev* bbtdev) //EEG for (int i=0; ichmap[i].dtype = EGD_FLOAT; + tdev->chmap[i].dtype = EGD_FLOAT; tdev->chmap[i].stype = EGD_EEG; } @@ -314,6 +319,9 @@ int init_data_com(struct devmodule* dev, const char* optv[]) tim.tv_sec = 0; tim.tv_nsec = 500000000; + // Set capabilities + bbt_set_capability(tdev); + struct sockaddr_rc addr = { 0 }; int s, status; @@ -324,7 +332,8 @@ int init_data_com(struct devmodule* dev, const char* optv[]) // set the connection parameters (who to connect to) memset(&addr, 0, sizeof(addr)); addr.rc_family = AF_BLUETOOTH; - addr.rc_channel = (uint8_t) 1; + //addr.rc_channel = (uint8_t) 1; + addr.rc_channel = (uint8_t) 6; str2ba( tdev->bt_addr, &addr.rc_bdaddr ); // connect to server @@ -349,9 +358,6 @@ int init_data_com(struct devmodule* dev, const char* optv[]) // Give it some time nanosleep(&tim,NULL); - // Set capabilities - bbt_set_capability(tdev); - int threadretval = pthread_create(&tdev->thid, NULL, bbt_read_fn, tdev); if (threadretval < 0) { @@ -372,8 +378,6 @@ int bbt_close_device(struct devmodule* dev) printf("Closing bbt device\n"); struct bbt_eegdev* tdev = get_bbt(dev); tdev->runacq = 0; - // Free channels metadata - free(tdev->chmap); if (fwrite("p", sizeof(char), 1, tdev->rfcomm) < 0) { @@ -388,6 +392,9 @@ int bbt_close_device(struct devmodule* dev) fclose(tdev->rfcomm); } printf("Closed successfully\n"); + + // Free channels metadata + free(tdev->chmap); return 0; } static @@ -408,12 +415,11 @@ int bbt_set_channel_groups(struct devmodule* dev, unsigned int ngrp, const struct grpconf* grp) { - struct bbt_eegdev* bbtdev = get_bbt(dev); + struct bbt_eegdev* tdev = get_bbt(dev); struct selected_channels* selch; int i, nsel = 0; - nsel = egdi_split_alloc_chgroups(dev, bbtdev->chmap, - ngrp, grp, &selch); + nsel = egdi_split_alloc_chgroups(dev, tdev->chmap, ngrp, grp, &selch); for (i=0; imtx); - - info->label = bbtlabel[ich]; - //printf("Label %d: %s\n", ich, info->label); - pthread_mutex_unlock(&tdev->mtx); - if (stype != EGD_TRIGGER) { + if (stype == EGD_EEG) { + 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 = bbtlabeleeg[ich]; + } else if (stype == EGD_SENSOR) { info->isint = 0; - info->dtype = EGD_DOUBLE; + info->dtype = EGD_FLOAT; info->min.valfloat = -16384.0; info->max.valfloat = +16384.0; info->unit = bbtunit; info->transducter = bbttransducter; - info->prefiltering = "Unknown"; + 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_trigger; - info->prefiltering = "No Filtering"; + info->transducter = bbttransducter; + info->prefiltering = bbtprefiltering; + info->label = bbtlabeltri; } } From c94c38f3220cd195b3a1be3049ce41ed672e08bc Mon Sep 17 00:00:00 2001 From: Serafeim Perdikis Date: Sat, 14 Nov 2015 12:19:43 +0100 Subject: [PATCH 12/22] Simply reverted the channel number to 1 instead of 6 (used for the bBT simulator) --- src/plugins/bbt.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plugins/bbt.c b/src/plugins/bbt.c index 3606e87..dd0dff8 100644 --- a/src/plugins/bbt.c +++ b/src/plugins/bbt.c @@ -332,8 +332,7 @@ int init_data_com(struct devmodule* dev, const char* optv[]) // set the connection parameters (who to connect to) memset(&addr, 0, sizeof(addr)); addr.rc_family = AF_BLUETOOTH; - //addr.rc_channel = (uint8_t) 1; - addr.rc_channel = (uint8_t) 6; + addr.rc_channel = (uint8_t) 1; str2ba( tdev->bt_addr, &addr.rc_bdaddr ); // connect to server From 296c8fc51a7d8586675b658a709e3f3dd2f5d38f Mon Sep 17 00:00:00 2001 From: localcnbiadmin Date: Mon, 16 Nov 2015 14:36:10 +0100 Subject: [PATCH 13/22] Supported gNautilus in gtecnet plugin with minimal configuration. Fixed wrong sampling rates for gNautilus in man doc --- doc/eegdev-gtecnet.5 | 2 +- doc/eegdev-gtecnet.5~ | 2 +- src/plugins/gtecnet.c | 24 +++++++++++++----------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/doc/eegdev-gtecnet.5 b/doc/eegdev-gtecnet.5 index a18cd90..e8a6859 100644 --- a/doc/eegdev-gtecnet.5 +++ b/doc/eegdev-gtecnet.5 @@ -32,7 +32,7 @@ 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 [256;512] +g.Nautilus [250;500] Default value: "512". .TP diff --git a/doc/eegdev-gtecnet.5~ b/doc/eegdev-gtecnet.5~ index a18cd90..e8a6859 100644 --- a/doc/eegdev-gtecnet.5~ +++ b/doc/eegdev-gtecnet.5~ @@ -32,7 +32,7 @@ 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 [256;512] +g.Nautilus [250;500] Default value: "512". .TP diff --git a/src/plugins/gtecnet.c b/src/plugins/gtecnet.c index f2f868c..5b070fa 100644 --- a/src/plugins/gtecnet.c +++ b/src/plugins/gtecnet.c @@ -122,10 +122,10 @@ static const char gtecnetlabelUSBamp[17][10] = { }; static const char gtecnetlabelNautilus[33][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", "TRIG" + "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] = { @@ -387,16 +387,18 @@ int init_data_com(struct devmodule* dev, const char* optv[]) // Get data info int foundChannelsALL = 0; gtecnal_GetDataInfo(tdev->Transceiver, tdev->SessionID, tdev->ScanInfo, &tdev->DataPointsPerScan, &foundChannelsALL, &tdev->ScanCount); - - // Check that the channels found are as many as expected - //if(tdev->NchannelsALL != foundChannelsALL){ - // fprintf(stderr,"%d channels expected, %d found. Crashing...\n",tdev->NchannelsALL,foundChannelsALL); - // exit(EXIT_FAILURE); - //} // Globalize Sampling Rate and calculate ScansPerFrame tdev->SamplingRate = tdev->devconf->sample_rate; - tdev->ScansPerFrame = (int)(tdev->SamplingRate/16);// This is a hard decision to always read frames of size SF/16 (aka, 16Hz reading) + 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/10); // Replace 16 Hz for 10 Hz when SF is multiple of 10 + } else { + tdev->ScansPerFrame = (int)(tdev->SamplingRate/5); // 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); From b0756074950d30e14a27800300d34727c89ae57a Mon Sep 17 00:00:00 2001 From: BCI FES stroke acute Date: Tue, 23 Feb 2016 16:15:06 +0100 Subject: [PATCH 14/22] Cganged reading rate to 4 Hz (for samplong freqs multiple of 2) which is what it seems the current API can support, contrary to what advertised. It has to be changed to 16 once an updated net API supports it. Also, disabled data ready event threshold: --- src/plugins/gtecnet.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/plugins/gtecnet.c b/src/plugins/gtecnet.c index 5b070fa..7dabcf7 100644 --- a/src/plugins/gtecnet.c +++ b/src/plugins/gtecnet.c @@ -187,7 +187,7 @@ void* gtecnet_read_fn(void *data) tdev->databuffer = (float*)malloc(tdev->ScansPerFrame * tdev->DataPointsPerScan * sizeof(float)); //Disable DataReadyEventThreshold - int DataReadyEventThreshouldOutput = gtecnal_DataReadyEventThreshold(tdev->Transceiver, tdev->SessionID, tdev->ScansPerFrame); + //int DataReadyEventThreshouldOutput = gtecnal_DataReadyEventThreshold(tdev->Transceiver, tdev->SessionID, tdev->ScansPerFrame); // Start acquisition int StartAcqOutput = gtecnal_StartAcquisition(tdev->Transceiver, tdev->SessionID); @@ -392,12 +392,13 @@ int init_data_com(struct devmodule* dev, const char* optv[]) 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/10); // Replace 16 Hz for 10 Hz when SF is multiple of 10 + tdev->ScansPerFrame = (int)(tdev->SamplingRate/2); // Replace 16 Hz for 10 Hz when SF is multiple of 10 } else { - tdev->ScansPerFrame = (int)(tdev->SamplingRate/5); // Be more conservative (5 Hz) for high sampling rates + 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 + //tdev->ScansPerFrame = (int)(tdev->SamplingRate/16);// 16 Hz for multiples of 2 + tdev->ScansPerFrame = (int)(tdev->SamplingRate/4);// 4 Hz for multiples of 2 } int retSetCap = gtecnet_set_capability(tdev); From a1800e4ee0b438049ede12d445c217b103c420c2 Mon Sep 17 00:00:00 2001 From: Simis Date: Fri, 11 Mar 2016 18:44:24 +0100 Subject: [PATCH 15/22] First versino fo arduino plugin. Still debugging --- configure.ac | 17 +++++++++++++++++ src/plugins/Makefile.am | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/configure.ac b/configure.ac index 6ea1ba6..5d88b64 100644 --- a/configure.ac +++ b/configure.ac @@ -202,6 +202,22 @@ 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([str2ba], [bluetooth], [arduino_support=yes], + [arduino_support=no; if test "x$with_arduino" != xcheck; then + AC_MSG_FAILURE([bluetooth library required for neurosky 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]) @@ -238,4 +254,5 @@ Configuration summary BARV support : $barv_support BBT support : $bbt_support gTecNet support : $gtecnet_support + Arduino support : $arduino_support " diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index f93ab4f..963bf7d 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -52,3 +52,9 @@ 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) From 9bd26891819dc2403267690b5b264434d2a42205 Mon Sep 17 00:00:00 2001 From: Simis Date: Fri, 11 Mar 2016 18:46:33 +0100 Subject: [PATCH 16/22] Added arduino.c --- src/plugins/arduino.c | 280 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 src/plugins/arduino.c diff --git a/src/plugins/arduino.c b/src/plugins/arduino.c new file mode 100644 index 0000000..166de79 --- /dev/null +++ b/src/plugins/arduino.c @@ -0,0 +1,280 @@ +/* + 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; + 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[] = "uV"; +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, NUMOPT}; +static const struct egdi_optname arduino_options[] = { + [OPT_PORT] = {.name = "port", .defvalue = "/dev/ttyACM0"}, + {.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; + + /* Allocate memory for read buffer */ + char buf[1]; + memset (&buf, '\0', sizeof(buf)); + float convertedbuf[16]; + int counter = 0; + + 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); + int n = read(arduinodev->pid, &buf , sizeof(buf)); + 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,n); + /* Error Handling */ + if(n<0) + { + fprintf(stdout,"Error reading: %s\n",strerror(errno)); + } + fprintf(stdout,"%f\n", convertedbuf[counter]); + + convertedbuf[counter] = (float)((int)(buf[0])); + if(counter >= 15){ + counter=0; + // Update the eegdev structure with the new data + if (ci->update_ringbuffer(&(arduinodev->dev), convertedbuf, 1*16*4)) + break; + } + counter++; + } + + 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 = 64, + .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); + + //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 = 1; + 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 +}; + From ff599919ec7a27ca84957a91df7b264eddad8f14 Mon Sep 17 00:00:00 2001 From: Serafeim Perdikis Date: Sun, 13 Mar 2016 01:01:35 +0100 Subject: [PATCH 17/22] Added backend for reading an analog input from an Arduino board (eegdev-arduino plugin) --- configure.ac | 4 +-- doc/Makefile.am | 4 +++ doc/eegdev-arduino.5 | 24 +++++++++++++++ src/plugins/arduino.c | 69 ++++++++++++++++++++++++++----------------- 4 files changed, 72 insertions(+), 29 deletions(-) create mode 100644 doc/eegdev-arduino.5 diff --git a/configure.ac b/configure.ac index 5d88b64..dbc7adb 100644 --- a/configure.ac +++ b/configure.ac @@ -209,9 +209,9 @@ 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([str2ba], [bluetooth], [arduino_support=yes], + AC_SEARCH_LIBS([pthread_create], [pthread], [arduino_support=yes], [arduino_support=no; if test "x$with_arduino" != xcheck; then - AC_MSG_FAILURE([bluetooth library required for neurosky support]) + 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") diff --git a/doc/Makefile.am b/doc/Makefile.am index adda807..7138e61 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -51,3 +51,7 @@ 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/src/plugins/arduino.c b/src/plugins/arduino.c index 166de79..f3f6acf 100644 --- a/src/plugins/arduino.c +++ b/src/plugins/arduino.c @@ -41,6 +41,7 @@ struct arduino_eegdev { struct termios tty; pthread_t thread_id; int pid; + float refmv; pthread_mutex_t acqlock; unsigned int runacq; char* pname; @@ -53,7 +54,7 @@ struct arduino_eegdev { static const char arduinolabel[NCH][8] = { "EMG1" }; -static const char arduinounit[] = "uV"; +static const char arduinounit[] = "mV"; static const char arduinotransducter[] = "EMG electrode"; static const union gval arduino_scales[EGD_NUM_DTYPE] = { @@ -63,9 +64,10 @@ static const union gval arduino_scales[EGD_NUM_DTYPE] = { }; static const int arduino_provided_stypes[] = {EGD_SENSOR}; -enum {OPT_PORT, NUMOPT}; +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} }; @@ -75,11 +77,13 @@ static void* arduino_read_fn(void* 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 buf[1]; - memset (&buf, '\0', sizeof(buf)); - float convertedbuf[16]; - int counter = 0; + char header; + char lowByte; + char highByte; + float* fsample = (float*)malloc(1*sizeof(float)); struct timeval start, end; while (1) { @@ -89,29 +93,37 @@ static void* arduino_read_fn(void* arg) if (!runacq) break; - /* *** READ *** */ - gettimeofday(&start,NULL); - int n = read(arduinodev->pid, &buf , sizeof(buf)); + /* *** 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,"\n%f milliseconds for %d frames\n",ElapsedTime,n); - /* Error Handling */ - if(n<0) - { - fprintf(stdout,"Error reading: %s\n",strerror(errno)); - } - fprintf(stdout,"%f\n", convertedbuf[counter]); + 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; - convertedbuf[counter] = (float)((int)(buf[0])); - if(counter >= 15){ - counter=0; - // Update the eegdev structure with the new data - if (ci->update_ringbuffer(&(arduinodev->dev), convertedbuf, 1*16*4)) - break; - } - counter++; } - return NULL; error: ci->report_error(&(arduinodev->dev), EIO); @@ -123,7 +135,7 @@ static int arduino_set_capability(struct arduino_eegdev* arduinodev) { struct systemcap cap = { - .sampling_freq = 64, + .sampling_freq = 256, .type_nch = {[EGD_SENSOR] = NCH}, .device_type = "Arduino", .device_id = "Arduino" @@ -144,6 +156,9 @@ 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]; @@ -239,7 +254,7 @@ int arduino_set_channel_groups(struct devmodule* dev, unsigned int ngrp, // Set parameters of (eeg -> ringbuffer) selch[i].in_offset = grp[i].index*sizeof(int32_t); selch[i].inlen = grp[i].nch*sizeof(int32_t); - selch[i].bsc = 1; + selch[i].bsc = 0; selch[i].typein = EGD_FLOAT; selch[i].sc = arduino_scales[grp[i].datatype]; selch[i].typeout = grp[i].datatype; From 82e10086d40415a833af279817744ceec93f3a16 Mon Sep 17 00:00:00 2001 From: Serafeim Perdikis Date: Thu, 31 Mar 2016 00:30:13 +0200 Subject: [PATCH 18/22] Changed HIamp channel map to the one used in SUVA. TP9 TP10 replaced by F9 F10. A1 A2 replaced by REF1 REF2 --- src/plugins/gtecnet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/gtecnet.c b/src/plugins/gtecnet.c index 7dabcf7..42a4b44 100644 --- a/src/plugins/gtecnet.c +++ b/src/plugins/gtecnet.c @@ -112,7 +112,7 @@ static const char gtecnetlabelHIamp[81][10] = { "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", - "TP9","TP10","A1","A2", + "F9","F10","REF1","REF2", "EXG1","EXG2","EXG3","EXG4","EXG5","EXG6", "EXG7","EXG8","EXG9","EXG10","EXG11","EXG12","EXG13","EXG14","EXG15","EXG16", "TRIG" }; From a2e88d8ccfd342ce9cbd67a8e23af97dcc8dfdb8 Mon Sep 17 00:00:00 2001 From: Serafeim Perdikis Date: Tue, 17 May 2016 17:12:35 +0200 Subject: [PATCH 19/22] Fixed gtecnet plugin after update from gTec fixing the gNEEDaccess server acquisition bugs --- doc/egd_open.3 | 2 +- src/plugins/barv.c | 2 ++ src/plugins/gtecnet.c | 20 ++++++++++++-------- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/doc/egd_open.3 b/doc/egd_open.3 index c5773c7..3ab41c3 100644 --- a/doc/egd_open.3 +++ b/doc/egd_open.3 @@ -73,7 +73,7 @@ installation folder of the libraries. .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. +\fB/usr/etc/eegdev\fP. .SH "SEE ALSO" .BR egd_close (3), .BR eegdev-open-options (5) diff --git a/src/plugins/barv.c b/src/plugins/barv.c index 561e370..9c813be 100644 --- a/src/plugins/barv.c +++ b/src/plugins/barv.c @@ -578,6 +578,8 @@ int barv_close_device(struct devmodule* dev) 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]; diff --git a/src/plugins/gtecnet.c b/src/plugins/gtecnet.c index 42a4b44..60d19e6 100644 --- a/src/plugins/gtecnet.c +++ b/src/plugins/gtecnet.c @@ -186,8 +186,8 @@ void* gtecnet_read_fn(void *data) tdev->databuffer = (float*)malloc(tdev->ScansPerFrame * tdev->DataPointsPerScan * sizeof(float)); - //Disable DataReadyEventThreshold - //int DataReadyEventThreshouldOutput = gtecnal_DataReadyEventThreshold(tdev->Transceiver, tdev->SessionID, tdev->ScansPerFrame); + //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); @@ -195,14 +195,19 @@ void* gtecnet_read_fn(void *data) // Start streaming int StartStreaming = gtecnal_StartStreaming(tdev->Transceiver, tdev->SessionID); + bool DataIsReady; struct timeval start, end; while(true){ if(tdev->runacq){ - gettimeofday(&start,NULL); + //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); + //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 @@ -397,8 +402,7 @@ int init_data_com(struct devmodule* dev, const char* optv[]) 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 - tdev->ScansPerFrame = (int)(tdev->SamplingRate/4);// 4 Hz for multiples of 2 + tdev->ScansPerFrame = (int)(tdev->SamplingRate/16);// 16 Hz for multiples of 2 } int retSetCap = gtecnet_set_capability(tdev); From dee79c4ba5d09a0244414d927ef46fcc562c7dc4 Mon Sep 17 00:00:00 2001 From: Serafeim Perdikis Date: Mon, 23 May 2016 23:45:05 +0200 Subject: [PATCH 20/22] Added in datafile plugin extra electrode names for correct labels in 128 channel setups --- doc/egd_open.3 | 2 +- src/plugins/fileout.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/egd_open.3 b/doc/egd_open.3 index 3ab41c3..c5773c7 100644 --- a/doc/egd_open.3 +++ b/doc/egd_open.3 @@ -73,7 +73,7 @@ installation folder of the libraries. .PD This variable controls which folder should be search to find the configuration files. If unset, they will be searched in -\fB/usr/etc/eegdev\fP. +\fB/usr/local/etc/eegdev\fP. .SH "SEE ALSO" .BR egd_close (3), .BR eegdev-open-options (5) 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:]]*)" ")"; From d32926458f2bf66ff94d3e80250682df454c4f27 Mon Sep 17 00:00:00 2001 From: Serafeim Perdikis Date: Fri, 3 Jun 2016 11:45:52 +0200 Subject: [PATCH 21/22] Fixed scales to 1.0 instead of dResolutions. This seems to have been a bug, because I thought that they are the same, but, apparently dResolutions is something else. --- src/plugins/barv.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/barv.c b/src/plugins/barv.c index 9c813be..50b305b 100644 --- a/src/plugins/barv.c +++ b/src/plugins/barv.c @@ -282,7 +282,9 @@ void* barv_read_fn(void *data) // Set channel signal info siginf[i] = barv_siginfo[0]; - siginf[i].scale = pMsgStart->dResolutions[i];//Scale individually for each channel + // ATTENTION: dResolutions does not seem to be the scale!!! + //siginf[i].scale = pMsgStart->dResolutions[i];//Scale individually for each channel + siginf[i].scale = 1.0;//Scale individually for each channel tdev->chmap[i].si = &siginf[i]; // Set pointer to next entry @@ -300,7 +302,7 @@ void* barv_read_fn(void *data) tdev->chmap[nChannels].label = trigLbl; tdev->chmap[nChannels].stype = EGD_TRIGGER; siginf[nChannels] = barv_siginfo[1]; - siginf[nChannels].scale = 1; + siginf[nChannels].scale = 1.0; tdev->chmap[nChannels].si = &siginf[nChannels]; // Set device capabilities From b925a726c6b283655d3ff8b3383841c5c50146b4 Mon Sep 17 00:00:00 2001 From: Simis Date: Fri, 12 Aug 2016 17:22:48 +0200 Subject: [PATCH 22/22] Reinstated the dResolutions for barv plugin (it seems to be the channel scale after all, the received value has to be multiplied with it). In the same plugin, I fixed the trigger channel label from Trigger to trigger. That is, because saved files would associate the trigger channel as amopther EEG channel, because type allocation is done with regex expressions and capital T is not among the ones accepted by the datafile plugin (fileout.c) --> Maybe we should make Trigger be acceptable? --- src/plugins/barv.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/plugins/barv.c b/src/plugins/barv.c index 50b305b..5fc34a6 100644 --- a/src/plugins/barv.c +++ b/src/plugins/barv.c @@ -282,9 +282,7 @@ void* barv_read_fn(void *data) // Set channel signal info siginf[i] = barv_siginfo[0]; - // ATTENTION: dResolutions does not seem to be the scale!!! - //siginf[i].scale = pMsgStart->dResolutions[i];//Scale individually for each channel - siginf[i].scale = 1.0;//Scale individually for each channel + siginf[i].scale = pMsgStart->dResolutions[i];//Scale individually for each channel tdev->chmap[i].si = &siginf[i]; // Set pointer to next entry @@ -296,7 +294,7 @@ void* barv_read_fn(void *data) tdev->nch = tdev->nch + 1; // Add info for trigger channel - char* trigStr = "Trigger"; + char* trigStr = "trigger"; char* trigLbl = (char*)malloc((strlen(trigStr)+1)*sizeof(char)); memcpy(trigLbl,trigStr,strlen(trigStr)+1); tdev->chmap[nChannels].label = trigLbl;