This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
@@ -0,0 +1,768 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkAdjustment" id="adjustment1">
<property name="upper">100</property>
<property name="value">18</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment3">
<property name="lower">1</property>
<property name="upper">1024</property>
<property name="value">4</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkWindow" id="dummy_window">
<property name="can_focus">False</property>
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">8</property>
<property name="n_columns">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkLabel" id="label_sampling_frequency">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Sampling frequency :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_sampling_frequency">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">model_sampling_frequency</property>
<child>
<object class="GtkCellRendererText" id="renderer_sampling_frequency"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<object class="GtkLabel" id="label_type">
<property name="can_focus">False</property>
<property name="label" translatable="yes">Device type :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label_number_of_channels">
<property name="can_focus">False</property>
<property name="label" translatable="yes">Number of channels :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_type">
<property name="can_focus">False</property>
<property name="model">model_type</property>
<child>
<object class="GtkCellRendererText" id="renderer_type"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_number_of_channels">
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment3</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkListStore" id="model_baud_rate">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">default</col>
</row>
<row>
<col id="0" translatable="yes">9 600</col>
</row>
<row>
<col id="0" translatable="yes">115 200</col>
</row>
<row>
<col id="0" translatable="yes">460 800</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model_bit_depth">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">default</col>
</row>
<row>
<col id="0" translatable="yes">8 bits</col>
</row>
<row>
<col id="0" translatable="yes">16 bits</col>
</row>
<row>
<col id="0" translatable="yes">24 bits</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model_gender">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">unspecified</col>
</row>
<row>
<col id="0" translatable="yes">female</col>
</row>
<row>
<col id="0" translatable="yes">male</col>
</row>
<row>
<col id="0" translatable="yes">unknown</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model_notch_filters">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">default</col>
</row>
<row>
<col id="0" translatable="yes">none</col>
</row>
<row>
<col id="0" translatable="yes">50Hz</col>
</row>
<row>
<col id="0" translatable="yes">60Hz</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model_preset">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Custom</col>
</row>
<row>
<col id="0" translatable="yes">Discovery 24</col>
</row>
<row>
<col id="0" translatable="yes">Atlantis 4x4</col>
</row>
<row>
<col id="0" translatable="yes">Atlantis 2x2</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model_sampling_frequency">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">256</col>
</row>
<row>
<col id="0" translatable="yes">512</col>
</row>
<row>
<col id="0" translatable="yes">1024</col>
</row>
<row>
<col id="0" translatable="yes">2048</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model_type">
<columns>
<!-- column-name text -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Discovery</col>
</row>
<row>
<col id="0" translatable="yes">Atlantis</col>
</row>
</data>
</object>
<object class="GtkDialog" id="openvibe-acquisition-server-settings">
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Device configuration</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="spacing">8</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_apply">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_title">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Brainmaster Discovery / Atlantis
&lt;small&gt;&lt;span color="darkred"&gt;Attention : You should obtain a device passkey from Brainmaster
in order to operate your device with OpenViBE&lt;/span&gt;&lt;/small&gt;</property>
<property name="use_markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">8</property>
<property name="spacing">8</property>
<child>
<object class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">12</property>
<property name="n_columns">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkLabel" id="label_gender">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Gender :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_age">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Age :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_identifier">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Identifier :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_gender">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">model_gender</property>
<child>
<object class="GtkCellRendererText" id="renderer_gender"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_age">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_identifier">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment2</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator3">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_device_preset">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Configuration preset :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
</child>
<child>
<object class="GtkComboBox" id="combobox_device_preset">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">model_preset</property>
<child>
<object class="GtkCellRendererText" id="renderer_device_preset"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_device">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Device port :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_device">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkCellRendererText" id="renderer_device"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_baud_rate">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Baud rate :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_bit_depth">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Bit depth :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_notch_filters">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Notch filter :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_baud_rate">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">model_baud_rate</property>
<child>
<object class="GtkCellRendererText" id="renderer_baud_rate"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_bit_depth">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">model_bit_depth</property>
<child>
<object class="GtkCellRendererText" id="renderer_bit_depth"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_notch_filters">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">model_notch_filters</property>
<child>
<object class="GtkCellRendererText" id="renderer_notch_filters"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator2">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="right_attach">2</property>
<property name="top_attach">9</property>
<property name="bottom_attach">10</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_device_serial">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Device Serial Nr :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">10</property>
<property name="bottom_attach">11</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_device_passkey">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Device Passkey :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">11</property>
<property name="bottom_attach">12</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry_device_serial">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">10</property>
<property name="bottom_attach">11</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry_device_passkey">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">11</property>
<property name="bottom_attach">12</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_change_channel_names">
<property name="label" translatable="yes">Change channel names</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-10">button_apply</action-widget>
<action-widget response="-6">button_cancel</action-widget>
</action-widgets>
</object>
</interface>
@@ -0,0 +1,317 @@
/*
* ovasCConfigurationBrainmasterDiscovery.cpp
*
* Copyright (c) 2012, Yann Renard. All rights reserved.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#if defined TARGET_HAS_ThirdPartyBrainmasterCodeMakerAPI
#include "ovasCConfigurationBrainmasterDiscovery.h"
#include "ovas_defines_brainmaster_discovery.h"
namespace OpenViBE {
namespace AcquisitionServer {
namespace {
static bool guardFlag = false;
void on_spin_button_changed(GtkSpinButton* pAnySpinButton, GtkComboBox* pDevicePresetComboBox)
{
if (!guardFlag) { gtk_combo_box_set_active(pDevicePresetComboBox, 0); } // Custom
}
void on_combo_box_changed(GtkComboBox* pAnyComboBox, GtkComboBox* pDevicePresetComboBox)
{
if (!guardFlag) { gtk_combo_box_set_active(pDevicePresetComboBox, 0); } // Custom
}
void on_device_preset_changed(GtkComboBox* pComboBox, CConfigurationBrainmasterDiscovery* pThis)
{
guardFlag = true;
// int i;
const int active = gtk_combo_box_get_active(pComboBox);
switch (active)
{
default:
case Preset_Custom: break;
case Preset_Discovery_24: gtk_spin_button_set_value(pThis->m_buttonChannelCount, 24); // 22 EEG channels + 2 AUX
gtk_combo_box_set_active(pThis->m_comboBoxType, 0); // Discovery
gtk_combo_box_set_active(pThis->m_comboBoxSamplingRate, 0); // default
//gtk_combo_box_set_active(pThis->m_comboBoxDevice, );
//gtk_combo_box_set_active(pThis->m_comboBoxPreset, );
gtk_combo_box_set_active(pThis->m_comboBoxBaudRate, 0); // default
gtk_combo_box_set_active(pThis->m_comboBoxBitDepth, 0); // default
gtk_combo_box_set_active(pThis->m_comboBoxNotchFilters, 0); // default
gtk_widget_set_sensitive(GTK_WIDGET(pThis->m_comboBoxBitDepth), FALSE); // default
pThis->m_rvChannelType.clear();
//for (i=0; i<22; ++i) pThis->m_rvChannelType[i]=ChannelType_EEG;
//for (i=22; i<24; ++i) pThis->m_rvChannelType[i]=ChannelType_AUX;
pThis->m_channelNames[0] = "Fp1";
pThis->m_channelNames[1] = "F3";
pThis->m_channelNames[2] = "C3";
pThis->m_channelNames[3] = "P3";
pThis->m_channelNames[4] = "O1";
pThis->m_channelNames[5] = "F7";
pThis->m_channelNames[6] = "T3";
pThis->m_channelNames[7] = "T5";
pThis->m_channelNames[8] = "Fz";
pThis->m_channelNames[9] = "Fp2";
pThis->m_channelNames[10] = "F4";
pThis->m_channelNames[11] = "C4";
pThis->m_channelNames[12] = "P4";
pThis->m_channelNames[13] = "O2";
pThis->m_channelNames[14] = "F8";
pThis->m_channelNames[15] = "T4";
pThis->m_channelNames[16] = "T6";
pThis->m_channelNames[17] = "Cz";
pThis->m_channelNames[18] = "Pz";
pThis->m_channelNames[19] = "A2";
pThis->m_channelNames[20] = "Fpz";
pThis->m_channelNames[21] = "Oz";
pThis->m_channelNames[22] = "AUX1";
pThis->m_channelNames[23] = "AUX2";
break;
case Preset_Atlantis_2x2: gtk_spin_button_set_value(pThis->m_buttonChannelCount, 4); // 2 EEG channels + 2 AUX
gtk_combo_box_set_active(pThis->m_comboBoxType, 1); // Atlantis
gtk_combo_box_set_active(pThis->m_comboBoxSamplingRate, 0); // default
//gtk_combo_box_set_active(pThis->m_comboBoxDevice, );
//gtk_combo_box_set_active(pThis->m_comboBoxPreset, );
gtk_combo_box_set_active(pThis->m_comboBoxBaudRate, 0); // default
gtk_combo_box_set_active(pThis->m_comboBoxBitDepth, 0); // default
gtk_combo_box_set_active(pThis->m_comboBoxNotchFilters, 0); // default
gtk_widget_set_sensitive(GTK_WIDGET(pThis->m_comboBoxBitDepth), TRUE); // default
pThis->m_rvChannelType.clear();
pThis->m_rvChannelType[0] = ChannelType_AUX;
pThis->m_rvChannelType[1] = ChannelType_AUX;
pThis->m_rvChannelType[2] = ChannelType_EEG;
pThis->m_rvChannelType[3] = ChannelType_EEG;
pThis->m_channelNames[0] = "AUX1";
pThis->m_channelNames[1] = "AUX2";
pThis->m_channelNames[2] = "A1-R1";
pThis->m_channelNames[3] = "A2-R2";
break;
case Preset_Atlantis_4x4: gtk_spin_button_set_value(pThis->m_buttonChannelCount, 8); // 2 EEG channels + 2 AUX + 2 EEG channels + 2 AUX
gtk_combo_box_set_active(pThis->m_comboBoxType, 1); // Atlantis
gtk_combo_box_set_active(pThis->m_comboBoxSamplingRate, 0); // default
//gtk_combo_box_set_active(pThis->m_comboBoxDevice, );
//gtk_combo_box_set_active(pThis->m_comboBoxPreset, );
gtk_combo_box_set_active(pThis->m_comboBoxBaudRate, 0); // default
gtk_combo_box_set_active(pThis->m_comboBoxBitDepth, 0); // default
gtk_combo_box_set_active(pThis->m_comboBoxNotchFilters, 0); // default
gtk_widget_set_sensitive(GTK_WIDGET(pThis->m_comboBoxBitDepth), TRUE); // default
pThis->m_rvChannelType.clear();
pThis->m_rvChannelType[0] = ChannelType_AUX;
pThis->m_rvChannelType[1] = ChannelType_AUX;
pThis->m_rvChannelType[2] = ChannelType_EEG;
pThis->m_rvChannelType[3] = ChannelType_EEG;
pThis->m_rvChannelType[4] = ChannelType_AUX;
pThis->m_rvChannelType[5] = ChannelType_AUX;
pThis->m_rvChannelType[6] = ChannelType_EEG;
pThis->m_rvChannelType[7] = ChannelType_EEG;
pThis->m_channelNames[0] = "AUX1";
pThis->m_channelNames[1] = "AUX2";
pThis->m_channelNames[2] = "A1-R1";
pThis->m_channelNames[3] = "A2-R2";
pThis->m_channelNames[4] = "AUX3";
pThis->m_channelNames[5] = "AUX4";
pThis->m_channelNames[6] = "A3-R3";
pThis->m_channelNames[7] = "A4-R4";
break;
}
guardFlag = false;
}
} // namespace
CConfigurationBrainmasterDiscovery::CConfigurationBrainmasterDiscovery(const char* gtkBuilderFilename, uint32_t& rPort, uint32_t& rPreset, uint32_t& rType,
uint32_t& rBaudRate, uint32_t& rSamplingRate, uint32_t& rBitDepth,
uint32_t& rNotchFilters, std::map<uint32_t, uint32_t>& rvChannelType,
std::string& sDeviceSerial, std::string& sDevicePasskey)
: CConfigurationBuilder(gtkBuilderFilename), m_rPort(rPort), m_rPreset(rPreset), m_rType(rType), m_rBaudRate(rBaudRate), m_rSamplingRate(rSamplingRate),
m_rBitDepth(rBitDepth), m_rNotchFilters(rNotchFilters), m_rvChannelType(rvChannelType), m_deviceSerial(sDeviceSerial), m_devicePasskey(sDevicePasskey)
{
m_listStore = gtk_list_store_new(1, G_TYPE_STRING);
}
CConfigurationBrainmasterDiscovery::~CConfigurationBrainmasterDiscovery() { g_object_unref(m_listStore); }
bool CConfigurationBrainmasterDiscovery::preConfigure()
{
if (!CConfigurationBuilder::preConfigure()) { return false; }
const uint32_t port = m_rPort;
m_buttonChannelCount = GTK_SPIN_BUTTON(this->m_nChannels);
m_comboBoxDevice = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_device"));
m_comboBoxPreset = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_device_preset"));
m_comboBoxType = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_type"));
m_comboBoxBaudRate = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_baud_rate"));
m_comboBoxSamplingRate = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_sampling_frequency"));
m_comboBoxBitDepth = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_bit_depth"));
m_comboBoxNotchFilters = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_notch_filters"));
m_pEntryDeviceSerial = GTK_ENTRY(gtk_builder_get_object(m_builder, "entry_device_serial"));
m_pEntryDevicePasskey = GTK_ENTRY(gtk_builder_get_object(m_builder, "entry_device_passkey"));
g_signal_connect(G_OBJECT(m_buttonChannelCount), "value-changed", G_CALLBACK(on_spin_button_changed), m_comboBoxPreset);
g_signal_connect(G_OBJECT(m_comboBoxPreset), "changed", G_CALLBACK(on_device_preset_changed), this);
g_signal_connect(G_OBJECT(m_comboBoxType), "changed", G_CALLBACK(on_combo_box_changed), m_comboBoxPreset);
g_signal_connect(G_OBJECT(m_comboBoxBaudRate), "changed", G_CALLBACK(on_combo_box_changed), m_comboBoxPreset);
g_signal_connect(G_OBJECT(m_comboBoxSamplingRate), "changed", G_CALLBACK(on_combo_box_changed), m_comboBoxPreset);
g_signal_connect(G_OBJECT(m_comboBoxBitDepth), "changed", G_CALLBACK(on_combo_box_changed), m_comboBoxPreset);
g_signal_connect(G_OBJECT(m_comboBoxNotchFilters), "changed", G_CALLBACK(on_combo_box_changed), m_comboBoxPreset);
g_object_unref(m_listStore);
m_listStore = gtk_list_store_new(1, G_TYPE_STRING);
gtk_combo_box_set_model(m_comboBoxDevice, GTK_TREE_MODEL(m_listStore));
char buffer[1024];
bool selected = false;
gtk_combo_box_append_text(m_comboBoxDevice, "Auto");
for (uint32_t i = 1; i < 17; ++i)
{
sprintf(buffer, "COM%i", i);
gtk_combo_box_append_text(m_comboBoxDevice, buffer);
if (port == i)
{
gtk_combo_box_set_active(m_comboBoxDevice, i);
selected = true;
}
}
if (!selected) { gtk_combo_box_set_active(m_comboBoxDevice, 0); }
switch (m_rType)
{
default:
case Type_Discovery: gtk_combo_box_set_active(m_comboBoxType, 0);
break;
case Type_Atlantis: gtk_combo_box_set_active(m_comboBoxType, 1);
break;
}
switch (m_rBaudRate)
{
default:
case BaudRate_Default: gtk_combo_box_set_active(m_comboBoxBaudRate, 0);
break;
case BaudRate_9600: gtk_combo_box_set_active(m_comboBoxBaudRate, 1);
break;
case BaudRate_115200: gtk_combo_box_set_active(m_comboBoxBaudRate, 2);
break;
case BaudRate_460800: gtk_combo_box_set_active(m_comboBoxBaudRate, 3);
break;
}
switch (m_rSamplingRate)
{
default:
case SamplingFrequency_256: gtk_combo_box_set_active(m_comboBoxSamplingRate, 0);
break;
case SamplingFrequency_512: gtk_combo_box_set_active(m_comboBoxSamplingRate, 1);
break;
case SamplingFrequency_1024: gtk_combo_box_set_active(m_comboBoxSamplingRate, 2);
break;
case SamplingFrequency_2048: gtk_combo_box_set_active(m_comboBoxSamplingRate, 3);
break;
}
switch (m_rBitDepth)
{
default:
case BitDepth_Default: gtk_combo_box_set_active(m_comboBoxBitDepth, 0);
break;
case BitDepth_8: gtk_combo_box_set_active(m_comboBoxBitDepth, 1);
break;
case BitDepth_16: gtk_combo_box_set_active(m_comboBoxBitDepth, 2);
break;
case BitDepth_24: gtk_combo_box_set_active(m_comboBoxBitDepth, 3);
break;
}
switch (m_rNotchFilters)
{
default:
case NotchFilter_Default: gtk_combo_box_set_active(m_comboBoxNotchFilters, 0);
break;
case NotchFilter_Off: gtk_combo_box_set_active(m_comboBoxNotchFilters, 1);
break;
case NotchFilter_50: gtk_combo_box_set_active(m_comboBoxNotchFilters, 2);
break;
case NotchFilter_60: gtk_combo_box_set_active(m_comboBoxNotchFilters, 3);
break;
}
switch (m_rPreset)
{
default:
case Preset_Custom: gtk_combo_box_set_active(m_comboBoxPreset, 0);
break;
case Preset_Discovery_24: gtk_combo_box_set_active(m_comboBoxPreset, 1);
break;
case Preset_Atlantis_4x4: gtk_combo_box_set_active(m_comboBoxPreset, 2);
break;
case Preset_Atlantis_2x2: gtk_combo_box_set_active(m_comboBoxPreset, 3);
break;
}
gtk_entry_set_text(m_pEntryDeviceSerial, m_deviceSerial.c_str());
gtk_entry_set_text(m_pEntryDevicePasskey, m_devicePasskey.c_str());
return true;
}
bool CConfigurationBrainmasterDiscovery::postConfigure()
{
if (m_applyConfig)
{
const int port = gtk_combo_box_get_active(m_comboBoxDevice);
if (port >= 0) { m_rPort = uint32_t(port); }
const int preset = gtk_combo_box_get_active(m_comboBoxPreset);
if (preset >= 0) { m_rPreset = uint32_t(preset); }
const int type = gtk_combo_box_get_active(m_comboBoxType);
if (type >= 0) { m_rType = uint32_t(type); }
const int baudRate = gtk_combo_box_get_active(m_comboBoxBaudRate);
if (baudRate >= 0) { m_rBaudRate = uint32_t(baudRate); }
const int sampling = gtk_combo_box_get_active(m_comboBoxSamplingRate);
if (sampling >= 0) { m_rSamplingRate = uint32_t(sampling); }
const int bitDepth = gtk_combo_box_get_active(m_comboBoxBitDepth);
if (bitDepth >= 0) { m_rBitDepth = uint32_t(bitDepth); }
const int notchFilters = gtk_combo_box_get_active(m_comboBoxNotchFilters);
if (notchFilters >= 0) { m_rNotchFilters = uint32_t(notchFilters); }
m_deviceSerial = gtk_entry_get_text(m_pEntryDeviceSerial);
m_devicePasskey = gtk_entry_get_text(m_pEntryDevicePasskey);
}
if (!CConfigurationBuilder::postConfigure()) { return false; }
return true;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyBrainmasterCodeMakerAPI
@@ -0,0 +1,78 @@
/*
* ovasCConfigurationBrainmasterDiscovery.h
*
* Copyright (c) 2012, Yann Renard. All rights reserved.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#pragma once
#if defined TARGET_HAS_ThirdPartyBrainmasterCodeMakerAPI
#include "../ovasCConfigurationBuilder.h"
#include <gtk/gtk.h>
#include <vector>
#include <string>
#include <map>
namespace OpenViBE {
namespace AcquisitionServer {
class CConfigurationBrainmasterDiscovery final : public CConfigurationBuilder
{
public:
CConfigurationBrainmasterDiscovery(const char* gtkBuilderFilename, uint32_t& rPort, uint32_t& rPreset, uint32_t& rType,
uint32_t& rBaudRate, uint32_t& rSamplingFrequency, uint32_t& rBitDepth, uint32_t& rNotchFilters,
std::map<uint32_t, uint32_t>& rvChannelType, std::string& sDeviceSerial, std::string& sDevicePasskey);
~CConfigurationBrainmasterDiscovery() override;
bool preConfigure() override;
bool postConfigure() override;
GtkSpinButton* m_buttonChannelCount = nullptr;
GtkComboBox* m_comboBoxDevice = nullptr;
GtkComboBox* m_comboBoxPreset = nullptr;
GtkComboBox* m_comboBoxType = nullptr;
GtkComboBox* m_comboBoxBaudRate = nullptr;
GtkComboBox* m_comboBoxSamplingRate = nullptr;
GtkComboBox* m_comboBoxBitDepth = nullptr;
GtkComboBox* m_comboBoxNotchFilters = nullptr;
GtkEntry* m_pEntryDeviceSerial = nullptr;
GtkEntry* m_pEntryDevicePasskey = nullptr;
using CConfigurationBuilder::m_channelNames;
GtkListStore* m_listStore = nullptr;
uint32_t& m_rPort;
uint32_t& m_rPreset;
uint32_t& m_rType;
uint32_t& m_rBaudRate;
uint32_t& m_rSamplingRate;
uint32_t& m_rBitDepth;
uint32_t& m_rNotchFilters;
std::map<uint32_t, uint32_t>& m_rvChannelType;
std::string& m_deviceSerial;
std::string& m_devicePasskey;
std::vector<GtkWidget*> m_sensitiveWidgets;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyBrainmasterCodeMakerAPI
@@ -0,0 +1,601 @@
/*
* ovasCDriverBrainmasterDiscovery.cpp
*
* Copyright (c) 2012, Yann Renard. All rights reserved.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#if defined TARGET_HAS_ThirdPartyBrainmasterCodeMakerAPI
#include "ovasCDriverBrainmasterDiscovery.h"
#include "ovasCConfigurationBrainmasterDiscovery.h"
#include "../ovasCConfigurationBuilder.h"
#include <toolkit/ovtk_all.h>
#include "ovas_defines_brainmaster_discovery.h"
namespace OpenViBE {
namespace AcquisitionServer {
extern "C" {
int OvAtlLoginDevice(const char* sSerialNumber, const char* sPassKey);
}
static float decode_24(unsigned char*& rpBuffer)
{
int res = rpBuffer[0] + rpBuffer[1] * 0x100 + rpBuffer[2] * 0x10000;
if (res > 0x800000) { res = res - 0x1000000; }
rpBuffer += 3;
return res * 0.01163f;
}
static float decode_16(unsigned char*& rpBuffer)
{
int res = rpBuffer[0] + rpBuffer[1] * 0x100;
if (res > 0x8000) { res = res - 0x10000; }
rpBuffer += 2;
return res * 0.1983f;
}
static float decode_8(unsigned char*& rpBuffer)
{
int res = rpBuffer[0];
res -= 0x80;
rpBuffer += 1;
return res * 100 / 128.f;
}
static uint32_t next_sync(uint32_t sync)
{
sync++;
sync &= 7;
return sync ? sync : 1;
}
//___________________________________________________________________//
// //
CDriverBrainmasterDiscovery::CDriverBrainmasterDiscovery(IDriverContext& ctx)
: IDriver(ctx)
, m_preset(Preset_Discovery_24)
{
m_deviceSerial = ctx.getConfigurationManager().expand("${AcquisitionServer_Driver_BrainmasterDeviceSerial}").toASCIIString();
m_devicePasskey = ctx.getConfigurationManager().expand("${AcquisitionServer_Driver_BrainmasterDevicePasskey}").toASCIIString();
m_frameDumpFilename = ctx.getConfigurationManager().expand("${AcquisitionServer_Driver_BrainmasterFrameDumpFilename}").toASCIIString();
m_baudRates[BaudRate_9600] = 0x30; // 9600
m_baudRates[BaudRate_115200] = 0x20; // 115200
m_baudRates[BaudRate_460800] = 0x10; // 460800
m_baudRateValues[BaudRate_9600] = 9600; // 9600
m_baudRateValues[BaudRate_115200] = 115200; // 115200
m_baudRateValues[BaudRate_460800] = 460800; // 460800
m_notchsFilters[NotchFilter_Default] = 0; // Off
m_notchsFilters[NotchFilter_Off] = 0; // Off
m_notchsFilters[NotchFilter_50] = 2; // 50 Hz
m_notchsFilters[NotchFilter_60] = 3; // 60 Hz
m_notchFiltersValues[NotchFilter_Default] = 0; // Off
m_notchFiltersValues[NotchFilter_Off] = 0; // Off
m_notchFiltersValues[NotchFilter_50] = 50; // 50 Hz
m_notchFiltersValues[NotchFilter_60] = 60; // 60 Hz
m_bitDepths[BitDepth_8] = 1; // 8 bits
m_bitDepths[BitDepth_16] = 2; // 16 bits
m_bitDepths[BitDepth_24] = 3; // 24 bits
m_bitDepthValues[BitDepth_8] = 8; // 8 bits
m_bitDepthValues[BitDepth_16] = 16; // 16 bits
m_bitDepthValues[BitDepth_24] = 24; // 24 bits
// 0xcc for 2 EEG, 0xff for 2 EEG + 2 AUX
m_channelSelectionMasks[24] = 0xffffff; // Type_Discovery
m_channelSelectionMasks[4] = 0xf; // Type_Atlantis 2x2
m_channelSelectionMasks[8] = 0xff; // Type_Atlantis 4x4
m_bitDepthDecoders[ChannelType_EEG][BitDepth_8] = decode_8; // 8 bits
m_bitDepthDecoders[ChannelType_EEG][BitDepth_16] = decode_16; // 16 bits
m_bitDepthDecoders[ChannelType_EEG][BitDepth_24] = decode_24; // 24 bits
m_bitDepthDecoders[ChannelType_AUX][BitDepth_8] = decode_8; // 8 bits
m_bitDepthDecoders[ChannelType_AUX][BitDepth_16] = decode_16; // 16 bits
m_bitDepthDecoders[ChannelType_AUX][BitDepth_24] = decode_16; // 24 bits
m_startModules[Type_Discovery] = ::DiscStartModule;
m_startModules[Type_Atlantis] = ::AtlStartModule;
m_stopModules[Type_Discovery] = ::DiscStopModule;
m_stopModules[Type_Atlantis] = ::AtlStopModule;
m_header.setSamplingFrequency(256);
m_header.setChannelCount(24);
m_header.setChannelName(0, "Fp1"/*"FP1"*/);
m_header.setChannelName(1, "F3");
m_header.setChannelName(2, "C3");
m_header.setChannelName(3, "P3");
m_header.setChannelName(4, "O1");
m_header.setChannelName(5, "F7");
m_header.setChannelName(6, "T3");
m_header.setChannelName(7, "T5");
m_header.setChannelName(8, "Fz");
m_header.setChannelName(9, "Fp2"/*"FP2"*/);
m_header.setChannelName(10, "F4");
m_header.setChannelName(11, "C4");
m_header.setChannelName(12, "P4");
m_header.setChannelName(13, "O2");
m_header.setChannelName(14, "F8");
m_header.setChannelName(15, "T4");
m_header.setChannelName(16, "T6");
m_header.setChannelName(17, "Cz");
m_header.setChannelName(18, "Pz");
m_header.setChannelName(19, "A2");
m_header.setChannelName(20, "Fpz"/*"FPz"*/);
m_header.setChannelName(21, "Oz");
m_header.setChannelName(22, "AUX1");
m_header.setChannelName(23, "AUX2");
for (size_t i = 0; i < m_header.getChannelCount(); ++i) { m_header.setChannelUnits(i, OVTK_UNIT_Volts, OVTK_FACTOR_Micro); }
}
//___________________________________________________________________//
// //
bool CDriverBrainmasterDiscovery::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
{
uint32_t i;
if (m_driverCtx.isConnected()) { return false; }
if (m_deviceSerial == "" || m_devicePasskey == "")
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "The device serial or passkey were not configured\n";
m_driverCtx.getLogManager() << Kernel::LogLevel_Error <<
"You can set their value from the configuration pannel or from the following configuration tokens :\n";
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << " - " << CString("AcquisitionServer_Driver_BrainmasterDeviceSerial") << "\n";
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << " - " << CString("AcquisitionServer_Driver_BrainmasterDevicePasskey") << "\n";
return false;
}
switch (m_type)
{
default:
case Type_Discovery: m_baudRateReal = (m_baudRate != BaudRate_Default ? m_baudRate : BaudRate_460800);
m_samplingRateReal = m_samplingRate;
m_bitDepthReal = BitDepth_24;
m_notchFiltersReal = (m_notchFilters != NotchFilter_Default ? m_notchFilters : NotchFilter_Default);
m_frameSize = 3;
m_dataOffset = 3;
break;
case Type_Atlantis: m_baudRateReal = (m_baudRate != BaudRate_Default ? m_baudRate : BaudRate_115200);
m_samplingRateReal = m_samplingRate;
m_bitDepthReal = (m_bitDepth != BitDepth_Default ? m_bitDepth : BitDepth_24);
m_notchFiltersReal = (m_notchFilters != NotchFilter_Default ? m_notchFilters : NotchFilter_Default);
m_frameSize = 1;
m_dataOffset = 1;
break;
}
switch (m_bitDepthReal)
{
default:
case BitDepth_24: for (i = 0; i < m_header.getChannelCount(); ++i)
{
switch (m_channelTypes[i])
{
default:
case ChannelType_EEG: m_frameSize += 3;
break;
case ChannelType_AUX: m_frameSize += 2;
break;
}
}
break;
case BitDepth_16: m_frameSize += m_header.getChannelCount() * 2;
break;
case BitDepth_8: m_frameSize += m_header.getChannelCount();
break;
}
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Computed frame size of " << m_frameSize << " bytes\n";
m_samples.resize(m_header.getChannelCount());
m_buffers.resize(m_frameSize);
m_callback = &callback;
m_nSamplePerSentBlock = nSamplePerSentBlock;
// Sets/Autodetects port
m_portReal = m_port;
if (m_portReal == 0)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Auto detecting COM port...\n";
m_portReal = autoDetectPort();
if (m_portReal == uint32_t(-1))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Port is not set and could not be auto detected, please configure the driver\n";
return false;
}
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Found COM port " << m_portReal << " !\n";
}
// Opens device handle at default speed
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Opening COM port " << m_portReal << " at 9600 bauds\n";
if (!::AtlOpenPort(m_portReal, 9600, nullptr))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not open port " << m_portReal << "\n";
return false;
}
// Writes baud rate
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Setting baud rate to " << m_baudRateValues[m_baudRateReal] << " bauds (code " << m_baudRates[
m_baudRateReal] << ")\n";
if (!::AtlSetBaudRate(m_baudRates[m_baudRateReal]))
{
::AtlClosePort(m_portReal);
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not set baud rate\n";
return false;
}
// Closes port (which was at default speed)
::AtlClosePort(m_portReal);
// Opens device handle at targeted speed
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Re-opening COM port " << m_portReal << " at " << m_baudRateValues[m_baudRateReal] <<
" bauds (code " << m_baudRates[m_baudRateReal] << ")\n";
if (!::AtlOpenPort(m_portReal, m_baudRateValues[m_baudRateReal], nullptr))
// if(!::AtlOpenPort(m_portReal, 9600, nullptr))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not open port " << m_portReal << "\n";
return false;
}
// Logs in the device
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Logging in the device...\n";
const int code = OvAtlLoginDevice(m_deviceSerial.c_str(), m_devicePasskey.c_str());
if (!code)
{
::AtlSetBaudRate(m_baudRates[BaudRate_9600]);
::AtlClosePort(m_portReal);
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not log in device (error code was " << uint32_t(code) << ")\n";
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Please check the device serial and passkey configuration\n";
m_driverCtx.getLogManager() << Kernel::LogLevel_Error <<
"You can set their value from the configuration pannel or from the following configuration tokens :\n";
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << " - " << CString("AcquisitionServer_Driver_BrainmasterDeviceSerial") << " (was set to [" <<
m_deviceSerial.c_str() << "])\n";
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << " - " << CString("AcquisitionServer_Driver_BrainmasterDevicePasskey") << " (was set to [" <<
m_devicePasskey.c_str() << "])\n";
if (!checkDeviceSerial(m_deviceSerial))
m_driverCtx.getLogManager() << Kernel::LogLevel_Error <<
"By the way, serial seems to be malformed, it is supposed to be XXXXX with each X be a digit\n";
if (!checkDevicePasskey(m_devicePasskey))
m_driverCtx.getLogManager() << Kernel::LogLevel_Error <<
"By the way, passkey seems to be malformed, it is supposed to be XXXX-XXXX-XXXX with each X be a digit or a letter\n";
return false;
}
// Reads firmware version
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Successfully opened device on port " << m_portReal << " | Firware nr : " <<
uint32_t(AtlQueryFirmware(0)) << "\n";
// Writes notch filters
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Setting notch filters to " << m_notchFiltersValues[m_notchFiltersReal] << " (code " <<
m_notchsFilters
[m_notchFiltersReal] << ")...\n";
if (!::AtlSetNotchFilters(m_notchsFilters[m_notchFiltersReal]))
{
::AtlSetBaudRate(m_baudRates[BaudRate_9600]);
::AtlClosePort(m_portReal);
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not set notch filters\n";
return false;
}
// Writes bit depth
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Setting bit depth to " << m_bitDepthValues[m_bitDepthReal] << " bits (code " << m_bitDepths[
m_bitDepthReal] << ")...\n";
if (!::AtlSetBytesPerSample(m_bitDepths[m_bitDepthReal]))
{
::AtlSetBaudRate(m_baudRates[BaudRate_9600]);
::AtlClosePort(m_portReal);
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not set bytes per sample\n";
return false;
}
// Selects all the channels
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Selecting all channels with channel mask " << m_channelSelectionMasks[m_header.getChannelCount()]
<<
"...\n";
if (!::AtlSelectChannels(m_channelSelectionMasks[m_header.getChannelCount()]))
{
::AtlSetBaudRate(m_baudRates[BaudRate_9600]);
::AtlClosePort(m_portReal);
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not select all the channels\n";
return false;
}
// Clears special data
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Clearing specials...\n";
if (!::AtlClearSpecials())
{
::AtlSetBaudRate(m_baudRates[BaudRate_9600]);
::AtlClosePort(m_portReal);
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not clear special data selection\n";
return false;
}
// Poking special code for Atlantis 4x4
if (m_header.getChannelCount() == 8)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Poking special code for Atlantis 4x4...\n";
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << uint32_t(AtlPoke(0xc006, 0x00)) <<
"\n"; // Sets internal sampling rate set to 512 Hz - This hack solves the EEG chan 3 & 4 flat lines issue
// m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << uint32_t(AtlPoke(0xb607, 0xff)) << "\n"; // ATC_CHANOUTMASK
// m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << uint32_t(AtlPoke(0xb608, 0x20)) << "\n"; // ATC_ADCMODE
// m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << uint32_t(AtlPoke(0xb7e9, 0x03)) << "\n"; // ??? Bit mode ?
}
// serialn_p ::AtlQuerySerialNumber(int auth)
// Actually starts the acquisition
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Starting module !\n";
if (!m_startModules[m_type]())
{
::AtlSetBaudRate(m_baudRates[BaudRate_9600]);
::AtlClosePort(m_portReal);
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not start module\n";
return false;
}
m_syncByte = 1;
// Preparing optional frame dump
if (m_frameDumpFilename == "")
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Won't dump frames\n";
m_frameDumpFlag = false;
}
else
{
FILE* file = fopen(m_frameDumpFilename.c_str(), "wb");
if (file)
{
m_frameDumpFlag = true;
fclose(file);
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Will dump frames in [" << m_frameDumpFilename << "]\n";
}
else
{
m_frameDumpFlag = false;
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Should have dumped frames in [" << m_frameDumpFilename <<
"] but file can't be opened for writing !\n";
}
}
return true;
}
bool CDriverBrainmasterDiscovery::start()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
return true;
}
bool CDriverBrainmasterDiscovery::loop()
{
if (!m_driverCtx.isConnected()) { return false; }
if (!m_driverCtx.isStarted()) { return true; }
bool gotData = false;
for (uint32_t i = 0; i < m_nSamplePerSentBlock; ++i)
{
// Updates Sync bits
m_syncByte = next_sync(m_syncByte);
// Searches for Sync bits
this->read(&m_buffers[0], 1);
if (((m_buffers[0] >> 5) & 7) != m_syncByte)
{
// Needs resync
uint32_t sync = this->sync();
if (sync != uint32_t(-1)) { m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Resynced by " << sync << " byte(s) !\n"; }
else { m_driverCtx.getLogManager() << Kernel::LogLevel_ImportantWarning << "Could not resync, did not find the sync bytes !\n"; }
m_wasLastFrameCorrect = false;
}
else
{
// Sends samples if last frame was correct
if (m_wasLastFrameCorrect)
{
m_callback->setSamples(&m_samples[0], 1);
gotData = true;
}
// Assumed synced stream - Reads leading bytes
this->read(&m_buffers[1], m_buffers.size() - 1);
#if 1
// For debug purpose
if (m_frameDumpFlag)
{
FILE* file = fopen(m_frameDumpFilename.c_str(), "ab");
if (file)
{
fprintf(file, "Frame : ");
for (uint32_t i = 0; i < m_buffers.size(); ++i) { fprintf(file, "0x%02x ", m_buffers[i]); }
fprintf(file, "\n");
fclose(file);
}
else
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Unexpected: Could not open dump file [" << m_frameDumpFilename <<
"] for writing\n";
m_frameDumpFlag = false;
}
}
#endif
// Converts new buffer to samples
unsigned char* buffer = &m_buffers[m_dataOffset];
for (uint32_t j = 0; j < m_samples.size(); ++j) { m_samples[j] = m_bitDepthDecoders[m_channelTypes[j]][m_bitDepthReal](buffer); }
m_wasLastFrameCorrect = true;
}
}
if (gotData) { m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount()); }
return true;
}
bool CDriverBrainmasterDiscovery::read(unsigned char* frame, uint32_t size)
{
const uint32_t count = size;
uint32_t idx = 0;
while (idx != count)
{
int iCode = ::AtlReadData(frame + idx, count - idx);
if (iCode >= 0) { idx += iCode; }
}
return true;
}
uint32_t CDriverBrainmasterDiscovery::sync()
{
std::vector<unsigned char> buffer;
buffer.resize(m_buffers.size() * 4);
bool finished = false;
uint32_t nSync = 0;
uint32_t i = 0;
while (!finished && i < 16)
{
i++;
this->read(&buffer[0], buffer.size());
for (uint32_t i = 0; i < m_buffers.size() && !finished; ++i)
{
uint32_t syncCandidate[4];
syncCandidate[0] = buffer[i + 0 * m_buffers.size()] >> 5;
syncCandidate[1] = buffer[i + 1 * m_buffers.size()] >> 5;
syncCandidate[2] = buffer[i + 2 * m_buffers.size()] >> 5;
syncCandidate[3] = buffer[i + 3 * m_buffers.size()] >> 5;
if (next_sync(syncCandidate[0]) == syncCandidate[1]
&& next_sync(syncCandidate[1]) == syncCandidate[2]
&& next_sync(syncCandidate[2]) == syncCandidate[3])
{
this->read(&buffer[0], i);
m_syncByte = syncCandidate[3];
nSync = i;
finished = true;
}
}
}
#if 1
// For debug purpose
if (m_frameDumpFlag)
{
FILE* file = fopen(m_frameDumpFilename.c_str(), "ab");
if (file)
{
fprintf(file, "Syncing...\n");
for (uint32_t i = 0; i < buffer.size(); ++i)
{
fprintf(file, "0x%02x ", buffer[i]);
if (((i + 1) % m_buffers.size()) == 0) { fprintf(file, "\n"); }
}
fclose(file);
}
else
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Unexpected: Could not open dump file [" << m_frameDumpFilename <<
"] for writing\n";
m_frameDumpFlag = false;
}
}
#endif
return finished ? nSync : uint32_t(-1);
}
bool CDriverBrainmasterDiscovery::stop()
{
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
return true;
}
bool CDriverBrainmasterDiscovery::uninitialize()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// Stops acquisition
if (!m_stopModules[m_type]())
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could stop module\n";
return false;
}
// Writes baud rate back to 9600
if (!::AtlSetBaudRate(m_baudRates[BaudRate_9600]))
{
::AtlClosePort(m_portReal);
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not set baud rate\n";
return false;
}
// Closes device handle
if (!::AtlClosePort(m_portReal))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not close port " << m_portReal << "\n";
return false;
}
return true;
}
//___________________________________________________________________//
// //
bool CDriverBrainmasterDiscovery::configure()
{
CConfigurationBrainmasterDiscovery config(Directories::getDataDir() + "/applications/acquisition-server/interface-Brainmaster-Discovery.ui",
m_port, m_preset, m_type, m_baudRate, m_samplingRate, m_bitDepth,
m_notchFilters, m_channelTypes, m_deviceSerial, m_devicePasskey);
if (!config.configure(m_header)) { return false; }
return true;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyBrainmasterCodeMakerAPI
@@ -0,0 +1,107 @@
/*
* ovasCDriverBrainmasterDiscovery.h
*
* Copyright (c) 2012, Yann Renard. All rights reserved.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#pragma once
#if defined TARGET_HAS_ThirdPartyBrainmasterCodeMakerAPI
#include "ovasIDriver.h"
#include "../ovasCHeader.h"
#include <vector>
#include <map>
#include <string>
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CDriverBrainmasterDiscovery
* \author Yann Renard
*/
class CDriverBrainmasterDiscovery final : public IDriver
{
public:
explicit CDriverBrainmasterDiscovery(IDriverContext& ctx);
void release() { delete this; }
const char* getName() override { return "Brainmaster Discovery and Atlantis"; }
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
bool uninitialize() override;
bool start() override;
bool stop() override;
bool loop() override;
bool read(unsigned char* frame, uint32_t size);
uint32_t sync();
bool isConfigurable() override { return true; }
bool configure() override;
const IHeader* getHeader() override { return &m_header; }
protected:
IDriverCallback* m_callback = nullptr;
CHeader m_header;
uint32_t m_nSamplePerSentBlock = 0;
std::string m_deviceSerial;
std::string m_devicePasskey;
std::string m_frameDumpFilename;
bool m_frameDumpFlag = false;
uint32_t m_port = 0;
uint32_t m_portReal = 0;
uint32_t m_preset = 0;
uint32_t m_type = 0;
uint32_t m_baudRate = 0;
uint32_t m_baudRateReal = 0;
uint32_t m_samplingRate = 0;
uint32_t m_samplingRateReal = 0;
uint32_t m_bitDepth = 0;
uint32_t m_bitDepthReal = 0;
uint32_t m_notchFilters = 0;
uint32_t m_notchFiltersReal = 0;
uint32_t m_frameSize = 0;
uint32_t m_dataOffset = 0;
std::map<uint32_t, uint32_t> m_baudRates;
std::map<uint32_t, uint32_t> m_baudRateValues;
std::map<uint32_t, uint32_t> m_notchsFilters;
std::map<uint32_t, uint32_t> m_notchFiltersValues;
std::map<uint32_t, uint32_t> m_bitDepths;
std::map<uint32_t, uint32_t> m_bitDepthValues;
std::map<uint32_t, uint32_t> m_channelTypes;
std::map<uint32_t, uint32_t> m_channelSelectionMasks;
std::map<uint32_t, std::map<uint32_t, float (*)(unsigned char*&)>> m_bitDepthDecoders;
std::map<uint32_t, int (*)()> m_startModules;
std::map<uint32_t, int (*)()> m_stopModules;
std::vector<float> m_samples;
std::vector<unsigned char> m_buffers;
uint32_t m_syncByte = 0;
bool m_wasLastFrameCorrect = false;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyBrainmasterCodeMakerAPI
@@ -0,0 +1,127 @@
/*
* ovas_defines_brainmaster_discovery.h
*
* Copyright (c) 2012, Yann Renard. All rights reserved.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#pragma once
#if defined TARGET_HAS_ThirdPartyBrainmasterCodeMakerAPI
#include "ovasIDriver.h"
#include "CMKRDLLU.H"
#include <string>
namespace OpenViBE {
namespace AcquisitionServer {
typedef enum
{
Preset_Custom=0,
Preset_Discovery_24=1,
Preset_Atlantis_4x4=2,
Preset_Atlantis_2x2=3,
Type_Discovery=0,
Type_Atlantis=1,
ChannelType_EEG=0,
ChannelType_AUX=1,
BaudRate_Default=0,
BaudRate_9600=1,
BaudRate_115200=2,
BaudRate_460800=3,
NotchFilter_Default=0,
NotchFilter_Off=1,
NotchFilter_50=2,
NotchFilter_60=3,
BitDepth_Default=0,
BitDepth_8=1,
BitDepth_16=2,
BitDepth_24=3,
SamplingFrequency_256=0,
SamplingFrequency_512=1,
SamplingFrequency_1024=2,
SamplingFrequency_2048=3,
} EParameter;
inline size_t autoDetectPort(const size_t startPort = 1, const size_t stopPort = 16, const size_t fallback = size_t(-1))
{
for (size_t i = startPort; i <= stopPort; ++i)
{
const BOOL res = ::AtlOpenPort(i, /*m_baudRate*/ 0, nullptr);
::AtlClosePort(i);
if (res) { return i; }
}
return fallback;
}
// returns true on success
// returns false on error
inline bool checkDeviceSerial(const std::string& serial)
{
// Checks length
if (serial.length() != 5) { return false; }
// Checks digits
for (size_t i = 0; i < serial.length(); ++i) { if (serial[i] < '0' || serial[i] > '9') { return false; } }
// First digit gives device kind
switch (serial[0])
{
case '3': // Atlantis
case '4': // Atlantis
case '6': // Discovery
default: break;
}
// Everything's fine
return true;
}
// returns true on success
// returns false on error
inline bool checkDevicePasskey(const std::string& passkey)
{
// Checks length -- other checks are hardcoded assuming length is 15
if (passkey.length() != 14) { return false; }
// Checks separators
if (passkey[4] != '-' || passkey[9] != '-') { return false; }
// Checks alpha-nums
for (size_t i = 0; i < 3; ++i)
{
for (size_t j = 0; j < 4; ++j)
{
const size_t k = i * 5 + j;
if ((passkey[k] < '0' || passkey[k] > '9') && (passkey[k] < 'a' || passkey[k] > 'z')
&& (passkey[k] < 'A' || passkey[k] > 'Z')) { return false; }
}
}
// Everything's fine
return true;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyBrainmasterCodeMakerAPI
@@ -0,0 +1,656 @@
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 2.12 -->
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkAdjustment" id="adjustment1">
<property name="value">1025</property>
<property name="upper">65535</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment3">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment4">
<property name="value">1</property>
<property name="lower">1</property>
<property name="upper">1024</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkListStore" id="model1">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">unspecified</col>
</row>
<row>
<col id="0" translatable="yes">female</col>
</row>
<row>
<col id="0" translatable="yes">male</col>
</row>
<row>
<col id="0" translatable="yes">unknown</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model2">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">128</col>
</row>
<row>
<col id="0" translatable="yes">256</col>
</row>
<row>
<col id="0" translatable="yes">512</col>
</row>
<row>
<col id="0" translatable="yes">1024</col>
</row>
<row>
<col id="0" translatable="yes">2048</col>
</row>
<row>
<col id="0" translatable="yes">4096</col>
</row>
</data>
</object>
<object class="GtkDialog" id="openvibe-acquisition-server-settings">
<property name="border_width">5</property>
<property name="title" translatable="yes">Device configuration</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel" id="label_title">
<property name="visible">True</property>
<property name="label" translatable="yes">Brainamp Standard (through Vision Recorder)</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox">
<property name="visible">True</property>
<property name="border_width">8</property>
<property name="spacing">8</property>
<child>
<object class="GtkHBox" id="hbox">
<property name="visible">True</property>
<property name="spacing">8</property>
<child>
<object class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="n_rows">5</property>
<property name="n_columns">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkSpinButton" id="spinbutton_host_port">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry_host_name">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x2022;</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_host_port">
<property name="visible">True</property>
<property name="label" translatable="yes">Vision Recorder connection port</property>
</object>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_host_name">
<property name="visible">True</property>
<property name="label" translatable="yes">Vision Recorder host name</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_identifier">
<property name="visible">True</property>
<property name="label" translatable="yes">Identifier :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label_age">
<property name="visible">True</property>
<property name="label" translatable="yes">Age :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_gender">
<property name="visible">True</property>
<property name="label" translatable="yes">Gender :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_identifier">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment2</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_age">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment3</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_gender">
<property name="visible">True</property>
<property name="model">model1</property>
<child>
<object class="GtkCellRendererText" id="renderer1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">4</property>
</packing>
</child>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_apply">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-10">button_apply</action-widget>
<action-widget response="-6">button_cancel</action-widget>
</action-widgets>
</object>
<object class="GtkWindow" id="dummy-widgets">
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="n_rows">10</property>
<property name="n_columns">10</property>
<child>
<object class="GtkSpinButton" id="spinbutton_number_of_channels">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment4</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_sampling_frequency">
<property name="visible">True</property>
<property name="model">model2</property>
<child>
<object class="GtkCellRendererText" id="renderer2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_change_channel_names">
<property name="label" translatable="yes">Change channel names</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
</object>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
</interface>
@@ -0,0 +1,364 @@
#include "ovasCDriverBrainProductsBrainVisionRecorder.h"
#include "../ovasCConfigurationNetworkBuilder.h"
#include <system/ovCTime.h>
#include <iostream>
#include <cstdlib>
#include <cstring>
namespace OpenViBE {
namespace AcquisitionServer {
CDriverBrainProductsBrainVisionRecorder::CDriverBrainProductsBrainVisionRecorder(IDriverContext& ctx)
: IDriver(ctx), m_settings("AcquisitionServer_Driver_BrainVisionRecorder", m_driverCtx.getConfigurationManager())
{
m_settings.add("Header", &m_header);
m_settings.add("ServerHostName", &m_sServerHostName);
m_settings.add("ServerHostPort", &m_serverHostPort);
m_settings.load();
}
//___________________________________________________________________//
// //
bool CDriverBrainProductsBrainVisionRecorder::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
{
if (m_driverCtx.isConnected()) { return false; }
// Initialize GUID value
M_DEFINE_GUID(GUID_RDAHeader, 1129858446, 51606, 19590, uint8_t(175), uint8_t(74), uint8_t(152), uint8_t(187), uint8_t(246), uint8_t(201), uint8_t(20),
uint8_t(80));
// Builds up client connection
m_connectionClient = Socket::createConnectionClient();
// Tries to connect to server
m_connectionClient->connect(m_sServerHostName, m_serverHostPort);
// Checks if connection is correctly established
if (!m_connectionClient->isConnected())
{
// In case it is not, try to reconnect
m_connectionClient->connect(m_sServerHostName, m_serverHostPort);
}
if (!m_connectionClient->isConnected())
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Connection problem! Tried 2 times without success! :(\n";
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Verify port number and/or Hostname...\n";
return false;
}
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Client connected\n";
// Initialize vars for reception
m_msgHeader = nullptr;
RDA_MessageHeader msgHeader;
m_msgHeaderStr = (char*)&msgHeader;
uint32_t received = 0;
uint32_t size = sizeof(msgHeader);
// Receive Header
while (received < size)
{
const uint32_t reqLength = size - received;
const uint32_t res = m_connectionClient->receiveBuffer((char*)m_msgHeaderStr, reqLength);
received += res;
m_msgHeaderStr += res;
}
// Check for correct header GUID.
if (!M_COMPARE_GUID(msgHeader.guid, GUID_RDAHeader))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "GUID received is not correct!\n";
return false;
}
// Check for correct header nType
if (msgHeader.nType != 1)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "First Message received is not an header!\n";
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Try to reconnect....\n";
return false;
}
// Check if we need a larger buffer, allocate
if (!reallocateHeaderBuffer(msgHeader.nSize)) { return false; }
// Retrieve rest of data
memcpy(*(&m_msgHeader), &msgHeader, sizeof(msgHeader));
m_msgHeaderStr = (char*)(*(&m_msgHeader)) + sizeof(msgHeader);
received = 0;
size = msgHeader.nSize - sizeof(msgHeader);
while (received < size)
{
const uint32_t reqLength = size - received;
const uint32_t res = m_connectionClient->receiveBuffer((char*)m_msgHeaderStr, reqLength);
received += res;
m_msgHeaderStr += res;
}
m_msgStart = (RDA_MessageStart*)m_msgHeader; // pHeader will retain the pointer ownership
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Header received\n";
// Save Header info into m_header
//m_header.setExperimentID();
//m_header.setExperimentDate();
//m_header.setSubjectId();
//m_header.setSubjectName();
//m_header.setSubjectAge(m_structHeader.subjectAge);
//m_header.setSubjectGender();
//m_header.setLaboratoryId();
//m_header.setLaboratoryName();
//m_header.setTechnicianId();
//m_header.setTechnicianName();
m_header.setChannelCount((uint32_t)m_msgStart->nChannels);
char* channelNames = (char*)m_msgStart->dResolutions + (m_msgStart->nChannels * sizeof(m_msgStart->dResolutions[0]));
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
{
m_header.setChannelName(i, channelNames);
m_header.setChannelGain(i, float((m_msgStart->dResolutions[i])));
m_header.setChannelUnits(i, OVTK_UNIT_Volts, OVTK_FACTOR_Micro);
channelNames += strlen(channelNames) + 1;
}
m_header.setSamplingFrequency(uint32_t(1000000 / m_msgStart->dSamplingInterval)); //dSamplingInterval in microseconds
m_nSamplePerSentBlock = nSamplePerSentBlock;
m_callback = &callback;
m_indexIn = 0;
m_indexOut = 0;
m_buffDataIdx = 0;
m_nMarker = 0;
m_nMarkers = 0;
return true;
}
bool CDriverBrainProductsBrainVisionRecorder::start()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
return true;
}
bool CDriverBrainProductsBrainVisionRecorder::loop()
{
if (!m_driverCtx.isConnected()) { return false; }
if (!m_driverCtx.isStarted()) { return true; }
M_DEFINE_GUID(GUID_RDAHeader,
1129858446, 51606, 19590, uint8_t(175), uint8_t(74), uint8_t(152), uint8_t(187), uint8_t(246), uint8_t(201), uint8_t(20), uint8_t(80)
);
// Initialize var to receive buffer of data
m_msgHeader = nullptr;
RDA_MessageHeader msgHeader;
m_msgHeaderStr = (char*)&msgHeader;
uint32_t received = 0;
uint32_t size = sizeof(msgHeader);
// Receive Header
while (received < size)
{
const uint32_t reqLength = size - received;
const uint32_t res = m_connectionClient->receiveBuffer((char*)m_msgHeaderStr, reqLength);
received += res;
m_msgHeaderStr += res;
}
// Check for correct header nType
if (msgHeader.nType == 1)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Message received is a header!\n";
return false;
}
if (msgHeader.nType == 3)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Message received is a STOP!\n";
return false;
}
if (msgHeader.nType != 4) { return true; }
// Check for correct header GUID.
if (!M_COMPARE_GUID(msgHeader.guid, GUID_RDAHeader))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "GUID received is not correct!\n";
return false;
}
// Check if we need a larger buffer, allocate
if (!reallocateHeaderBuffer(msgHeader.nSize)) { return false; }
// Retrieve rest of block.
memcpy(*(&m_msgHeader), &msgHeader, sizeof(msgHeader));
m_msgHeaderStr = (char*)(*(&m_msgHeader)) + sizeof(msgHeader);
received = 0;
size = msgHeader.nSize - sizeof(msgHeader);
while (received < size)
{
const uint32_t reqLength = size - received;
const uint32_t res = m_connectionClient->receiveBuffer((char*)m_msgHeaderStr, reqLength);
received += res;
m_msgHeaderStr += res;
}
m_buffDataIdx++;
// Put the data into MessageData32 structure
m_msgData = nullptr;
m_msgData = (RDA_MessageData32*)m_msgHeader;
//////////////////////
//Markers
if (m_msgData->nMarkers > 0)
{
// if (m_msgData->nMarkers == 0)
// {
// return true;
// }
m_marker = (RDA_Marker*)((char*)m_msgData->fData + m_msgData->nPoints * m_header.getChannelCount() * sizeof(m_msgData->fData[0]));
m_nMarkers = m_msgData->nMarkers;
m_stimulationIDs.assign(m_nMarkers, 0);
m_stimulationDates.assign(m_nMarkers, 0);
m_stimulationSamples.assign(m_nMarkers, 0);
for (uint32_t i = 0; i < m_msgData->nMarkers; ++i)
{
char* type = m_marker->sTypeDesc;
char* desc = type + strlen(type) + 1;
m_stimulationIDs[i] = atoi(strtok(desc, "S"));
m_stimulationDates[i] = CTime(m_header.getSamplingFrequency(), m_marker->nPosition).time();
m_stimulationSamples[i] = m_marker->nPosition;
m_marker = (RDA_Marker*)((char*)m_marker + m_marker->nSize);
}
m_nMarker += m_msgData->nMarkers;
}
size = m_header.getChannelCount() * uint32_t(m_msgData->nPoints);
if (m_signalBuffers.size() < size) { m_signalBuffers.resize(size); }
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
{
for (uint32_t j = 0; j < uint32_t(m_msgData->nPoints); ++j)
{
m_signalBuffers[j + (i * uint32_t(m_msgData->nPoints))] =
float(m_msgData->fData[(m_header.getChannelCount() * j) + i]) * m_header.getChannelGain(i);
}
}
// send data
CStimulationSet stimSet;
stimSet.setStimulationCount(m_nMarkers);
for (uint32_t i = 0; i < m_nMarkers; ++i)
{
stimSet.setStimulationIdentifier(i, OVTK_StimulationId_Label(m_stimulationIDs[i]));
stimSet.setStimulationDate(i, m_stimulationDates[i]);
stimSet.setStimulationDuration(i, 0);
}
m_callback->setSamples(&m_signalBuffers[0], uint32_t(m_msgData->nPoints));
m_callback->setStimulationSet(stimSet);
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
m_nMarkers = 0;
return true;
}
bool CDriverBrainProductsBrainVisionRecorder::stop()
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Connection stopped\n";
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
return true;
}
bool CDriverBrainProductsBrainVisionRecorder::uninitialize()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
if (m_msgHeader)
{
free(m_msgHeader);
m_msgHeader = nullptr;
}
m_msgHeaderStr = nullptr;
m_msgStart = nullptr;
m_msgStop = nullptr;
m_msgData = nullptr;
m_marker = nullptr;
m_callback = nullptr;
m_headerBufferSize = 0;
m_signalBuffers.clear();
// Cleans up client connection
m_connectionClient->close();
m_connectionClient->release();
m_connectionClient = nullptr;
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Client disconnected\n";
return true;
}
//___________________________________________________________________//
// //
bool CDriverBrainProductsBrainVisionRecorder::configure()
{
CConfigurationNetworkBuilder
config(Directories::getDataDir() + "/applications/acquisition-server/interface-BrainProducts-BrainVisionRecorder.ui");
config.setHostName(m_sServerHostName);
config.setHostPort(m_serverHostPort);
if (config.configure(m_header))
{
m_sServerHostName = config.getHostName();
m_serverHostPort = config.getHostPort();
m_settings.save();
return true;
}
return false;
}
bool CDriverBrainProductsBrainVisionRecorder::reallocateHeaderBuffer(const size_t newSize)
{
// Reallocate buffer?
if (newSize > m_headerBufferSize)
{
if (m_msgHeader) { free(m_msgHeader); }
m_headerBufferSize = newSize;
m_msgHeader = (RDA_MessageHeader*)malloc(m_headerBufferSize);
if (!m_msgHeader)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Couldn't allocate memory\n";
return false;
}
}
return true;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,141 @@
#pragma once
#include "ovasIDriver.h"
#include "../ovasCHeader.h"
#include <openvibe/ov_all.h>
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
#include <vector>
#include <socket/IConnectionClient.h>
#ifndef M_DEFINE_GUID
#define M_DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
#endif
#ifndef M_COMPARE_GUID
#define M_COMPARE_GUID(name1, name2) \
( name1.Data1 == name2.Data1 && name1.Data2 == name2.Data2 && \
name1.Data3 == name2.Data3 && name1.Data4[0] == name2.Data4[0] && \
name1.Data4[1] == name2.Data4[1] && name1.Data4[2] == name2.Data4[2] &&\
name1.Data4[3] == name2.Data4[3] && name1.Data4[4] == name2.Data4[4] &&\
name1.Data4[5] == name2.Data4[5] && name1.Data4[6] == name2.Data4[6] &&\
name1.Data4[7] == name2.Data4[7] \
)
#endif
namespace OpenViBE {
namespace AcquisitionServer {
class CDriverBrainProductsBrainVisionRecorder final : public IDriver
{
public:
explicit CDriverBrainProductsBrainVisionRecorder(IDriverContext& ctx);
~CDriverBrainProductsBrainVisionRecorder() override {}
const char* getName() override { return "Brain Products amplifiers (through BrainVision Recorder)"; }
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
bool uninitialize() override;
bool start() override;
bool stop() override;
bool loop() override;
bool isConfigurable() override { return true; }
bool configure() override;
const IHeader* getHeader() override { return &m_header; }
protected:
SettingsHelper m_settings;
IDriverCallback* m_callback = nullptr;
Socket::IConnectionClient* m_connectionClient = nullptr;
CString m_sServerHostName = "localhost";
uint32_t m_serverHostPort = 51244;
CHeader m_header;
uint32_t m_nSamplePerSentBlock = 0;
uint32_t m_indexIn = 0;
uint32_t m_indexOut = 0;
uint32_t m_buffDataIdx = 0;
uint32_t m_nMarker = 0;
uint32_t m_nMarkers = 0;
std::vector<size_t> m_stimulationIDs;
std::vector<size_t> m_stimulationDates;
std::vector<size_t> m_stimulationSamples;
#pragma pack(push)
#pragma pack(1)
typedef struct
{
uint32_t Data1;
unsigned short Data2;
unsigned short Data3;
uint8_t Data4[8];
} GUID;
struct RDA_Marker //; A single marker in the marker array of RDA_MessageData
{
uint32_t nSize; // Size of this marker.
uint32_t nPosition; // Relative position in the data block.
uint32_t nPoints; // Number of points of this marker
int nChannel; // Associated channel number (-1 = all channels).
char sTypeDesc[1]; // Type, description in ASCII delimited by '\0'.
};
struct RDA_MessageHeader //; Message header
{
GUID guid; // Always GUID_RDAHeader
uint32_t nSize; // Size of the message block in bytes including this header
uint32_t nType; // Message type.
};
// **** Messages sent by the RDA server to the clients. ****
struct RDA_MessageStart : RDA_MessageHeader //; Setup / Start infos, Header -> nType = 1
{
uint32_t 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.
};
struct RDA_MessageStop : RDA_MessageHeader { }; //; Data acquisition has been stopped. // Header -> nType = 3
struct RDA_MessageData32 : RDA_MessageHeader //; Block of 32-bit floating point data, Header -> nType = 4, sent only from port 51244
{
uint32_t nBlock; // Block number, i.e. acquired blocks since acquisition started.
uint32_t nPoints; // Number of data points in this block
uint32_t 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]
};
#pragma pack(pop)
RDA_MessageHeader* m_msgHeader = nullptr;
char* m_msgHeaderStr = nullptr;
size_t m_headerBufferSize = 0;
RDA_MessageStart* m_msgStart = nullptr;
RDA_MessageStop* m_msgStop = nullptr;
RDA_MessageData32* m_msgData = nullptr;
RDA_Marker* m_marker = nullptr;
std::vector<float> m_signalBuffers;
private:
bool reallocateHeaderBuffer(size_t newSize);
};
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,432 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 2.6 -->
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkAdjustment" id="adjustment1">
<property name="upper">100</property>
<property name="value">18</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment3">
<property name="lower">1</property>
<property name="upper">128</property>
<property name="value">128</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment4">
<property name="lower">1</property>
<property name="upper">254</property>
<property name="value">1</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkListStore" id="model1">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">unspecified</col>
</row>
<row>
<col id="0" translatable="yes">female</col>
</row>
<row>
<col id="0" translatable="yes">male</col>
</row>
<row>
<col id="0" translatable="yes">unknown</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model2">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">300</col>
</row>
<row>
<col id="0" translatable="yes">500</col>
</row>
<row>
<col id="0" translatable="yes">540</col>
</row>
<row>
<col id="0" translatable="yes">600</col>
</row>
<row>
<col id="0" translatable="yes">1000</col>
</row>
</data>
</object>
<object class="GtkDialog" id="openvibe-acquisition-server-settings">
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Device configuration</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="spacing">8</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_apply">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_title">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Cognionics</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">8</property>
<property name="spacing">8</property>
<child>
<object class="GtkHBox" id="hbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">8</property>
<child>
<object class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">7</property>
<property name="n_columns">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkComboBox" id="combobox_gender">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">model1</property>
<child>
<object class="GtkCellRendererText" id="renderer1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_age">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_identifier">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment2</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_sampling_frequency">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Sampling frequency :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_number_of_channels">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Number of channels :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_gender">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Gender :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_age">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Age :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_identifier">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Identifier :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
</child>
<child>
<object class="GtkComboBox" id="combobox_sampling_frequency">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">model2</property>
<property name="active">0</property>
<child>
<object class="GtkCellRendererText" id="renderer2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_number_of_channels">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment3</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0.49000000953674316</property>
<property name="label" translatable="yes">COM Port:</property>
</object>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_comport">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment4</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Check impedance :</property>
</object>
<packing>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_impedance">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_change_channel_names">
<property name="label" translatable="yes">Change channel names</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-10">button_apply</action-widget>
<action-widget response="-6">button_cancel</action-widget>
</action-widgets>
</object>
</interface>
@@ -0,0 +1,72 @@
#if defined(WIN32)
#include "ovasCConfigurationCognionics.h"
namespace OpenViBE {
namespace AcquisitionServer {
/*_________________________________________________
Insert callback to specific widget here
Example with a button that launch a calibration of the device:
//Callback connected to a dedicated gtk button:
static void button_calibrate_pressed_cb(GtkButton* button, void* data)
{
CConfigurationCognionics* config=static_cast<CConfigurationCognionics*>(data);
config->buttonCalibratePressedCB();
}
//Callback actually called:
void CConfigurationGTecGUSBamp::buttonCalibratePressedCB()
{
// Connect to the hardware, ask for calibration, verify the return code, etc.
}
_________________________________________________*/
// If you added more reference attribute, initialize them here
CConfigurationCognionics::CConfigurationCognionics(IDriverContext& ctx, const char* gtkBuilderFilename, uint32_t& comport)
: CConfigurationBuilder(gtkBuilderFilename), m_COMPORT(comport), m_driverCtx(ctx) {}
bool CConfigurationCognionics::preConfigure()
{
if (! CConfigurationBuilder::preConfigure()) { return false; }
GtkSpinButton* comport = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_comport"));
gtk_spin_button_set_value(comport, m_COMPORT);
// Connect here all callbacks
// Example:
// g_signal_connect(gtk_builder_get_object(m_builder, "button_calibrate"), "pressed", G_CALLBACK(button_calibrate_pressed_cb), this);
// Insert here the pre-configure code.
// For example, you may want to check if a device is currently connected
// and if more than one are connected. Then you can list in a dedicated combo-box
// the device currently connected so the user can choose which one he wants to acquire from.
return true;
}
bool CConfigurationCognionics::postConfigure()
{
if (m_applyConfig)
{
GtkSpinButton* comport = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_comport"));
m_COMPORT = uint32_t(gtk_spin_button_get_value(comport));
//printf("Selected COM Port: %d", m_COMPORT);
// If the user pressed the "apply" button, you need to save the changes made in the configuration.
// For example, you can save the connection ID of the selected device:
// m_connectionID = <value-from-gtk-widget>
}
if (! CConfigurationBuilder::postConfigure()
) { return false; } // normal header is filled (Subject ID, Age, Gender, channels, sampling frequency), ressources are realesed
return true;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // WIN32
@@ -0,0 +1,43 @@
#pragma once
#include "../ovasCConfigurationBuilder.h"
#include "ovasIDriver.h"
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CConfigurationCognionics
* \author Mike Chi (Cognionics, Inc.)
* \copyright AGPL3
* \date Thu Apr 18 21:19:49 2013
* \brief The CConfigurationCognionics handles the configuration dialog specific to the Cognionics device.
*
* TODO: details
*
* \sa CDriverCognionics
*/
class CConfigurationCognionics final : public CConfigurationBuilder
{
public:
// you may have to add to your constructor some reference parameters
// for example, a connection ID:
//CConfigurationCognionics(IDriverContext& ctx, const char* gtkBuilderFilename, uint32_t& rConnectionId);
CConfigurationCognionics(IDriverContext& ctx, const char* gtkBuilderFilename, uint32_t& comport);
bool preConfigure() override;
bool postConfigure() override;
uint32_t& m_COMPORT;
protected:
IDriverContext& m_driverCtx;
/*
* Insert here all specific attributes, such as a connection ID.
* use references to directly modify the corresponding attribute of the driver
* Example:
*/
};
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,398 @@
#include "ovasCDriverCognionics.h"
#include "ovasCConfigurationCognionics.h"
#include <stdio.h>
#include <stdlib.h>
#if defined(WIN32)
#include <windows.h>
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace AcquisitionServer {
//Cognionics functions
int init_SPP(int port);
void getData();
double get_IMP_CH(int ch);
void close_SPP();
unsigned long grab_SPP(int bytes, unsigned char* buf);
void write_SPP(int bytes, unsigned char* buf);
//Cognionics defs
#define GAIN 3.0
#define VREF 2.5
#define ISTIM 0.000000024
#define ADC_TO_VOLTS 2.0*(VREF/(4294967296.0*GAIN))
#define TO_Z 1.4/(ISTIM*2.0)
//Cognionics variables
int EEG_GRAB;
int CHS;
int SAMPLE_RATE;
float* dataBufferPtr;
int prev_trigger;
CStimulationSet cogStimulationSet;
//Cognionics Buffers
int* prev_4_samples; //buffer for previous 4 samples in each channel for computing MA and IMP
//___________________________________________________________________//
// //
CDriverCognionics::CDriverCognionics(IDriverContext& ctx)
: IDriver(ctx), m_settings("AcquisitionServer_Driver_Cognionics", m_driverCtx.getConfigurationManager()), m_comPort(1)
{
m_header.setSamplingFrequency(300);
m_header.setChannelCount(64);
m_settings.add("Header", &m_header);
m_settings.add("ComPort", &m_comPort);
m_settings.load();
}
//___________________________________________________________________//
// //
bool CDriverCognionics::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
{
if (m_driverCtx.isConnected() || !m_header.isChannelCountSet() || !m_header.isSamplingFrequencySet()) { return false; }
// Builds up a buffer to store
// acquired samples. This buffer
// will be sent to the acquisition
// server later...
m_sample = new float[m_header.getChannelCount() * nSamplePerSentBlock];
//initalize Cognionics varaibles, pointers and buffers
EEG_GRAB = nSamplePerSentBlock;
SAMPLE_RATE = m_header.getSamplingFrequency();
CHS = m_header.getChannelCount();
dataBufferPtr = &m_sample[0];
prev_4_samples = new int[EEG_GRAB * CHS];
if (!m_sample)
{
delete [] m_sample;
delete [] prev_4_samples;
m_sample = nullptr;
return false;
}
// ...
// initialize hardware and get
// available header information
// from it
// Using for example the connection ID provided by the configuration (m_connectionID)
// ...
//init serial port
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Attempting to Connect to Device at COM Port: " << m_comPort << "\n";
const int serSuccess = init_SPP(m_comPort);
if (serSuccess == -1)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unable to Open Port, Please Check Device and Settings\n";
return false;
}
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Successfully Opened Port at COM: " << m_comPort << "\n";
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Number Channels (EEG + Packet Counter + Trigger): " << m_header.getChannelCount() << "\n";
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Sampling Rate: " << m_header.getSamplingFrequency() << "\n";
for (size_t c = 0; c < m_header.getChannelCount(); ++c) { m_header.setChannelUnits(c, OVTK_UNIT_Volts, OVTK_FACTOR_Base); }
//set impedance check mode ON
getData();
unsigned char imp_on = 0x11;
write_SPP(1, &imp_on);
// Saves parameters
m_callback = &callback;
m_nSamplePerSentBlock = nSamplePerSentBlock;
return true;
}
bool CDriverCognionics::start()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// ...
// request hardware to start
// sending data
// ...
//set impedance check mode off
getData();
unsigned char imp_off = 0x12;
write_SPP(1, &imp_off);
return true;
}
bool CDriverCognionics::loop()
{
if (!m_driverCtx.isConnected()) { return false; }
if (m_driverCtx.isStarted())
{
// ...
// receive samples from hardware
// put them the correct way in the sample array
// whether the buffer is full, send it to the acquisition server
//...
//get the number of samples specified
getData();
//OpenVibe call back for new samples
m_callback->setSamples(m_sample);
// When your sample buffer is fully loaded,
// it is advised to ask the acquisition server
// to correct any drift in the acquisition automatically.
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
// ...
// receive events from hardware
// and put them the correct way in a CStimulationSet object
//...
m_callback->setStimulationSet(cogStimulationSet);
cogStimulationSet.clear();
}
else
{
//get a fresh batch of samples to keep connection alive
getData();
if (m_driverCtx.isImpedanceCheckRequested())
{
//update impedance values
for (int c = 0; c < CHS; ++c) { m_driverCtx.updateImpedance(c, get_IMP_CH(c)); }
}
}
return true;
}
bool CDriverCognionics::stop()
{
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
// ...
// request the hardware to stop
// sending data
// ...
//set impedance check mode on
getData();
unsigned char imp_off = 0x11;
write_SPP(1, &imp_off);
return true;
}
bool CDriverCognionics::uninitialize()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// ...
// uninitialize hardware here
// ...
close_SPP();
//free buffers
delete [] m_sample;
delete [] prev_4_samples;
m_sample = nullptr;
m_callback = nullptr;
return true;
}
//___________________________________________________________________//
// //
bool CDriverCognionics::isConfigurable()
{
return true; // change to false if your device is not configurable
}
bool CDriverCognionics::configure()
{
// Change this line if you need to specify some references to your driver attribute that need configuration, e.g. the connection ID.
CConfigurationCognionics config(m_driverCtx,
Directories::getDataDir() + "/applications/acquisition-server/interface-Cognionics.ui",
m_comPort); // the specific header is passed into the specific configuration
if (!config.configure(m_header)) { return false; }
m_settings.save();
return true;
}
void getData()
{
unsigned char packet_start = 0;
int lsb2, lsb1;
unsigned char temp;
for (int c = 0; c < EEG_GRAB; ++c)
{
packet_start = 0;
//wait for packet start
while (packet_start != 0xFF) { grab_SPP(1, &packet_start); }
//get packet counter
grab_SPP(1, &packet_start);
//grab CHS-2 since the last two channels are for sync and packet counter
for (int j = 0; j < (CHS - 2); ++j)
{
grab_SPP(1, &temp);
int msb = temp;
grab_SPP(1, &temp);
lsb2 = temp;
grab_SPP(1, &temp);
lsb1 = temp;
//reassemble 24-bit 2's compltement promoted to 32 bit int
msb = (msb << 24) | (lsb2 << 17) | (lsb1 << 10);
//shift previous 4 sample buffer
*(prev_4_samples + j * 4 + 3) = *(prev_4_samples + j * 4 + 2);
*(prev_4_samples + j * 4 + 2) = *(prev_4_samples + j * 4 + 1);
*(prev_4_samples + j * 4 + 1) = *(prev_4_samples + j * 4 + 0);
*(prev_4_samples + j * 4 + 0) = msb;
const double sample = ADC_TO_VOLTS * double(msb);
//4-point MA for devices without way to disable impedance
/*
eeg_sample = ADC_TO_VOLTS * ((double) *(prev_4_samples + j*4 + 3) +
*(prev_4_samples + j*4 + 2) +
*(prev_4_samples + j*4 + 1) +
*(prev_4_samples + j*4 + 0) )/4.0;
*/
*(dataBufferPtr + j * EEG_GRAB + c) = float(sample);
}
//save packet counter;
*(dataBufferPtr + (CHS - 1) * EEG_GRAB + c) = float(packet_start);
//dummy data
grab_SPP(1, &temp);
grab_SPP(1, &temp);
//grab trigger
grab_SPP(1, &temp);
lsb2 = temp;
grab_SPP(1, &temp);
lsb1 = temp;
//assemble trigger code
int newTrigger = (lsb2 << 8) | lsb1;
//downshift serial triggers if necessary
if (newTrigger > 255) { newTrigger = newTrigger >> 8; }
//store in EEG channel
*(dataBufferPtr + (CHS - 2) * EEG_GRAB + c) = float(newTrigger);
//detect new stimulation
if (newTrigger != prev_trigger)
{
//time offset from start of chunk (c - current sample in the chunk)
const uint64_t date = c * (1LL << 32) / SAMPLE_RATE;
cogStimulationSet.appendStimulation(OVTK_StimulationId_Label(newTrigger&0x000000ff), date, 0);
}
prev_trigger = newTrigger;
}
}
double get_IMP_CH(const int ch)
{
double d1 = double(*(prev_4_samples + ch * 4 + 2)) - double(*(prev_4_samples + ch * 4 + 0));
double d2 = double(*(prev_4_samples + ch * 4 + 3)) - double(*(prev_4_samples + ch * 4 + 1));
d1 = d1 * ADC_TO_VOLTS;
d2 = d2 * ADC_TO_VOLTS;
if (d1 < 0) { d1 = -d1; }
if (d2 < 0) { d2 = -d2; }
if (d2 > d1) { d1 = d2; }
return d1 * TO_Z;
}
//serial port handling
HANDLE hSerial;
COMMTIMEOUTS timeouts = { 0 };
int init_SPP(const int port)
{
char com[100];
sprintf(com, "\\\\.\\COM%d", port);
hSerial = CreateFile(com, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hSerial == INVALID_HANDLE_VALUE) { return -1; }
//setup serial port parameters
DCB dcbSerialParams = { 0 };
if (!GetCommState(hSerial, &dcbSerialParams))
{
//error getting state
}
dcbSerialParams.BaudRate = 1500000;
dcbSerialParams.ByteSize = 8;
dcbSerialParams.StopBits = ONESTOPBIT;
dcbSerialParams.Parity = NOPARITY;
if (!SetCommState(hSerial, &dcbSerialParams))
{
//error setting serial port state
}
//setup serial port timeout
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout = 500;
timeouts.ReadTotalTimeoutConstant = 50;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.WriteTotalTimeoutConstant = 50;
timeouts.WriteTotalTimeoutMultiplier = 10;
if (!SetCommTimeouts(hSerial, &timeouts))
{
//error occureed. Inform user
}
return 0;
}
void close_SPP() { CloseHandle(hSerial); }
unsigned long grab_SPP(const int bytes, unsigned char* buf)
{
DWORD dwBytesRead = 0;
ReadFile(hSerial, buf, bytes, &dwBytesRead, nullptr);
return int(dwBytesRead);
}
void write_SPP(const int bytes, unsigned char* buf)
{
DWORD dwBytesWritten = 0;
WriteFile(hSerial, buf, bytes, &dwBytesWritten, nullptr);
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif //WIN32
@@ -0,0 +1,64 @@
#pragma once
#include "ovasIDriver.h"
#include "../ovasCHeader.h"
#include <openvibe/ov_all.h>
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CDriverCognionics
* \author Mike Chi (Cognionics, Inc.)
* \copyright AGPL3
* \date Thu Apr 18 21:19:49 2013
* \brief The CDriverCognionics allows the acquisition server to acquire data from a Cognionics device.
*
* TODO: details
*
* \sa CConfigurationCognionics
*/
class CDriverCognionics final : public IDriver
{
public:
explicit CDriverCognionics(IDriverContext& ctx);
~CDriverCognionics() override {}
const char* getName() override { return "Cognionics"; }
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
bool uninitialize() override;
bool start() override;
bool stop() override;
bool loop() override;
bool isConfigurable() override;
bool configure() override;
const IHeader* getHeader() override { return &m_header; }
bool isFlagSet(const EDriverFlag flag) const override { return flag == EDriverFlag::IsUnstable; }
protected:
SettingsHelper m_settings;
IDriverCallback* m_callback = nullptr;
// Replace this generic Header with any specific header you might have written
CHeader m_header;
uint32_t m_nSamplePerSentBlock = 0;
float* m_sample = nullptr;
private:
/*
* Insert here all specific attributes, such as USB port number or device ID.
* Example :
*/
uint32_t m_comPort = 0;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,639 @@
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 2.12 -->
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkAdjustment" id="adjustment1">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="upper">65535</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment3">
<property name="value">1</property>
<property name="lower">1</property>
<property name="upper">1024</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment4">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkListStore" id="model1">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">unspecified</col>
</row>
<row>
<col id="0" translatable="yes">female</col>
</row>
<row>
<col id="0" translatable="yes">male</col>
</row>
<row>
<col id="0" translatable="yes">unknown</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model2">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">128</col>
</row>
<row>
<col id="0" translatable="yes">256</col>
</row>
<row>
<col id="0" translatable="yes">512</col>
</row>
<row>
<col id="0" translatable="yes">1024</col>
</row>
<row>
<col id="0" translatable="yes">2048</col>
</row>
<row>
<col id="0" translatable="yes">4096</col>
</row>
</data>
</object>
<object class="GtkDialog" id="openvibe-acquisition-server-settings">
<property name="border_width">5</property>
<property name="title" translatable="yes">Device configuration</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel" id="label_title">
<property name="visible">True</property>
<property name="label" translatable="yes">CTF/VSM MEG</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox">
<property name="visible">True</property>
<property name="border_width">8</property>
<property name="spacing">8</property>
<child>
<object class="GtkHBox" id="hbox">
<property name="visible">True</property>
<property name="spacing">8</property>
<child>
<object class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="n_rows">4</property>
<property name="n_columns">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkLabel" id="label_identifier">
<property name="visible">True</property>
<property name="label" translatable="yes">Identifier :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_identifier">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_gender">
<property name="visible">True</property>
<property name="label" translatable="yes">Gender :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_gender">
<property name="visible">True</property>
<property name="model">model1</property>
<child>
<object class="GtkCellRendererText" id="renderer1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_host_name">
<property name="visible">True</property>
<property name="label" translatable="yes">Connection host name</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry_host_name">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x2022;</property>
<property name="text" translatable="yes">localhost</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_host_port">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment2</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_host_port">
<property name="visible">True</property>
<property name="label" translatable="yes">Connection host port</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">4</property>
</packing>
</child>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_apply">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-10">button_apply</action-widget>
<action-widget response="-6">button_cancel</action-widget>
</action-widgets>
</object>
<object class="GtkWindow" id="dummy-widgets">
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="n_rows">10</property>
<property name="n_columns">10</property>
<child>
<object class="GtkSpinButton" id="spinbutton_number_of_channels">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment3</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_sampling_frequency">
<property name="visible">True</property>
<property name="model">model2</property>
<child>
<object class="GtkCellRendererText" id="renderer2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_change_channel_names">
<property name="label" translatable="yes">Change channel names</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
</object>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_age">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment4</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
</interface>
@@ -0,0 +1,372 @@
#include "ovasCDriverCtfVsmMeg.h"
#include "../ovasCConfigurationNetworkBuilder.h"
#include <system/ovCTime.h>
#include <iostream>
namespace OpenViBE {
namespace AcquisitionServer {
CDriverCtfVsmMeg::CDriverCtfVsmMeg(IDriverContext& ctx)
: IDriver(ctx), m_settings("AcquisitionServer_Driver_CtfVsmMeg", m_driverCtx.getConfigurationManager())
{
m_settings.add("Header", &m_header);
m_settings.add("ServerHostName", &m_sServerHostName);
m_settings.add("ServerHostPort", &m_serverHostPort);
m_settings.load();
}
//___________________________________________________________________//
// //
bool CDriverCtfVsmMeg::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
{
if (m_driverCtx.isConnected()) { return false; }
// Builds up client connection
m_connectionClient = Socket::createConnectionClient();
// Tries to connect to server
m_connectionClient->connect(m_sServerHostName, m_serverHostPort);
// Checks if connection is correctly established
if (!m_connectionClient->isConnected())
{
// In case it is not, try to reconnect
m_connectionClient->connect(m_sServerHostName, m_serverHostPort);
}
if (!m_connectionClient->isConnected())
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Connection problem! Tried 2 times without success! :(\n";
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Verify port number and/or Hostname...\n";
return false;
}
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Client connected\n";
// Initialize vars for reception
uint32_t received = 0;
m_pStructHeader = (char*)&m_structHeader;
// Receive Header
while (received < sizeof(m_structHeader))
{
const uint32_t reqLength = sizeof(m_structHeader) - received;
const uint32_t res = m_connectionClient->receiveBuffer((char*)m_pStructHeader, reqLength);
received += res;
m_pStructHeader += res;
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Receiving Header....\n";
}
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Header received\n";
// Save Header info into m_header
//m_header.setExperimentID();
//m_header.setExperimentDate();
//m_header.setSubjectId();
//m_header.setSubjectName();
m_header.setSubjectAge(m_structHeader.subjectAge);
//m_header.setSubjectGender();
//m_header.setLaboratoryId();
//m_header.setLaboratoryName();
//m_header.setTechnicianId();
//m_header.setTechnicianName();
m_header.setChannelCount(m_structHeader.numberOfChannels);
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
{
m_header.setChannelName(i, m_structHeader.channelLabel[i]);
m_header.setChannelGain(i, (m_structHeader.qGain[i] * m_structHeader.properGain[i]));
}
m_header.setSamplingFrequency(uint32_t(m_structHeader.samplingRate));
//m_header.setSampleCount(nSamplePerSentBlock);
m_sample = new float[m_header.getChannelCount() * nSamplePerSentBlock];
if (!m_sample)
{
delete [] m_sample;
m_sample = nullptr;
return false;
}
m_callback = &callback;
m_nSamplePerSentBlock = nSamplePerSentBlock;
m_indexIn = 0;
m_indexOut = 0;
m_socketFlag = 1976;
m_buffDataIdx = 0;
return true;
}
bool CDriverCtfVsmMeg::start()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
if (m_buffDataIdx == 0)
{
// Initialize var to send flag
uint32_t sent = 0;
uint32_t* flag = static_cast<uint32_t*>(&m_socketFlag);
// Send flag to server
while (sent < sizeof(m_socketFlag))
{
const uint32_t reqLength = sizeof(m_socketFlag) - sent;
const uint32_t res = m_connectionClient->sendBuffer(static_cast<uint32_t*>(flag), reqLength);
sent += res;
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << " > Sending flag to start....\n";
}
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Flag sent\n";
}
return true;
}
bool CDriverCtfVsmMeg::loop()
{
if (!m_driverCtx.isConnected()) { return false; }
if (!m_driverCtx.isStarted()) { return true; }
// Initialize var to receive buffer of data
uint32_t received = 0;
m_pStructBuffData = (char*)&m_structBuffData;
// Read a first buffer of data
m_buffDataIdx++;
while (received < sizeof(m_structBuffData))
{
const uint32_t reqLength = sizeof(m_structBuffData) - received;
const uint32_t res = m_connectionClient->receiveBuffer((char*)m_pStructBuffData, reqLength);
received += res;
m_pStructBuffData += res;
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << " > Receiving buffer of data....nb: " << m_buffDataIdx << "\n";
}
// if input flow is equal to output one
if (m_nSamplePerSentBlock == uint32_t(m_structBuffData.nbSamplesPerChanPerBlock))
{
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
{
for (uint32_t j = 0; j < m_nSamplePerSentBlock; ++j)
{
if ((m_structHeader.channelTypeIndex[i] == 0) | (m_structHeader.channelTypeIndex[i] == 1) | (m_structHeader.channelTypeIndex[i] == 5))
{
m_sample[j + i * m_nSamplePerSentBlock] = float(
float(m_structBuffData.data[m_header.getChannelCount() * j + i]) * 1e15 / m_header.getChannelGain(i));
}
else
{
m_sample[j + i * m_nSamplePerSentBlock] =
float(m_structBuffData.data[m_header.getChannelCount() * j + i]) / m_header.getChannelGain(i);
}
}
}
// send data
m_callback->setSamples(m_sample);
}
else
{
// if output flow is bigger
if (m_nSamplePerSentBlock > uint32_t(m_structBuffData.nbSamplesPerChanPerBlock))
{
while (m_indexOut + uint32_t(m_structBuffData.nbSamplesPerChanPerBlock) < m_nSamplePerSentBlock)
{
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
{
for (uint32_t j = 0; j < uint32_t(m_structBuffData.nbSamplesPerChanPerBlock); ++j)
{
if ((m_structHeader.channelTypeIndex[i] == 0) | (m_structHeader.channelTypeIndex[i] == 1) | (m_structHeader.channelTypeIndex[i] == 5))
{
m_sample[m_indexOut + j + i * m_nSamplePerSentBlock] = float(
float(m_structBuffData.data[m_header.getChannelCount() * j + i]) * 1e15 / m_header.getChannelGain(i));
}
else
{
m_sample[m_indexOut + j + i * m_nSamplePerSentBlock] =
float(m_structBuffData.data[m_header.getChannelCount() * j + i]) / m_header.getChannelGain(i);
}
}
}
m_indexOut = m_indexOut + uint32_t(m_structBuffData.nbSamplesPerChanPerBlock);
// Ask for another buffer
received = 0;
m_pStructBuffData = (char*)&m_structBuffData;
while (received < sizeof(m_structBuffData))
{
const uint32_t reqLength = sizeof(m_structBuffData) - received;
const uint32_t res = m_connectionClient->receiveBuffer((char*)m_pStructBuffData, reqLength);
received += res;
m_pStructBuffData += res;
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << " > Receiving buffer of data....nb: " << m_buffDataIdx << "\n";
}
}
// Finishing filling up
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
{
for (uint32_t j = 0; j < m_nSamplePerSentBlock - m_indexOut; ++j)
{
if ((m_structHeader.channelTypeIndex[i] == 0) | (m_structHeader.channelTypeIndex[i] == 1) | (m_structHeader.channelTypeIndex[i] == 5))
{
m_sample[m_indexOut + j + i * m_nSamplePerSentBlock] = float(
float(m_structBuffData.data[m_header.getChannelCount() * j + i]) * 1e15 / m_header.getChannelGain(i));
}
else
{
m_sample[m_indexOut + j + i * m_nSamplePerSentBlock] =
float(m_structBuffData.data[m_header.getChannelCount() * j + i]) / m_header.getChannelGain(i);
}
}
}
// send data
m_callback->setSamples(m_sample);
// Reset index out because new output
m_indexIn = m_nSamplePerSentBlock - m_indexOut;
m_indexOut = 0;
// Save the rest of the buffer input
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
{
for (uint32_t j = 0; j < uint32_t(m_structBuffData.nbSamplesPerChanPerBlock) - m_indexIn; ++j)
{
if ((m_structHeader.channelTypeIndex[i] == 0) | (m_structHeader.channelTypeIndex[i] == 1) | (m_structHeader.channelTypeIndex[i] == 5))
{
m_sample[j + i * m_nSamplePerSentBlock] = float(
float(m_structBuffData.data[m_indexIn * m_header.getChannelCount() + m_header.getChannelCount() * j + i]) * 1e15 / m_header.
getChannelGain(i));
}
else
{
m_sample[j + i * m_nSamplePerSentBlock] =
float(m_structBuffData.data[m_indexIn * m_header.getChannelCount() + m_header.getChannelCount() * j + i]) / m_header.
getChannelGain(i);
}
}
}
m_indexOut = uint32_t(m_structBuffData.nbSamplesPerChanPerBlock) - m_indexIn;
m_indexIn = 0;
}
else
{
// if output flow is smaller
while (m_indexIn + (m_nSamplePerSentBlock - m_indexOut) < uint32_t(m_structBuffData.nbSamplesPerChanPerBlock))
{
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
{
for (uint32_t j = 0; j < m_nSamplePerSentBlock - m_indexOut; ++j)
{
if ((m_structHeader.channelTypeIndex[i] == 0) | (m_structHeader.channelTypeIndex[i] == 1) | (m_structHeader.channelTypeIndex[i] == 5))
{
m_sample[j + m_indexOut + i * m_nSamplePerSentBlock] = float(
float(m_structBuffData.data[m_header.getChannelCount() * m_indexIn + m_header.getChannelCount() * j + i]) * 1e15 /
m_header.getChannelGain(i));
}
else
{
m_sample[j + m_indexOut + i * m_nSamplePerSentBlock] =
float(m_structBuffData.data[m_header.getChannelCount() * m_indexIn + m_header.getChannelCount() * j + i]) / m_header.
getChannelGain(i);
}
}
}
//send data
m_callback->setSamples(m_sample);
m_indexIn = m_indexIn + (m_nSamplePerSentBlock - m_indexOut);
m_indexOut = 0;
}
// save the rest of buff data
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
{
for (uint32_t j = 0; j < uint32_t(m_structBuffData.nbSamplesPerChanPerBlock) - m_indexIn; ++j)
{
if ((m_structHeader.channelTypeIndex[i] == 0) | (m_structHeader.channelTypeIndex[i] == 1) | (m_structHeader.channelTypeIndex[i] == 5))
{
m_sample[j + m_indexOut + i * m_nSamplePerSentBlock] = float(
float(m_structBuffData.data[m_header.getChannelCount() * m_indexIn + m_header.getChannelCount() * j + i]) * 1e15 / m_header.
getChannelGain(i));
}
else
{
m_sample[j + m_indexOut + i * m_nSamplePerSentBlock] =
float(m_structBuffData.data[m_header.getChannelCount() * m_indexIn + m_header.getChannelCount() * j + i]) / m_header.
getChannelGain(i);
}
}
}
m_indexOut = uint32_t(m_structBuffData.nbSamplesPerChanPerBlock) - m_indexIn;
m_indexIn = 0;
}
}
return true;
}
bool CDriverCtfVsmMeg::stop()
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Connection stopped\n";
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
return true;
}
bool CDriverCtfVsmMeg::uninitialize()
{
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
delete [] m_sample;
m_sample = nullptr;
m_callback = nullptr;
// Cleans up client connection
m_connectionClient->close();
m_connectionClient->release();
m_connectionClient = nullptr;
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Client disconnected\n";
return true;
}
//___________________________________________________________________//
// //
bool CDriverCtfVsmMeg::configure()
{
CConfigurationNetworkBuilder config(Directories::getDataDir() + "/applications/acquisition-server/interface-CtfVsm-Meg.ui");
config.setHostName(m_sServerHostName);
config.setHostPort(m_serverHostPort);
if (config.configure(m_header))
{
m_sServerHostName = config.getHostName();
m_serverHostPort = config.getHostPort();
m_settings.save();
return true;
}
return false;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,109 @@
#pragma once
#include "ovasIDriver.h"
#include "../ovasCHeader.h"
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
#include <openvibe/ov_all.h>
#include <socket/IConnectionClient.h>
#define NB_CHAN_RECORDED_MAX 410
#define NB_SAMP_ACQ_Packet 28160
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CDriverCtfVsmMeg
* \author Pierre-Emmanuel Aguera (INSERM)
*/
class CDriverCtfVsmMeg final : public IDriver
{
public:
typedef char str32[32];
typedef char str100[100];
explicit CDriverCtfVsmMeg(IDriverContext& ctx);
~CDriverCtfVsmMeg() override { }
const char* getName() override { return "CTF/VSM MEG"; }
bool isFlagSet(const EDriverFlag flag) const override { return flag == EDriverFlag::IsUnstable; }
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
bool uninitialize() override;
bool start() override;
bool stop() override;
bool loop() override;
bool isConfigurable() override { return true; }
bool configure() override;
const IHeader* getHeader() override { return &m_header; }
Socket::IConnectionClient* m_connectionClient = nullptr;
CString m_sServerHostName = "localhost";
uint32_t m_serverHostPort = 9999;
struct
{
int nbCharExperimentId;
str100 experimentId;
int nbCharExperimentDate;
str32 experimentDate;
int nbCharSubjectName;
str32 subjectName;
int subjectAge;
char subjectGender[1]; /**F: female or M: Male*/
int labId;
str32 labName;
int technicianId;
str32 technicianName;
float samplingRate;
int numberOfChannels;
str32 channelLabel[NB_CHAN_RECORDED_MAX];
int channelTypeIndex[NB_CHAN_RECORDED_MAX];
float properGain[NB_CHAN_RECORDED_MAX];
float qGain[NB_CHAN_RECORDED_MAX];
float ioGain[NB_CHAN_RECORDED_MAX];
int numberOfCoils[NB_CHAN_RECORDED_MAX];
int gradOrderNum[NB_CHAN_RECORDED_MAX];
} m_structHeader;
char* m_pStructHeader = nullptr;
struct
{
int sampleNumber;
int nbSamplesPerChanPerBlock;
int nbSamplesTotPerBlock;
signed int data[NB_SAMP_ACQ_Packet];
} m_structBuffData;
char* m_pStructBuffData = nullptr;
protected:
SettingsHelper m_settings;
IDriverCallback* m_callback = nullptr;
CHeader m_header;
uint32_t m_nSamplePerSentBlock = 0;
float* m_sample = nullptr;
uint32_t m_indexIn = 0;
uint32_t m_indexOut = 0;
uint32_t m_socketFlag = 0;
uint32_t m_buffDataIdx = 0;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,165 @@
#if defined(TARGET_HAS_ThirdPartyEEGOAPI)
#include <bitset>
#include <sstream>
#include "ovasCConfigurationEEGO.h"
namespace OpenViBE {
namespace AcquisitionServer {
// Function to set a predefined string in the combobox.
// Copied from ovasCConfigurationBuilder. Seems to be OK, albeit it is strange to have the code duplication,
// but other amplifier drivers do it too.
// Would be nicer if the method would move to some utilities provider.
static void GTKComboBoxSetActiveText(GtkComboBox* box, const gchar* text)
{
GtkTreeModel* treeModel = gtk_combo_box_get_model(box);
GtkTreeIter it;
int index = 0;
gchar* name = nullptr;
if (gtk_tree_model_get_iter_first(treeModel, &it))
{
do
{
gtk_tree_model_get(treeModel, &it, 0, &name, -1);
if (std::string(name) == std::string(text))
{
gtk_combo_box_set_active(box, index);
return;
}
index++;
} while (gtk_tree_model_iter_next(treeModel, &it));
}
}
// If you added more reference attribute, initialize them here
CConfigurationEEGO::CConfigurationEEGO(IDriverContext& ctx, const char* gtkBuilderFilename, CHeaderEEGO& eegoHeader)
: CConfigurationBuilder(gtkBuilderFilename), m_driverCtx(ctx), m_eegoHeader(eegoHeader) {}
bool CConfigurationEEGO::preConfigure()
{
if (!CConfigurationBuilder::preConfigure()) { return false; }
// Insert here the pre-configure code.
// For example, you may want to check if a device is currently connected
// and if more than one are connected. Then you can list in a dedicated combo-box
// the device currently connected so the user can choose which one he wants to acquire from.
// Steffen Heimes: This is actually a TODO:
GtkWidget* widget = GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_signal_range_eeg"));
m_eegRangeComboBox = GTK_COMBO_BOX(widget);
widget = GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_signal_range_bip"));
m_bipRangeComboBox = GTK_COMBO_BOX(widget);
widget = GTK_WIDGET(gtk_builder_get_object(m_builder, "entry_eeg_mask"));
m_eegEntryMask = GTK_ENTRY(widget);
g_signal_connect(widget, "changed", G_CALLBACK(updateChannelNumCB), this);
widget = GTK_WIDGET(gtk_builder_get_object(m_builder, "entry_bip_mask"));
m_bipEntryMask = GTK_ENTRY(widget);
g_signal_connect(widget, "changed", G_CALLBACK(updateChannelNumCB), this);
widget = GTK_WIDGET(gtk_builder_get_object(m_builder, "entry_num_channels"));
m_nChannelEntry = GTK_ENTRY(widget);
if (!m_eegRangeComboBox) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not connect to range widget \n"; }
else
{
if (m_eegoHeader.isEEGRangeSet())
{
const std::string range = std::to_string(m_eegoHeader.getEEGRange());
GTKComboBoxSetActiveText(m_eegRangeComboBox, range.c_str());
}
else { gtk_combo_box_set_active(m_eegRangeComboBox, 0); }
if (m_eegoHeader.isBIPRangeSet())
{
const std::string range = std::to_string(m_eegoHeader.getBIPRange());
GTKComboBoxSetActiveText(m_bipRangeComboBox, range.c_str());
}
else { gtk_combo_box_set_active(m_bipRangeComboBox, 0); }
}
if (m_eegoHeader.isBIPMaskSet()) { gtk_entry_set_text(m_bipEntryMask, m_eegoHeader.getBIPMask()); }
if (m_eegoHeader.isEEGMaskSet()) { gtk_entry_set_text(m_eegEntryMask, m_eegoHeader.getEEGMask()); }
return true;
}
bool CConfigurationEEGO::postConfigure()
{
if (m_applyConfig)
{
const gchar* rangeEEG = gtk_combo_box_get_active_text(m_eegRangeComboBox);
const gchar* rangeBip = gtk_combo_box_get_active_text(m_bipRangeComboBox);
const gchar* maskBip = gtk_entry_get_text(m_bipEntryMask);
const gchar* maskEEG = gtk_entry_get_text(m_eegEntryMask);
m_eegoHeader.setBIPMask(maskBip);
m_eegoHeader.setEEGMask(maskEEG);
m_eegoHeader.setEEGRange(rangeEEG ? atoi(rangeEEG) : 0);
m_eegoHeader.setBIPRange(rangeBip ? atoi(rangeBip) : 0);
}
if (!CConfigurationBuilder::postConfigure()
) { return false; } // normal header is filled (Subject ID, Age, Gender, channels, sampling frequency), ressources are released
// get sum of max active channels. It would be a good idea to use the amplifier connected as a source of the maximum of available channels.
const uint64_t bip = m_eegoHeader.getBIPMaskInt();
const uint64_t eeg = m_eegoHeader.getEEGMaskInt();
const std::bitset<64> bitsetEEG(eeg);
const std::bitset<24> bitsetBip(bip);
m_header->setChannelCount(bitsetEEG.count() + bitsetBip.count() + 2);
// Plus status channels: trigger and sample counter
return true;
}
/// GTK Callbacks
/* static */
void CConfigurationEEGO::updateChannelNumCB(GtkWidget* /*widget*/, CConfigurationEEGO* pThis)
{
// get the values
const gchar* maskBip = gtk_entry_get_text(pThis->m_bipEntryMask);
const gchar* maskEEG = gtk_entry_get_text(pThis->m_eegEntryMask);
uint64_t bip;
uint64_t eeg;
const bool bipSuccess = CHeaderEEGO::convertMask(maskBip, bip);
const bool eegSuccess = CHeaderEEGO::convertMask(maskEEG, eeg);
const std::bitset<64> bitsetEEG(eeg);
const std::bitset<24> bitsetBip(bip);
// format them
std::stringstream ss;
if (eegSuccess) { ss << bitsetEEG.count(); }
else { ss << "Error"; }
ss << " + ";
if (bipSuccess) { ss << bitsetBip.count(); }
else { ss << "Error"; }
ss << " + 2; (EEG + BIP + STATUS)";
// set text
gtk_entry_set_text(pThis->m_nChannelEntry, ss.str().c_str());
const uint32_t nChannel = bitsetEEG.count() + bitsetBip.count() + 2;
pThis->m_header->setChannelCount(nChannel);
// Workaround! The current channel number is not derived from the channel count. It is retrieved from the /here hidden/
// widget when the channel editing window is opening. Thus we have to set the value there too.
if (GTK_SPIN_BUTTON(pThis->m_nChannels)) { gtk_spin_button_set_value(GTK_SPIN_BUTTON(pThis->m_nChannels), nChannel); }
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyEEGOAPI
@@ -0,0 +1,51 @@
#pragma once
#if defined(TARGET_HAS_ThirdPartyEEGOAPI)
#include <ovasIDriver.h>
#include <../ovasCConfigurationBuilder.h>
#include <gtk/gtk.h>
#include "ovasCHeaderEEGO.h"
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CConfigurationEEGO
* \author Steffen Heimes (Eemagine GmbH)
* \date Fri May 27 21:48:42 2011
* \brief The CConfigurationEEGO handles the configuration dialog for setting specific for EEGO.
* \sa CDriverEEGO
*/
class CConfigurationEEGO final : public CConfigurationBuilder
{
public:
CConfigurationEEGO(IDriverContext& ctx, const char* gtkBuilderFilename, CHeaderEEGO& eegoHeader);
bool preConfigure() override;
bool postConfigure() override;
// Data
protected:
IDriverContext& m_driverCtx;
// Methods
private:
static void updateChannelNumCB(GtkWidget* widget, CConfigurationEEGO* pThis);
// Data
CHeaderEEGO& m_eegoHeader;
GtkComboBox* m_eegRangeComboBox = nullptr;
GtkComboBox* m_bipRangeComboBox = nullptr;
GtkEntry* m_eegEntryMask = nullptr;
GtkEntry* m_bipEntryMask = nullptr;
GtkEntry* m_nChannelEntry = nullptr;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif
@@ -0,0 +1,471 @@
#if defined(TARGET_HAS_ThirdPartyEEGOAPI)
// stl includes
#include <algorithm>
#include <bitset>
#include <exception>
#include <memory>
#if defined TARGET_OS_Linux || defined TARGET_OS_MacOS
#include <cstddef>
#include <type_traits>
#include <utility>
#endif
// OV includes
#include <toolkit/ovtk_all.h>
#include <system/ovCTime.h>
// auxilliary classes for this implementation
#include "ovasCDriverEEGO.h"
#include "ovasCConfigurationEEGO.h"
// The interface to the driver
// We have to setup the binding method and the unicode support first
#define _UNICODE
#if defined(_MBCS) // Only unicode should be supported. Better stay away from MBCS methods!
#undef _MBCS
#endif
#include <eemagine/sdk/factory.h> // Where it all starts
// Namespaces
namespace OpenViBE {
namespace AcquisitionServer {
namespace es = eemagine::sdk;
#if defined TARGET_OS_Linux || defined TARGET_OS_MacOS
namespace std {
template<class T> struct _Unique_if {
typedef unique_ptr<T> _Single_object;
};
template<class T> struct _Unique_if<T[]> {
typedef unique_ptr<T[]> _Unknown_bound;
};
template<class T, size_t N> struct _Unique_if<T[N]> {
typedef void _Known_bound;
};
template<class T, class... Args>
typename _Unique_if<T>::_Single_object
make_unique(Args&&... args) {
return unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template<class T>
typename _Unique_if<T>::_Unknown_bound
make_unique(size_t n) {
typedef typename remove_extent<T>::type U;
return unique_ptr<T>(new U[n]());
}
template<class T, class... Args>
typename _Unique_if<T>::_Known_bound
make_unique(Args&&...) = delete;
}
#endif
//___________________________________________________________________//
// //
CDriverEEGO::CDriverEEGO(IDriverContext& ctx)
: IDriver(ctx)
, m_settings("AcquisitionServer_Driver_EEGO", m_driverCtx.getConfigurationManager()), m_triggerChannel(-1) // == Nonexistent
, m_lastTriggerValue(~0) // Set every bit to 1
, m_iBIPRange(1500)
, m_iEEGRange(1000)
, m_sEEGMask("0xFFFFFFFFFFFFFFFF") // 64 channels
, m_sBIPMask("0xFFFFFF") // 24 channels
{
m_header.setSamplingFrequency(500);
m_header.setChannelCount(88);
// The following class allows saving and loading driver settings from the acquisition server .conf file
m_settings.add("Header", &m_header);
// To save your custom driver settings, register each variable to the SettingsHelper
//m_settings.add("SettingName", &variable);
m_settings.add("BIPRange", &m_iBIPRange);
m_settings.add("EEGRange", &m_iEEGRange);
m_settings.add("EEGMask", &m_sEEGMask);
m_settings.add("BIPMask", &m_sBIPMask);
m_settings.load();
}
CDriverEEGO::~CDriverEEGO() {}
const char* CDriverEEGO::getName() { return "EEGO"; }
bool CDriverEEGO::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
{
if (m_driverCtx.isConnected()
|| !m_header.isChannelCountSet()
|| !m_header.isSamplingFrequencySet()) { return false; }
try
{
// Builds up a buffer to store
// acquired samples. This buffer
// will be sent to the acquisition
// server later...
m_sample = std::make_unique<float[]>(m_header.getChannelCount() * nSamplePerSentBlock);
m_samplesInBuffer = 0;
// Get the amplifier. If none is connected an exception will be thrown
try { m_pAmplifier.reset(factory().getAmplifier()); }
catch (const std::exception& ex)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Failure to get an amplifier! Reason: " << ex.what() << "\n";
throw;
}
if (m_driverCtx.isImpedanceCheckRequested())
{
// end streaming first, if started
m_pStream.reset();
// After init we are in impedance mode until the recording is started
m_pStream.reset(m_pAmplifier->OpenImpedanceStream(getRefChannelMask()));
}
}
catch (const std::exception& ex)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to initialize the driver. Exception: " << ex.what() << "\n";
m_sample.reset();
m_pAmplifier.reset();
m_pStream.reset();
return false;
}
// Save parameters
m_callback = &callback;
m_nSamplePerSentBlock = nSamplePerSentBlock;
return true;
}
bool CDriverEEGO::check_configuration()
{
// get masks from configuration
const uint64_t maskBip = getBipChannelMask();
const uint64_t maskEEG = getRefChannelMask();
const std::bitset<64> bitsetEEG(maskEEG);
const std::bitset<24> bitsetBip(maskBip);
const size_t allChannels = bitsetBip.count() + bitsetEEG.count() + 2; // trigger and sample count as additional channels
if (allChannels < m_header.getChannelCount())
{
// Not enough channels, we have to reduce them
GtkWidget* dialog = gtk_message_dialog_new(nullptr, // parent
GTK_DIALOG_MODAL, // Behavoir
GTK_MESSAGE_QUESTION, // Type
GTK_BUTTONS_OK_CANCEL, // buttons
"The channels masks are set to only stream %ld channels, but %d channels should be streamed.\n"
"Change the amount of channels to %ld?", allChannels, m_header.getChannelCount(), allChannels);
const gint res = gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
dialog = nullptr;
switch (res)
{
case GTK_RESPONSE_OK:
// update the channel count to contain only the selected masks
m_header.setChannelCount(allChannels);
break;
default:
// Nothing can be done here
return false;
}
}
return true;
}
uint64_t CDriverEEGO::getRefChannelMask() const
{
uint64_t maskEEG(0);
if (!CHeaderEEGO::convertMask(m_sEEGMask, maskEEG))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Error converting mask: m_sEEGMask: " << m_sEEGMask << "\n";
}
return maskEEG;
}
uint64_t CDriverEEGO::getBipChannelMask() const
{
uint64_t maskBip(0);
if (!CHeaderEEGO::convertMask(m_sBIPMask, maskBip))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Error converting mask: maskBip: " << maskBip << "\n";
}
return maskBip;
}
eemagine::sdk::factory& CDriverEEGO::factory()
{
if (m_pFactory == nullptr)
{
// create the amplifier factory
// To initialize we need to locate the path of the DLL
// Create path to the dll
#ifdef _WIN32
const CString libDir = Directories::getBinDir() + "\\eego-SDK.dll";
const std::string path(libDir.toASCIIString());
#else
const std::string path("libeego-SDK.so");
#endif // _WIN32
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "SDK dll/so path: " << path << "\n";
m_pFactory = std::make_unique<es::factory>(path);
// to check what is going on case of error; Log version
const auto version = m_pFactory->getVersion();
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "EEGO SDK Version: " << version.major << "." << version.minor << "." << version.micro << "." <<
version.build << "\n";
}
return *m_pFactory;
}
bool CDriverEEGO::start()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted() || !m_pAmplifier) { return false; }
// Check configuration
if (!check_configuration()) { return false; }
// ...
// request hardware to start
// sending data
// ..
const double bipRange = m_iBIPRange / 1000.;
const double eegRange = m_iEEGRange / 1000.;
try
{
// stop old streams, if existing
m_pStream.reset();
// Create the eeg stream
m_pStream.reset(m_pAmplifier->OpenEegStream(m_header.getSamplingFrequency(), eegRange, bipRange, getRefChannelMask(), getBipChannelMask()));
// Error check
if (!m_pStream)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "The stream returned is NULL!" << "\n";
return false;
}
// find trigger channel
auto list = m_pStream->getChannelList();
const auto triggerIterator = std::find_if(list.begin(), list.end(), [](eemagine::sdk::channel& chan)
{
return chan.getType() == eemagine::sdk::channel::trigger;
});
if (triggerIterator == list.end())
{
m_triggerChannel = -1; // Unkown
}
else { m_triggerChannel = (*triggerIterator).getIndex(); }
// Wait till we are really getting data.
while (m_pStream->getData().getSampleCount() == 0)
{
System::Time::sleep(5); // Do Nothing
}
}
catch (const std::exception& ex)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not open EEG stream: " << ex.what() << "\n";
return false;
}
return true;
}
bool CDriverEEGO::loop()
{
bool result = false;
try { result = loop_wrapped(); }
catch (const std::exception& ex) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error in data update: " << ex.what() << "\n"; }
catch (...) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unknown error in data update." << "\n"; }
return result;
}
bool CDriverEEGO::loop_wrapped()
{
if (!m_driverCtx.isConnected()) { return false; }
if (!m_driverCtx.isStarted() && !m_driverCtx.isImpedanceCheckRequested()) { return true; } // Nothing to be done here!
if (!m_pStream) { return false; }
// Check if we really provide enough channels
// When doing impedance only the normal EEG channels are tested. This is fine and handled.
if (m_pStream->getChannelList().size() < m_header.getChannelCount() && m_driverCtx.isStarted()) // !started -> impedance
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "The amplifier got asked for more channels than it could provide" << "\n";
return false;
}
if (m_driverCtx.isStarted()) // Normal operation
{
eemagine::sdk::buffer data;
try { data = m_pStream->getData(); }
catch (const std::exception& ex)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error fetching data: " << ex.what() << "\n";
return false;
}
const size_t nSample = data.getSampleCount();
// For EEGO the with every index increment, the channel is incremented.
// For OpenVibe it means next sample. Therefor we have to transpose the data
for (size_t sample = 0; sample < nSample; ++sample)
{
for (size_t channel = 0; channel < m_header.getChannelCount(); ++channel)
{
const int ovIdx = int(m_samplesInBuffer + channel * m_nSamplePerSentBlock);
const double& sampleVal = data.getSample(channel, sample);
m_sample[ovIdx] = float(sampleVal);
}
// Add potential triggers to stimulation set
// check for triggers
if (m_triggerChannel >= 0) // Only try to find triggers when the channel exists
{
// A trigger is detected when the level changes in positive direction, all additional bits are seen as trigger code
// a change from 1 to 0 is ignored
const uint32_t currentTriggers = uint32_t(data.getSample(m_triggerChannel, sample));
const uint32_t currentNewTriggers = currentTriggers & ~m_lastTriggerValue;
m_lastTriggerValue = currentTriggers;
if (currentNewTriggers != 0)
{
const uint64_t currentTime = CTime(m_header.getSamplingFrequency(), m_samplesInBuffer).time();
m_stimSet.appendStimulation(OVTK_StimulationId_Label(currentNewTriggers), currentTime, 0);
}
}
// Send buffer counter
m_samplesInBuffer++;
// Send buffer is full, so send it
if (m_samplesInBuffer == m_nSamplePerSentBlock)
{
m_callback->setSamples(m_sample.get());
m_callback->setStimulationSet(m_stimSet);
// When your sample buffer is fully loaded,
// it is advised to ask the acquisition server
// to correct any drift in the acquisition automatically.
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
m_samplesInBuffer = 0;
m_stimSet.clear();
}
}
}
else // Impedance
{
// Get the impedance data, here the data is always the most current state.
// The method can block if impedance still needs to be calculated.
eemagine::sdk::buffer data;
try { data = m_pStream->getData(); }
catch (const std::exception& ex)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error fetching data: " << ex.what() << "\n";
return false;
}
// We have to take care not to r/w over any boundary.
const size_t minChannels = std::min(size_t(data.getChannelCount()), m_header.getChannelCount());
for (size_t channel = 0; channel < minChannels; ++channel) { m_driverCtx.updateImpedance(channel, data.getSample(channel, 0)); }
}
return true;
}
bool CDriverEEGO::stop()
{
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
// ...
// request the hardware to stop
// sending data
// ...
if (m_driverCtx.isImpedanceCheckRequested())
{
try
{
m_pStream.reset();
m_pStream.reset(m_pAmplifier->OpenImpedanceStream(getRefChannelMask())); // And we can stream Impedances once more.
}
catch (const std::exception& ex)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error restarting impedance: " << ex.what() << "\n";
return false;
}
}
return true;
}
bool CDriverEEGO::uninitialize()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// ...
// uninitialize hardware here
// ...
m_pStream.reset();
m_pAmplifier.reset();
m_sample.reset();
m_callback = nullptr;
m_samplesInBuffer = 0;
return true;
}
//___________________________________________________________________//
// //
bool CDriverEEGO::isConfigurable()
{
return true; // change to false if your device is not configurable
}
bool CDriverEEGO::configure()
{
// Change this line if you need to specify some references to your driver attribute that need configuration, e.g. the connection ID.
CConfigurationEEGO config(m_driverCtx,
Directories::getDataDir() + "/applications/acquisition-server/interface-EEGO.ui",
m_header);
m_header.setBIPRange(m_iBIPRange);
m_header.setEEGRange(m_iEEGRange);
m_header.setBIPMask(m_sBIPMask);
m_header.setEEGMask(m_sEEGMask);
if (!config.configure(m_header)) { return false; }
m_iBIPRange = m_header.getBIPRange();
m_iEEGRange = m_header.getEEGRange();
m_sBIPMask = m_header.getBIPMask();
m_sEEGMask = m_header.getEEGMask();
m_settings.save();
return true;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif
@@ -0,0 +1,99 @@
#pragma once
#if defined(TARGET_HAS_ThirdPartyEEGOAPI)
#include <memory>
#include "ovasIDriver.h"
#include "ovasCHeaderEEGO.h"
#include <openvibe/ov_all.h>
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
// forward declarations
namespace eemagine {
namespace sdk {
class amplifier;
class stream;
class factory;
}
}
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CDriverEEGO
* \author Steffen Heimes (eemagine GmbH)
* \date Mon Oct 20 14:40:33 2014
* \brief The CDriverEEGO allows the acquisition server to acquire data from an EEGO device.
*
* \sa CConfigurationEEGO
*/
class CDriverEEGO final : public IDriver
{
public:
explicit CDriverEEGO(IDriverContext& ctx);
~CDriverEEGO() override;
const char* getName() override;
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
bool uninitialize() override;
bool start() override;
bool stop() override;
bool loop() override;
bool isConfigurable() override;
bool configure() override;
const IHeader* getHeader() override { return &m_header; }
bool isFlagSet(const EDriverFlag /*flag*/) const
override { return false; } // The only currently used flag is for checking for unstability. eego is stable now.
private:
bool loop_wrapped();
/**
* Check if the configuration makes sense and tries to fix it, informing the user.
*/
bool check_configuration();
uint64_t getRefChannelMask() const;
uint64_t getBipChannelMask() const;
eemagine::sdk::factory& factory();
protected:
SettingsHelper m_settings;
IDriverCallback* m_callback = nullptr;
CHeaderEEGO m_header;
uint32_t m_nSamplePerSentBlock = 0;
std::unique_ptr<float[]> m_sample;
std::unique_ptr<eemagine::sdk::factory> m_pFactory;
std::unique_ptr<eemagine::sdk::amplifier> m_pAmplifier;
std::unique_ptr<eemagine::sdk::stream> m_pStream;
private:
uint32_t m_samplesInBuffer = 0;
uint32_t m_triggerChannel = 0;
CStimulationSet m_stimSet; // Storing the samples over time
// To detect flanks in the trigger signal. The last state on the trigger input.
uint32_t m_lastTriggerValue = 0;
// For setting store/load
uint32_t m_iBIPRange = 0; // [mV]
uint32_t m_iEEGRange = 0; // [mV]
CString m_sEEGMask; // String interpreted as value to be interpreted as bitfield
CString m_sBIPMask; // String interpreted as value to be interpreted as bitfield
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif
@@ -0,0 +1,94 @@
#if defined TARGET_HAS_ThirdPartyEEGOAPI
#include <algorithm>
#include <locale>
#include <boost/algorithm/string.hpp>
#include "ovasCHeaderEEGO.h"
namespace OpenViBE {
namespace AcquisitionServer {
void CHeaderEEGO::setEEGRange(const uint32_t range)
{
m_iEEGRange = range;
m_bEEGRangeSet = true;
}
void CHeaderEEGO::setEEGMask(const CString& mask)
{
m_sEEGMask = mask;
m_bEEGMaskSet = true;
}
void CHeaderEEGO::setBIPRange(const uint32_t range)
{
m_iBIPRange = range;
m_bBIPRangeSet = true;
}
void CHeaderEEGO::setBIPMask(const CString& mask)
{
m_sBIPMask = mask;
m_bBIPMaskSet = true;
}
/* static */
bool CHeaderEEGO::convertMask(char const* str, uint64_t& out)
{
bool error = false;
// init r_outValue anyway
out = 0;
std::string input(str); //easier substring handling etc. Minor performance penalty which should not matter.
boost::algorithm::trim(input); // Make sure to handle whitespace correctly
// check prefixes
if (boost::algorithm::istarts_with(input, "0b"))
{
// binary
const auto substring = input.substr(2);
// check for valid string members
if (!std::all_of(substring.begin(), substring.end(), [&](const char& chr) { return chr == '0' || chr == '1'; })) { error = true; }
else
{
// use the substring for string to number conversion as base 2
out = strtoull(substring.c_str(), nullptr, 2);
}
}
else if (boost::algorithm::istarts_with(input, "0x"))
{
// hex
const auto substring = input.substr(2);
std::locale loc;
if (!std::all_of(substring.begin(), substring.end(), [&](const char& chr) { return std::isxdigit(chr, loc); })) { error = true; }
else { out = strtoull(substring.c_str(), nullptr, 16); }
}
else if (boost::algorithm::istarts_with(input, "0"))
{
// octal
const auto substring = input.substr(1);
if (!std::all_of(substring.begin(), substring.end(), [&](const char& chr) { return chr >= '0' && chr < '8'; })) { error = true; }
else { out = strtoull(substring.c_str(), nullptr, 8); }
}
else
{
// decimal
//const auto substring = input;
std::locale loc;
if (!std::all_of(input.begin(), input.end(), [&](const char& chr) { return std::isdigit(chr, loc); })) { error = true; }
else { out = strtoull(input.c_str(), nullptr, 10); }
}
// if no special handling for the base 2 case is neccessary we can just use the std::stroull implementation and do not mess with that any further.
return !error;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif
@@ -0,0 +1,63 @@
#pragma once
#if defined TARGET_HAS_ThirdPartyEEGOAPI
#include "../ovasCHeader.h"
namespace OpenViBE {
namespace AcquisitionServer {
class CHeaderEEGO final : public CHeader
{
public:
CHeaderEEGO() { }
// EEG, referential channels
// range
uint32_t getEEGRange() const { return m_iEEGRange; }
void setEEGRange(uint32_t range);
bool isEEGRangeSet() const { return m_bEEGRangeSet; }
// mask
CString getEEGMask() const { return m_sEEGMask; }
uint64_t getEEGMaskInt() const { return strtoull(m_sEEGMask, nullptr, 0); } // Same as method above. Only string parsing has been done
void setEEGMask(const CString& mask);
bool isEEGMaskSet() const { return m_bEEGMaskSet; }
// Bipolar channels
// range
uint32_t getBIPRange() const { return m_iBIPRange; }
uint64_t getBIPMaskInt() const { return strtoull(m_sBIPMask, nullptr, 0); } // Same as method above. Only string parsing has been done
void setBIPRange(uint32_t range);
bool isBIPRangeSet() const { return m_bBIPRangeSet; }
// mask
CString getBIPMask() const { return m_sBIPMask; }
void setBIPMask(const CString& mask);
bool isBIPMaskSet() const { return m_bBIPMaskSet; }
// Converts a string representing a number to this number as unsigned 64 bit value.
// Accepts 0x, 0b and 0 notation for hexadecimal, binary and octal notation.
// Otherwise it is interpreted as decimal.
// Returns true if the conversion was successfull, false on error.
// Please note that the error checking goes beyond the parsing strtoull etc.:
// The strto* methods stop parsing at the first character which could not be interpreted.
// Here the string is checked against all invalid chars and an error will be returned.
static bool convertMask(char const* str, uint64_t& out);
// data
protected:
uint32_t m_iEEGRange = 1000;
uint32_t m_iBIPRange = 1500;
CString m_sEEGMask = "0xFFFFFFFFFFFFFFFF";
CString m_sBIPMask = "0xFFFFFF";
bool m_bEEGRangeSet = false;
bool m_bBIPRangeSet = false;
bool m_bEEGMaskSet = false;
bool m_bBIPMaskSet = false;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif
@@ -0,0 +1,380 @@
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 2.6 -->
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkAdjustment" id="adjustment1">
<property name="value">18</property>
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment3">
<property name="value">64</property>
<property name="lower">1</property>
<property name="upper">64</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment4">
<property name="value">120</property>
<property name="lower">1</property>
<property name="upper">65536</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkListStore" id="model1">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">unspecified</col>
</row>
<row>
<col id="0" translatable="yes">female</col>
</row>
<row>
<col id="0" translatable="yes">male</col>
</row>
<row>
<col id="0" translatable="yes">unknown</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model2">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">250</col>
</row>
</data>
</object>
<object class="GtkDialog" id="openvibe-acquisition-server-settings">
<property name="border_width">5</property>
<property name="title" translatable="yes">Device configuration</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel" id="label_title">
<property name="visible">True</property>
<property name="label" translatable="yes">Encephalan</property>
<property name="justify">center</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox">
<property name="visible">True</property>
<property name="border_width">8</property>
<property name="orientation">vertical</property>
<property name="spacing">8</property>
<child>
<object class="GtkHBox" id="hbox">
<property name="visible">True</property>
<property name="spacing">8</property>
<child>
<object class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="n_rows">7</property>
<property name="n_columns">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkComboBox" id="combobox_gender">
<property name="visible">True</property>
<property name="model">model1</property>
<child>
<object class="GtkCellRendererText" id="renderer1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_age">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_identifier">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment2</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_sampling_frequency">
<property name="visible">True</property>
<property name="label" translatable="yes">Sampling frequency :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_number_of_channels">
<property name="visible">True</property>
<property name="label" translatable="yes">Number of channels :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_gender">
<property name="visible">True</property>
<property name="label" translatable="yes">Gender :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_age">
<property name="visible">True</property>
<property name="label" translatable="yes">Age :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_identifier">
<property name="visible">True</property>
<property name="label" translatable="yes">Identifier :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
</child>
<child>
<object class="GtkComboBox" id="combobox_sampling_frequency">
<property name="visible">True</property>
<property name="model">model2</property>
<property name="active">0</property>
<child>
<object class="GtkCellRendererText" id="renderer2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_number_of_channels">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment3</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_connection_ip">
<property name="visible">True</property>
<property name="label" translatable="yes">Connection IP :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_connection_port">
<property name="visible">True</property>
<property name="label" translatable="yes">Connection Port :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_port">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment4</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry_ip">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">30</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_change_channel_names">
<property name="label" translatable="yes">Change channel names</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">4</property>
</packing>
</child>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_apply">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-10">button_apply</action-widget>
<action-widget response="-6">button_cancel</action-widget>
</action-widgets>
</object>
</interface>
@@ -0,0 +1,42 @@
#include "ovasCConfigurationEncephalan.h"
#include <gtk/gtk.h>
namespace OpenViBE {
namespace AcquisitionServer {
//---------------------------------------------------------------------------------------------------
CConfigurationEncephalan::CConfigurationEncephalan(IDriverContext& driverContext, const char* gtkFileName, uint32_t& connectionPort, char* connectionIp)
: CConfigurationBuilder(gtkFileName), m_driverContext(driverContext), m_connectionPort(connectionPort), m_connectionIp(connectionIp) { }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CConfigurationEncephalan::preConfigure()
{
if (!CConfigurationBuilder::preConfigure()) { return false; }
GtkSpinButton* port = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_port"));
gtk_spin_button_set_value(port, m_connectionPort);
GtkEntry* ip = GTK_ENTRY(gtk_builder_get_object(m_builder, "entry_ip"));
gtk_entry_set_text(ip, m_connectionIp);
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CConfigurationEncephalan::postConfigure()
{
if (m_applyConfig)
{
GtkSpinButton* port = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_port"));
m_connectionPort = uint32_t(gtk_spin_button_get_value_as_int(port));
GtkEntry* ip = GTK_ENTRY(gtk_builder_get_object(m_builder, "entry_ip"));
m_connectionIp = const_cast<char*>(gtk_entry_get_text(ip));
}
return CConfigurationBuilder::postConfigure();
}
//---------------------------------------------------------------------------------------------------
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,36 @@
///-------------------------------------------------------------------------------------------------
///
/// \file ovasCConfigurationEncephalan.h
/// \brief The CConfigurationEncephalan handles the configuration dialog specific to the Encephalan device.
/// \author Alexey Minin (UrFU)
/// \version 1.0.
/// \date 02/01/2019.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "../ovasCConfigurationBuilder.h"
#include "ovasIDriver.h"
namespace OpenViBE {
namespace AcquisitionServer {
/// <summary> The CConfigurationEncephalan handles the configuration dialog specific to the Encephalan device. </summary>
/// <seealso cref="CDriverEncephalan" />
class CConfigurationEncephalan final : public CConfigurationBuilder
{
public:
CConfigurationEncephalan(IDriverContext& driverContext, const char* gtkFileName, uint32_t& connectionPort, char* connectionIp);
bool preConfigure() override;
bool postConfigure() override;
char* getConnectionIp() const { return m_connectionIp; }
protected:
IDriverContext& m_driverContext;
uint32_t& m_connectionPort;
char* m_connectionIp = nullptr;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,274 @@
#if defined TARGET_OS_Windows
#include "ovasCDriverEncephalan.h"
#include "ovasCConfigurationEncephalan.h"
namespace OpenViBE {
namespace AcquisitionServer {
//---------------------------------------------------------------------------------------------------
CDriverEncephalan::CDriverEncephalan(IDriverContext& driverContext)
: IDriver(driverContext), m_settings("AcquisitionServer_Driver_Encephalan", m_driverCtx.getConfigurationManager())
{
m_connectionIp = static_cast<char*>("127.0.0.1");
m_header.setSamplingFrequency(250);
m_header.setChannelCount(8);
m_settings.add("Header", &m_header);
m_settings.load();
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CDriverEncephalan::initialize(const uint32_t sampleCountPerBlock, IDriverCallback& callback)
{
if (m_driverCtx.isConnected() || !m_header.isChannelCountSet() || !m_header.isSamplingFrequencySet()) { return false; }
m_sample = new float[m_header.getChannelCount() * sampleCountPerBlock];
if (!m_sample)
{
delete[] m_sample;
m_sample = nullptr;
return false;
}
// Saves parameters
m_callback = &callback;
m_nSamplePerBlock = sampleCountPerBlock;
return connectEncephalan();
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CDriverEncephalan::start()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// request hardware to start
return sendRequestForData();
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CDriverEncephalan::loop()
{
if (!m_driverCtx.isConnected()) { return false; }
if (!m_driverCtx.isStarted()) { return true; }
m_currentPoint = 0;
ZeroMemory(m_sample, sizeof(float)*m_nSamplePerBlock*m_header.getChannelCount());
while (m_currentPoint < m_nSamplePerBlock) { receiveData(); }
m_callback->setSamples(m_sample);
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CDriverEncephalan::stop() { return m_driverCtx.isConnected() && m_driverCtx.isStarted(); }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CDriverEncephalan::uninitialize()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
delete[] m_sample;
m_sample = nullptr;
m_callback = nullptr;
closesocket(m_client);
WSACleanup();
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CDriverEncephalan::connectEncephalan()
{
WSADATA wsaData;
const int wsaret = WSAStartup(0x101, &wsaData);
if (wsaret != 0)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error WSAStartup (initialization windows socket api): " << WSAGetLastError() << "\n";
return false;
}
m_client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (m_client == INVALID_SOCKET)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error socket (creation a socket): " << WSAGetLastError() << "\n";
WSACleanup();
return false;
}
sockaddr_in sockaddrIn;
sockaddrIn.sin_family = AF_INET;
sockaddrIn.sin_addr.s_addr = inet_addr(m_connectionIp);
sockaddrIn.sin_port = htons(u_short(m_connectionPort));
const int connectError = connect(m_client, reinterpret_cast<sockaddr*>(&sockaddrIn), sizeof(sockaddrIn));
if (connectError != 0)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error connect (connection to a specified socket): " << WSAGetLastError() << "\n";
return false;
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CDriverEncephalan::sendRequestForData() const
{
BYTE* sendDataBuffer = new BYTE;
unsigned long ID = 0x0001; //EEG_ONLY_MODE
//unsigned long ID = 0x000C; //ALL_CHANNELS_MODE
//unsigned long ID = 0x000E; //HD_CHANNELS_MODE
CopyMemory(sendDataBuffer, &ID, sizeof(ID));
if (!sendData(sendDataBuffer, sizeof(ID)))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error sendRequestForData: " << WSAGetLastError() << "\n";
return false;
}
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "sendRequestForData\n";
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CDriverEncephalan::sendData(BYTE* data, int dataSize) const
{
if (m_client == INVALID_SOCKET) { return false; }
const long fullLen = dataSize + long(sizeof(int));
char* outData = new char[fullLen];
CopyMemory(&outData[0], &dataSize, sizeof(int));
CopyMemory(&outData[sizeof(int)], &data[0], dataSize);
if (send(m_client, outData, fullLen, 0) != fullLen)
{
delete[] outData;
return false;
}
delete[]outData;
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CDriverEncephalan::receiveData()
{
int inpSize = 0;
char* inpData = new char[sizeof(inpSize) * sizeof(BYTE)];
const int nReadSize = recv(m_client, inpData, sizeof(inpSize), 0);
if (nReadSize <= 0) { return false; }
CopyMemory(&inpSize, &inpData[0], sizeof(inpSize));
delete[]inpData;
if ((inpSize > 0) && (inpSize < 1024 * 512))
{
//unsigned long ID = 0;
inpData = new char[inpSize * sizeof(BYTE)];
if (recv(m_client, inpData, inpSize, 0) == inpSize) { readData(reinterpret_cast<BYTE*>(inpData), inpSize); }
delete[]inpData;
inpData = nullptr;
}
else { return false; }
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
void CDriverEncephalan::readData(BYTE* data, const int dataSize)
{
BYTE* pCurData = data;
int curDataSize = dataSize;
unsigned long ID = 0;
getData(pCurData, curDataSize, &ID, sizeof(ID));
switch (ID)
{
case 0x0002: // Information on research
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Recieved information on research\n";
break;
case 0x00E2: // This prompted all the data including the kilohertz
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Recieved all the data including the kilohertz\n";
break;
case 0x0003: m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "0x0003 data package\n";
break;
case 0x0006: m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "0x0006 data package\n";
break;
case 0x0008: // We came signal data
receiveEEGData(pCurData, curDataSize);
break;
case 0x00E8: // Packet came with HD data
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Packet came with HD data\n";
break;
case 0x0009: m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "0x0009 data package\n";
break;
case 0x000A: m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "0x000A data package\n";
break;
case 0x0101: m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "0x0101 data package\n";
break;
default: m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Undefined data package\n";
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
// Parsing package with EEG data
void CDriverEncephalan::receiveEEGData(BYTE* curData, int curDataSize)
{
int sliceNum = 0;
int pointsNumber = 0;
getData(curData, curDataSize, &sliceNum, sizeof(sliceNum));
getData(curData, curDataSize, &pointsNumber, sizeof(pointsNumber));
short* dstData = new short[pointsNumber];
ZeroMemory(dstData, sizeof(short)*pointsNumber);
getData(curData, curDataSize, dstData, pointsNumber * int(sizeof(short)));
for (size_t iPos = 0; iPos < m_header.getChannelCount(); ++iPos)
{
float val = float(dstData[iPos]);
CopyMemory(m_sample + (m_currentPoint + iPos * m_nSamplePerBlock), &val, sizeof(float));
}
m_currentPoint++;
delete[] dstData;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
void CDriverEncephalan::getData(BYTE* & data, int& dataSize, void* dstData, const int dstSize)
{
if (dataSize < dstSize) { return; }
CopyMemory(dstData, data, dstSize);
data += dstSize;
dataSize -= dstSize;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CDriverEncephalan::configure()
{
// Change this line if you need to specify some references to your driver attribute that need configuration, e.g. the connection ID.
CConfigurationEncephalan configuration(m_driverCtx, Directories::getDataDir() + "/applications/acquisition-server/interface-Encephalan.ui",
m_connectionPort, m_connectionIp);
if (!configuration.configure(m_header)) { return false; }
m_connectionIp = configuration.getConnectionIp();
m_settings.save();
return true;
}
//---------------------------------------------------------------------------------------------------
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_OS_Windows
@@ -0,0 +1,72 @@
///-------------------------------------------------------------------------------------------------
///
/// \file ovasCDriverEncephalan.h
/// \brief The CDriverEncephalan allows the acquisition server to acquire data from a Encephalan device.
/// \author Alexey Minin (UrFU)
/// \version 1.0.
/// \date 02/01/2019.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#if defined TARGET_OS_Windows
#include "ovasIDriver.h"
#include "../ovasCHeader.h"
#include <openvibe/ov_all.h>
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
#include "winsock.h"
namespace OpenViBE {
namespace AcquisitionServer {
/// <summary> The CDriverEncephalan allows the acquisition server to acquire data from a Encephalan device. </summary>
/// <seealso cref="CConfigurationEncephalan" />
class CDriverEncephalan final : public IDriver
{
public:
explicit CDriverEncephalan(IDriverContext& driverContext);
~CDriverEncephalan() override = default;
const char* getName() override { return "Encephalan"; }
bool initialize(const uint32_t sampleCountPerBlock, IDriverCallback& callback) override;
bool uninitialize() override;
bool start() override;
bool stop() override;
bool loop() override;
bool isConfigurable() override { return true; } // change to false if your device is not configurable
bool configure() override;
const IHeader* getHeader() override { return &m_header; }
bool isFlagSet(const EDriverFlag flag) const override { return flag == EDriverFlag::IsUnstable; }
protected:
bool connectEncephalan();
bool sendRequestForData() const;
bool sendData(BYTE* data, int dataSize) const;
bool receiveData();
void readData(BYTE* data, int dataSize);
static void getData(BYTE* & data, int& dataSize, void* dstData, int dstSize);
void receiveEEGData(BYTE* curData, int curDataSize);
SettingsHelper m_settings;
IDriverCallback* m_callback = nullptr;
CHeader m_header;
uint32_t m_nSamplePerBlock = 0;
float* m_sample = nullptr;
char* m_connectionIp = nullptr;
uint32_t m_connectionPort = 120;
uint32_t m_currentPoint = 0;
SOCKET m_client = 0;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_OS_Windows
@@ -0,0 +1,702 @@
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 2.12 -->
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkAdjustment" id="adjustment1">
<property name="value">1025</property>
<property name="upper">49151</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment3">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment4">
<property name="value">1</property>
<property name="lower">1</property>
<property name="upper">1024</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkListStore" id="model1">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">unspecified</col>
</row>
<row>
<col id="0" translatable="yes">female</col>
</row>
<row>
<col id="0" translatable="yes">male</col>
</row>
<row>
<col id="0" translatable="yes">unknown</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model2">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">128</col>
</row>
<row>
<col id="0" translatable="yes">256</col>
</row>
<row>
<col id="0" translatable="yes">512</col>
</row>
<row>
<col id="0" translatable="yes">1024</col>
</row>
<row>
<col id="0" translatable="yes">2048</col>
</row>
<row>
<col id="0" translatable="yes">4096</col>
</row>
</data>
</object>
<object class="GtkDialog" id="openvibe-acquisition-server-settings">
<property name="border_width">5</property>
<property name="title" translatable="yes">Device configuration</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel" id="label_title">
<property name="visible">True</property>
<property name="label" translatable="yes">Fieldtrip</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox">
<property name="visible">True</property>
<property name="border_width">8</property>
<property name="spacing">8</property>
<child>
<object class="GtkHBox" id="hbox">
<property name="visible">True</property>
<property name="spacing">8</property>
<child>
<object class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="n_rows">7</property>
<property name="n_columns">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkSpinButton" id="spinbutton_host_port">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry_host_name">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">15</property>
<property name="invisible_char">&#x2022;</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_host_port">
<property name="visible">True</property>
<property name="label" translatable="yes">Buffer port number</property>
</object>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_host_name">
<property name="visible">True</property>
<property name="label" translatable="yes">Buffer host name</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_identifier">
<property name="visible">True</property>
<property name="label" translatable="yes">Identifier :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label_age">
<property name="visible">True</property>
<property name="label" translatable="yes">Age :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_gender">
<property name="visible">True</property>
<property name="label" translatable="yes">Gender :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_identifier">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment2</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_age">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment3</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_gender">
<property name="visible">True</property>
<property name="model">model1</property>
<child>
<object class="GtkCellRendererText" id="renderer1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_minSamples">
<property name="visible">True</property>
<property name="label" translatable="yes">Min nb of samples in buffer</property>
</object>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_minSamples">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
<property name="adjustment">adjustment5</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_SRCorrection">
<property name="label" translatable="yes">Correct manually drift due to non-integer sampling rate</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="right_attach">2</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">4</property>
</packing>
</child>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_apply">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-10">button_apply</action-widget>
<action-widget response="-6">button_cancel</action-widget>
</action-widgets>
</object>
<object class="GtkWindow" id="dummy-widgets">
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="n_rows">10</property>
<property name="n_columns">10</property>
<child>
<object class="GtkSpinButton" id="spinbutton_number_of_channels">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment4</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_sampling_frequency">
<property name="visible">True</property>
<property name="model">model2</property>
<child>
<object class="GtkCellRendererText" id="renderer2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_change_channel_names">
<property name="label" translatable="yes">Change channel names</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
</object>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
<object class="GtkAdjustment" id="adjustment5">
<property name="value">1</property>
<property name="lower">1</property>
<property name="upper">10000</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
</interface>
@@ -0,0 +1,32 @@
#pragma once
#include "windows.h"
#include "ov_defines.h"
#include "ovCString.h"
//___________________________________________________________________//
// //
// Get Cpu Time //
//___________________________________________________________________//
// //
namespace OpenViBE
{
OV_API bool GetEnvVar( CString& sVar, CString& sValue );
OV_API bool PutEnvVar( CString& sVar, CString& sValue );
OV_API inline double GetCPUTimeInMilliseconds()
{
static bool bGotCPUFreq = false;
static double timerFrequency = 1;
if ( !bGotCPUFreq )
{
unsigned __int64 cpufreq = 1;
QueryPerformanceFrequency((LARGE_INTEGER*)&cpufreq);
timerFrequency = (1000.0/cpufreq);
}
unsigned __int64 curTime = 0;
QueryPerformanceCounter((LARGE_INTEGER *)&curTime);
return timerFrequency * curTime;
};
}
@@ -0,0 +1,128 @@
/*
* Copyright (C) 2008, Robert Oostenveld & Christian Hesse
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
/* prevent double include */
#ifndef BUFFER_H
#define BUFFER_H
#include <cstdlib>
#include <string.h>
#include <stdio.h>
#include "platform_includes.h"
#include "message.h"
#ifndef POLLRDNORM
#define POLLRDNORM POLLIN
#endif
#ifndef POLLRDBAND
#define POLLRDBAND POLLPRI
#endif
#ifndef POLLWRNORM
#define POLLWRNORM POLLOUT
#endif
#ifndef POLLWRBAND
#define POLLWRBAND POLLOUT
#endif
#define BACKLOG 16
#define DEFAULT_HOSTNAME "localhost"
#define DEFAULT_PORT 1972
#define SO_RCVBUF_SIZE 16384
#define SO_SNDBUF_SIZE 16384
/* this is because the function has been renamed, but is perhaps already in use in other software */
#define open_remotehost open_connection
/* FIXME these should be variable */
#define MAXNUMBYTE (512*1024*1024)
#define MAXNUMSAMPLE 600000
#define MAXNUMEVENT 100
#define WRAP(x,y) ((x) - (int(float(x)/(y)))*(y))
#define FREE(x) {if (x) {free(x); x= nullptr;}}
#ifdef __cplusplus
extern "C" {
#endif
/* declaration of "public" buffer API functions */
/* SK: where are these, and what are they for ? */
int read_header( const char *hostname, int port, void **ppw);
int read_data( const char *hostname, int port, int *pnw, void **ppw);
int read_event( const char *hostname, int port, int *pnw, void **ppw);
int write_header(const char *hostname, int port, void **ppw);
int write_data( const char *hostname, int port, int *pnw, void **ppw);
int write_event( const char *hostname, int port, int *pnw, void **ppw);
int flush_header(const char *hostname, int port);
int flush_data( const char *hostname, int port);
int flush_event( const char *hostname, int port);
void cleanup_buffer();
/* definition of the functions that implement the network transparent server */
void *tcpserver(void *);
void *tcpsocket(void *);
/* definition of test functions that emulate an acquisition system */
void *sinewave_thread(void *);
void *event_thread(void *);
/* definition of the functions used in thread cancelation, see cleanup.c */
void cleanup_message(void **arg);
void cleanup_header(void **arg);
void cleanup_data(void **arg);
void cleanup_event(void **arg);
void cleanup_buf(void **arg);
void cleanup_socket(int *);
/* definition of helper functions for debugging and printing the content of various structures */
void print_request(messagedef_t *);
void print_response(messagedef_t *);
void print_headerdef(headerdef_t *);
void print_datadef(datadef_t *);
void print_eventdef(eventdef_t *);
void print_datasel(datasel_t *);
void print_eventsel(eventsel_t *);
void print_buf(void *, int);
/* definition of even more helper functions, see util.c */
int open_connection(const char*, int);
int open_unix_connection(const char *name);
int close_connection(int);
unsigned int append(void **, unsigned int, void *, unsigned int);
unsigned int bufread(int, void *, unsigned int);
unsigned int bufwrite(int, const void *, unsigned int);
int clientrequest(int, const message_t *, message_t**);
int dmarequest(const message_t *, message_t**);
int tcprequest(int, const message_t *, message_t**);
unsigned int wordsize_from_type(UINT32_T data_type);
void check_datatypes();
int check_event_array(unsigned int size, const void *buf);
const ft_chunk_t *find_chunk(const void *buf, unsigned int offset0, unsigned int size, UINT32_T chunk_type);
void ft_swap16(unsigned int numel, void *data);
void ft_swap32(unsigned int numel, void *data);
void ft_swap64(unsigned int numel, void *data);
int ft_swap_buf_to_native(UINT16_T command, UINT32_T bufsize, void *buf);
int ft_convert_chunks_from_native(UINT32_T size, UINT32_T nchans, void *buf);
int ft_swap_from_native(UINT16_T orgCommand, message_t *msg);
typedef struct {
char name[256];
int port;
} host_t;
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2008, Robert Oostenveld
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include <stdlib.h>
#include <stdio.h>
#include "buffer.h"
/* this is used for debugging */
int verbose = 0;
void cleanup_socket(int *arg) {
if (verbose>0) fprintf(stderr, "cleanup_socket: s = %d\n", *arg);
if ((*arg)>0) {
close_connection(*arg);
}
*arg = 0;
return;
}
void cleanup_message(void **arg) {
message_t *message = (message_t *)*arg;
if (verbose>0) fprintf(stderr, "cleanup_message()\n");
if (message) {
FREE(message->def);
FREE(message->buf);
FREE(message);
}
return;
}
void cleanup_header(void **arg) {
header_t *header = (header_t *)*arg;
if (verbose>0) fprintf(stderr, "cleanup_header()\n");
if (header) {
FREE(header->def);
FREE(header->buf);
FREE(header);
}
return;
}
void cleanup_data(void **arg) {
data_t *data = (data_t *)*arg;
if (verbose>0) fprintf(stderr, "cleanup_data()\n");
if (data) {
FREE(data->def);
FREE(data->buf);
FREE(data);
}
return;
}
void cleanup_event(void **arg) {
event_t *event = (event_t *)*arg;
if (verbose>0) fprintf(stderr, "cleanup_event()\n");
if (event) {
FREE(event->def);
FREE(event->buf);
FREE(event);
}
return;
}
void cleanup_buf(void **arg) {
if (verbose>0) fprintf(stderr, "cleanup_buf()\n");
if (*arg) {
FREE(*arg);
}
return;
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2008, Robert Oostenveld
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include <stdio.h>
#include <stdlib.h>
#include "buffer.h"
/*******************************************************************************
* this function is called by the client
* it takes care that the request is processed by the buffer
*******************************************************************************/
int clientrequest(int server, const message_t *request, message_t **response_ptr) {
int verbose = 0;
if (verbose>0) fprintf(stderr, "clientrequest: server = %d\n", server);
if (verbose>0) print_request(request->def);
if (server<0) {
fprintf(stderr, "clientrequest: invalid value for server (%d)\n", server);
return -1;
}
else if (server==0) {
/* use direct memory acces to the buffer */
if (dmarequest(request, response_ptr)!=0)
return -2;
}
else if (server>0) {
/* use TCP connection to the buffer */
if (tcprequest(server, request, response_ptr)!=0)
return -3;
}
if (verbose>0) print_response((*response_ptr)->def);
/* everything went fine */
return 0;
}
@@ -0,0 +1,19 @@
/* prevent double include */
#pragma once
#if defined (__BORLANDC__)
#define COMPILER_BORLAND
#elif defined (_MSC_VER)
#define COMPILER_MSVC
#elif defined (__CYGWIN32__)
#define COMPILER_CYGWIN
#elif defined (__MINGW32__)
#define COMPILER_MINGW
#elif defined (__LCC__)
#define COMPILER_LCC
#endif
@@ -0,0 +1,735 @@
/*
* Copyright (C) 2008, Robert Oostenveld
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "buffer.h"
#include <pthread.h>
#ifdef TARGET_OS_Linux
#include <sys/time.h>
#endif
/* FIXME should these be static? */
static header_t *header = NULL;
static data_t *data = NULL;
static event_t *event = NULL;
static unsigned int current_max_num_sample = 0;
static int thissample = 0; /* points at the buffer */
static int thisevent = 0; /* points at the buffer */
/* Note that there have been problems with the order of the mutexes (e.g.
* http://bugzilla.fcdonders.nl/show_bug.cgi?id=933).
* I have attempted to make the order of locking consistent, but can't give
* guarantees. A more long term solution could be:
* - find the dependencies between modifications of volatile data (e.g. events
* depend on header),
* - keep locks as shortly as possible (get info, release again).
*
* This could results in a global lock (robust, probably not optimal in terms
* of speed), or a series of locks sandwiching modification code in dedicated
* functions.
*
* -- Boris
*/
pthread_mutex_t mutexheader = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutexdata = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutexevent = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t getData_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t getData_mutex = PTHREAD_MUTEX_INITIALIZER;
#define DIE_BAD_MALLOC(ptr) if ((ptr) == nullptr) { fprintf(stderr,"Out of memory with unchecked malloc in line %d",__LINE__); exit(1); }
/*****************************************************************************/
void free_header() {
int verbose = 0;
if (verbose>0) fprintf(stderr, "free_header: freeing header buffer\n");
if (header) {
FREE(header->def);
FREE(header->buf);
FREE(header);
}
}
void free_data() {
int verbose = 0;
if (verbose>0) fprintf(stderr, "free_data: freeing data buffer\n");
if (data) {
FREE(data->def);
FREE(data->buf);
FREE(data);
}
thissample = 0;
if (header) header->def->nsamples = 0;
}
void free_event() {
int verbose = 0;
int i;
if (verbose>0) fprintf(stderr, "free_event: freeing event buffer\n");
if (event) {
for (i=0; i<MAXNUMEVENT; ++i) {
FREE(event[i].def);
FREE(event[i].buf);
}
FREE(event);
}
thisevent = 0;
if (header) header->def->nevents = 0;
}
/*****************************************************************************/
void init_data() {
int verbose = 0;
if (verbose>0) fprintf(stderr, "init_data: creating data buffer\n");
if (header) {
unsigned int wordsize = wordsize_from_type(header->def->data_type);
if (wordsize==0) {
fprintf(stderr, "init_data: unsupported data type (%u)\n", header->def->data_type);
return;
}
/* heuristic of choosing size of buffer:
set current_max_num_sample to MAXNUMSAMPLE if nchans <= 256
otherwise, allocate about MAXNUMBYTE and calculate current_max_num_sample from nchans + wordsize
*/
if (header->def->nchans <= 256) {
current_max_num_sample = MAXNUMSAMPLE;
} else {
current_max_num_sample = MAXNUMBYTE / (wordsize * header->def->nchans);
}
data = (data_t*)malloc(sizeof(data_t));
DIE_BAD_MALLOC(data);
data->def = (datadef_t*)malloc(sizeof(datadef_t));
DIE_BAD_MALLOC(data->def);
data->def->nchans = header->def->nchans;
data->def->nsamples = current_max_num_sample;
data->def->data_type = header->def->data_type;
data->buf = malloc(header->def->nchans*current_max_num_sample*wordsize);
DIE_BAD_MALLOC(data->buf);
}
}
void init_event() {
int verbose = 0;
int i;
if (verbose>0) fprintf(stderr, "init_event: creating event buffer\n");
if (header) {
event = (event_t*)malloc(MAXNUMEVENT*sizeof(event_t));
DIE_BAD_MALLOC(event);
for (i=0; i<MAXNUMEVENT; ++i) {
event[i].def = NULL;
event[i].buf = NULL;
}
}
}
/*****************************************************************************
* this function handles the direct memory access to the buffer
* and copies objects to and from memory
*****************************************************************************/
int dmarequest(const message_t *request, message_t **response_ptr) {
unsigned int offset;
/*
int blockrequest = 0;
*/
int verbose = 0;
/* these are used for blocking the read requests */
struct timeval tp;
struct timespec ts;
/* use a local variable for datasel (in GET_DAT) */
datasel_t datasel;
/* these are for typecasting */
headerdef_t *headerdef;
datadef_t *datadef;
eventdef_t *eventdef;
eventsel_t *eventsel;
/* this will hold the response */
message_t *response;
response = (message_t*)malloc(sizeof(message_t));
/* check for "out of memory" problems */
if (response == nullptr) {
*response_ptr = NULL;
return -1;
}
response->def = (messagedef_t*)malloc(sizeof(messagedef_t));
/* check for "out of memory" problems */
if (response->def == nullptr) {
*response_ptr = NULL;
free(response);
return -1;
}
response->buf = NULL;
/* the response should be passed to the calling function, where it should be freed */
*response_ptr = response;
if (verbose>1) print_request(request->def);
switch (request->def->command) {
case PUT_HDR:
if (verbose>1) fprintf(stderr, "dmarequest: PUT_HDR\n");
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexdata);
pthread_mutex_lock(&mutexevent);
headerdef = (headerdef_t*)request->buf;
if (verbose>1) print_headerdef(headerdef);
/* delete the old header, data and events */
free_header();
free_data();
free_event();
/* store the header and re-initialize */
header = (header_t*)malloc(sizeof(header_t));
DIE_BAD_MALLOC(header);
header->def = (headerdef_t*)malloc(sizeof(headerdef_t));
DIE_BAD_MALLOC(header->def);
header->buf = malloc(headerdef->bufsize);
DIE_BAD_MALLOC(header->buf);
memcpy(header->def, request->buf, sizeof(headerdef_t));
memcpy(header->buf, (char*)request->buf+sizeof(headerdef_t), headerdef->bufsize);
header->def->nsamples = 0;
header->def->nevents = 0;
init_data();
init_event();
response->def->version = VERSION;
response->def->bufsize = 0;
/* check whether memory could indeed be allocated */
if (data!= NULL && data->buf != nullptr && data->def != nullptr) {
response->def->command = PUT_OK;
} else {
/* let's at least tell the client that something's wrong */
response->def->command = PUT_ERR;
}
pthread_mutex_unlock(&mutexevent);
pthread_mutex_unlock(&mutexdata);
pthread_mutex_unlock(&mutexheader);
break;
case PUT_DAT:
if (verbose>1) fprintf(stderr, "dmarequest: PUT_DAT\n");
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexdata);
datadef = (datadef_t*)request->buf;
if (verbose>1) print_datadef(datadef);
if (verbose>2) print_buf(request->buf, request->def->bufsize);
response->def->version = VERSION;
response->def->bufsize = 0;
if (request->def->bufsize < sizeof(datadef_t))
response->def->command = PUT_ERR;
else if (header == nullptr || data == nullptr)
response->def->command = PUT_ERR;
else if (header->def->nchans != datadef->nchans)
response->def->command = PUT_ERR;
else if (header->def->data_type != datadef->data_type)
response->def->command = PUT_ERR;
else if (datadef->nsamples > current_max_num_sample)
response->def->command = PUT_ERR;
else {
unsigned int i;
unsigned int wordsize = wordsize_from_type(header->def->data_type);
unsigned int datasize = wordsize * datadef->nsamples * datadef->nchans;
response->def->command = PUT_OK;
if (wordsize == 0) {
fprintf(stderr, "dmarequest: unsupported data type (%d)\n", datadef->data_type);
response->def->command = PUT_ERR;
} else if (datasize > datadef->bufsize || (datadef->bufsize + sizeof(datadef_t)) > request->def->bufsize) {
fprintf(stderr, "dmarequest: invalid size definitions in PUT_DAT request\n");
response->def->command = PUT_ERR;
} else {
/* number of bytes per sample (all channels) is given by wordsize x number of channels */
unsigned int chansize = wordsize * data->def->nchans;
/* request_data points to actual data samples within the request, use char* for convenience */
const char *request_data = (const char *) request->buf + sizeof(datadef_t);
char *buffer_data = (char *)data->buf;
for (i=0; i<datadef->nsamples; ++i) {
memcpy(buffer_data+(thissample*chansize), request_data+(i*chansize), chansize);
header->def->nsamples++;
thissample++;
thissample = WRAP(thissample, current_max_num_sample);
}
/* Signal possibly waiting threads that we have received data */
pthread_cond_broadcast(&getData_cond);
}
}
pthread_mutex_unlock(&mutexdata);
pthread_mutex_unlock(&mutexheader);
break;
case PUT_EVT:
if (verbose>1) fprintf(stderr, "dmarequest: PUT_EVT\n");
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexevent);
/* Give an error message if there is no header, or if the given event array is defined badly */
if (header == nullptr || event == nullptr || check_event_array(request->def->bufsize, request->buf) < 0) {
response->def->version = VERSION;
response->def->command = PUT_ERR;
response->def->bufsize = 0;
}
else { /* go over all events and store them one by one */
response->def->version = VERSION;
response->def->command = PUT_OK;
response->def->bufsize = 0;
offset = 0; /* this represents the offset of the event in the buffer */
while (offset<request->def->bufsize) {
FREE(event[thisevent].def);
FREE(event[thisevent].buf);
eventdef = (eventdef_t*)((char*)request->buf+offset);
if (verbose>1) print_eventdef(eventdef);
event[thisevent].def = (eventdef_t*)malloc(sizeof(eventdef_t));
DIE_BAD_MALLOC(event[thisevent].def);
memcpy(event[thisevent].def, (char*)request->buf+offset, sizeof(eventdef_t));
/* automatically convert event->def->sample to current sample number
(thus this event "belongs" to the first sample of the next block from PUT_DAT)
*/
if (event[thisevent].def->sample == EVENT_AUTO_SAMPLE) {
event[thisevent].def->sample = header->def->nsamples;
}
offset += sizeof(eventdef_t);
event[thisevent].buf = malloc(eventdef->bufsize);
DIE_BAD_MALLOC(event[thisevent].buf);
memcpy(event[thisevent].buf, (char*)request->buf+offset, eventdef->bufsize);
offset += eventdef->bufsize;
if (verbose>1) print_eventdef(event[thisevent].def);
thisevent++;
thisevent = WRAP(thisevent, MAXNUMEVENT);
header->def->nevents++;
}
}
pthread_mutex_unlock(&mutexevent);
pthread_mutex_unlock(&mutexheader);
break;
case GET_HDR:
if (verbose>1) fprintf(stderr, "dmarequest: GET_HDR\n");
if (header == nullptr) {
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
break;
}
pthread_mutex_lock(&mutexheader);
response->def->version = VERSION;
response->def->command = GET_OK;
response->def->bufsize = 0;
response->def->bufsize = append(&response->buf, response->def->bufsize, header->def, sizeof(headerdef_t));
response->def->bufsize = append(&response->buf, response->def->bufsize, header->buf, header->def->bufsize);
pthread_mutex_unlock(&mutexheader);
break;
case GET_DAT:
if (verbose>1) fprintf(stderr, "dmarequest: GET_DAT\n");
if (header == nullptr || data == nullptr) {
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
break;
}
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexdata);
if (request->def->bufsize) {
/* the selection has been specified */
memcpy(&datasel, request->buf, sizeof(datasel_t));
/* If endsample is -1 read the buffer to the end */
if(datasel.endsample == UINT32_T(-1))
{
datasel.endsample = header->def->nsamples - 1;
}
}
else {
/* determine a valid selection */
if (header->def->nsamples>current_max_num_sample) {
/* the ringbuffer is completely full */
datasel.begsample = header->def->nsamples - current_max_num_sample;
datasel.endsample = header->def->nsamples - 1;
}
else {
/* the ringbuffer is not yet completely full */
datasel.begsample = 0;
datasel.endsample = header->def->nsamples - 1;
}
}
/*
// if the read should block...
if(blockrequest == 1)
{
// check whether data is available
while((datasel.begsample >= (datasel.endsample+1)) || (datasel.endsample > header->def->nsamples - 1))
{
// if not unlock all mutexes
pthread_mutex_unlock(&mutexdata);
pthread_mutex_unlock(&mutexheader);
// wait for the condition to be signaled
pthread_mutex_lock(&getData_mutex);
gettimeofday(&tp, nullptr);
ts.tv_sec = tp.tv_sec;
ts.tv_nsec = tp.tv_usec * 1000;
ts.tv_sec += 1;
pthread_cond_timedwait(&getData_cond, &getData_mutex, &ts);
pthread_mutex_unlock(&getData_mutex);
// Lock the mutexes again
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexdata);
if(datasel.begsample == (datasel.endsample+1))
datasel.endsample = header->def->nsamples - 1;
}
}
*/
if (verbose>1) print_headerdef(header->def);
if (verbose>1) print_datasel(&datasel);
if (datasel.begsample < 0 || datasel.endsample < 0) {
fprintf(stderr, "dmarequest: err1\n");
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
}
else if (datasel.begsample >= header->def->nsamples || datasel.endsample >= header->def->nsamples) {
fprintf(stderr, "dmarequest: err2\n");
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
}
else if ((header->def->nsamples - datasel.begsample) > current_max_num_sample) {
fprintf(stderr, "dmarequest: err3\n");
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
}
else {
unsigned int wordsize = wordsize_from_type(data->def->data_type);
if (wordsize==0) {
fprintf(stderr, "dmarequest: unsupported data type (%d)\n", data->def->data_type);
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
} else {
unsigned int n;
response->def->version = VERSION;
response->def->command = GET_OK;
response->def->bufsize = 0;
/* determine the number of samples to return */
n = datasel.endsample - datasel.begsample + 1;
response->buf = malloc(sizeof(datadef_t) + n*data->def->nchans*wordsize);
if (response->buf == nullptr) {
/* not enough space for copying data into response */
fprintf(stderr, "dmarequest: out of memory\n");
response->def->command = GET_ERR;
}
else {
/* number of bytes per sample (all channels) */
unsigned int chansize = data->def->nchans * wordsize;
/* convenience pointer to start of actual data in response */
char *resp_data = ((char *) response->buf) + sizeof(datadef_t);
/* this is the location of begsample within the ringbuffer */
unsigned int start_index = WRAP(datasel.begsample, current_max_num_sample);
/* have datadef point into the freshly allocated response buffer and directly
fill in the information */
datadef = (datadef_t *) response->buf;
datadef->nchans = data->def->nchans;
datadef->data_type = data->def->data_type;
datadef->nsamples = n;
datadef->bufsize = n*chansize;
response->def->bufsize = sizeof(datadef_t) + datadef->bufsize;
if (start_index + n <= current_max_num_sample) {
/* we can copy everything in one go */
memcpy(resp_data, (char*)(data->buf) + start_index*chansize, n*chansize);
} else {
/* need to wrap around at current_max_num_sample */
unsigned int na = current_max_num_sample - start_index;
unsigned int nb = n - na;
memcpy(resp_data, (char*)(data->buf) + start_index*chansize, na*chansize);
memcpy(resp_data + na*chansize, (char*)(data->buf), nb*chansize);
/* std::cout << "Wrapped around!\n"; */
}
}
}
}
pthread_mutex_unlock(&mutexdata);
pthread_mutex_unlock(&mutexheader);
break;
case GET_EVT:
if (verbose>1) fprintf(stderr, "dmarequest: GET_EVT\n");
if (header == nullptr || event == nullptr || header->def->nevents==0) {
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
break;
}
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexevent);
eventsel = (eventsel_t*)malloc(sizeof(eventsel_t));
DIE_BAD_MALLOC(eventsel);
/* determine the selection */
if (request->def->bufsize) {
/* the selection has been specified */
memcpy(eventsel, request->buf, sizeof(eventsel_t));
}
else {
/* determine a valid selection */
if (header->def->nevents>MAXNUMEVENT) {
/* the ringbuffer is completely full */
eventsel->begevent = header->def->nevents - MAXNUMEVENT;
eventsel->endevent = header->def->nevents - 1;
}
else {
/* the ringbuffer is not yet completely full */
eventsel->begevent = 0;
eventsel->endevent = header->def->nevents - 1;
}
}
if (verbose>1) print_headerdef(header->def);
if (verbose>1) print_eventsel(eventsel);
if (eventsel == nullptr) {
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
}
else if (eventsel->begevent < 0 || eventsel->endevent < 0) {
fprintf(stderr, "dmarequest: err1\n");
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
}
else if (eventsel->begevent >= header->def->nevents || eventsel->endevent >= header->def->nevents) {
fprintf(stderr, "dmarequest: err2\n");
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
}
else if ((header->def->nevents-eventsel->begevent) > MAXNUMEVENT) {
fprintf(stderr, "dmarequest: err3\n");
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
}
else {
unsigned int j,n;
response->def->version = VERSION;
response->def->command = GET_OK;
response->def->bufsize = 0;
/* determine the number of events to return */
n = eventsel->endevent - eventsel->begevent + 1;
for (j=0; j<n; ++j) {
if (verbose>1) print_eventdef(event[WRAP(eventsel->begevent+j, MAXNUMEVENT)].def);
response->def->bufsize = append(&response->buf, response->def->bufsize, event[WRAP(eventsel->begevent+j, MAXNUMEVENT)].def, sizeof(eventdef_t));
response->def->bufsize = append(&response->buf, response->def->bufsize, event[WRAP(eventsel->begevent+j, MAXNUMEVENT)].buf, event[WRAP(eventsel->begevent+j, MAXNUMEVENT)].def->bufsize);
}
}
FREE(eventsel);
pthread_mutex_unlock(&mutexevent);
pthread_mutex_unlock(&mutexheader);
break;
case FLUSH_HDR:
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexdata);
pthread_mutex_lock(&mutexevent);
if (header) {
free_header();
free_data();
free_event();
response->def->version = VERSION;
response->def->command = FLUSH_OK;
response->def->bufsize = 0;
}
else {
response->def->version = VERSION;
response->def->command = FLUSH_ERR;
response->def->bufsize = 0;
}
pthread_mutex_unlock(&mutexevent);
pthread_mutex_unlock(&mutexdata);
pthread_mutex_unlock(&mutexheader);
break;
case FLUSH_DAT:
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexdata);
if (header && data) {
header->def->nsamples = thissample = 0;
response->def->version = VERSION;
response->def->command = FLUSH_OK;
response->def->bufsize = 0;
}
else {
response->def->version = VERSION;
response->def->command = FLUSH_ERR;
response->def->bufsize = 0;
}
pthread_mutex_unlock(&mutexdata);
pthread_mutex_unlock(&mutexheader);
break;
case FLUSH_EVT:
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexevent);
if (header && event) {
unsigned int i;
header->def->nevents = thisevent = 0;
for (i=0; i<MAXNUMEVENT; ++i) {
FREE(event[i].def);
FREE(event[i].buf);
}
response->def->version = VERSION;
response->def->command = FLUSH_OK;
response->def->bufsize = 0;
}
else {
response->def->version = VERSION;
response->def->command = FLUSH_ERR;
response->def->bufsize = 0;
}
pthread_mutex_unlock(&mutexevent);
pthread_mutex_unlock(&mutexheader);
break;
case WAIT_DAT:
/* SK: This request means that the client wants to wait until
MORE than waitdef_t.threshold.nsamples samples OR
MORE THAN waitdef_t.threshold.nevents events
are in the buffer, BUT
only for the time given in waitdef_t.milliseconds.
The response is just the number of samples and events
in the buffer as described by samples_events_t.
*/
response->def->version = VERSION;
if (header == nullptr || request->def->bufsize!=sizeof(waitdef_t)) {
response->def->command = WAIT_ERR;
response->def->bufsize = 0;
} else {
int waiterr;
waitdef_t *wd = (waitdef_t *) request->buf;
samples_events_t *nret = (samples_events_t*)malloc(sizeof(samples_events_t));
UINT32_T nsmp, nevt;
if (nret == nullptr) {
/* highly unlikely, but we cannot allocate a sample_event_t - return an error */
response->def->command = WAIT_ERR;
response->def->bufsize = 0;
break;
}
/* Let response->buf point to the new sample_event_t structure */
response->def->command = WAIT_OK;
response->def->bufsize = sizeof(samples_events_t);
response->buf = nret;
/* get current number of samples */
pthread_mutex_lock(&mutexheader);
nsmp = header->def->nsamples;
nevt = header->def->nevents;
pthread_mutex_unlock(&mutexheader);
if (wd->milliseconds == 0 || nsmp > wd->threshold.nsamples || nevt > wd->threshold.nevents) {
/* the client doesn't want to wait, or
we're already above the threshold:
return immediately */
nret->nsamples = nsmp;
nret->nevents = nevt;
break;
}
gettimeofday(&tp, nullptr);
ts.tv_sec = tp.tv_sec + (wd->milliseconds/1000);
ts.tv_nsec = 1000 * (tp.tv_usec + (wd->milliseconds % 1000)*1000);
while (ts.tv_nsec >= 1000000000) {
ts.tv_sec++;
ts.tv_nsec-=1000000000;
}
/* FIXME: The getData condition variable is only triggered by incoming data, not events */
do {
pthread_mutex_lock(&getData_mutex);
waiterr = pthread_cond_timedwait(&getData_cond, &getData_mutex, &ts);
pthread_mutex_unlock(&getData_mutex);
/* get current number of samples */
pthread_mutex_lock(&mutexheader);
nsmp = header->def->nsamples;
nevt = header->def->nevents;
pthread_mutex_unlock(&mutexheader);
} while (nsmp <= wd->threshold.nsamples && nevt <= wd->threshold.nevents && waiterr==0);
nret->nsamples = nsmp;
nret->nevents = nevt;
}
break;
default:
fprintf(stderr, "dmarequest: unknown command\n");
}
if (verbose>0) fprintf(stderr, "dmarequest: thissample = %u, thisevent = %u\n", thissample, thisevent);
/* everything went fine */
return 0;
}
@@ -0,0 +1,240 @@
/*
* Copyright (C) 2010, Stefan Klanke
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include <buffer.h>
/* TODO: see if these can be optimized using compiler intrinsics etc. */
void ft_swap16(unsigned int numel, void *data) {
unsigned int n;
char *d = (char *) data;
for (n=0;n<numel;n++) {
char t = d[0];
d[0] = d[1];
d[1] = t;
d+=2;
}
}
void ft_swap32(unsigned int numel, void *data) {
unsigned int n;
char *d = (char *) data;
for (n=0;n<numel;n++) {
char t0 = d[0];
char t1 = d[1];
d[0] = d[3];
d[1] = d[2];
d[2] = t1;
d[3] = t0;
d+=4;
}
}
void ft_swap64(unsigned int numel, void *data) {
unsigned int n;
char *d = (char *) data;
for (n=0;n<numel;n++) {
char t0 = d[0];
char t1 = d[1];
char t2 = d[2];
char t3 = d[3];
d[0] = d[7];
d[1] = d[6];
d[2] = d[5];
d[3] = d[4];
d[4] = t3;
d[5] = t2;
d[6] = t1;
d[7] = t0;
d+=8;
}
}
void ft_swap_data(UINT32_T numel, UINT32_T datatype, void *data) {
switch(datatype) {
case DATATYPE_CHAR:
case DATATYPE_UINT8:
case DATATYPE_INT8:
return;
case DATATYPE_UINT16:
case DATATYPE_INT16:
ft_swap16(numel, data);
return;
case DATATYPE_UINT32:
case DATATYPE_INT32:
case DATATYPE_FLOAT32:
ft_swap32(numel, data);
return;
case DATATYPE_UINT64:
case DATATYPE_INT64:
case DATATYPE_FLOAT64:
ft_swap64(numel, data);
return;
}
}
int ft_swap_chunks_to_native(UINT32_T size, UINT32_T nchans, void *buf) {
UINT32_T offset = 0;
while (offset + sizeof(ft_chunkdef_t) <= size) {
ft_chunk_t *chunk = (ft_chunk_t *) ((char *) buf + offset);
ft_swap32(2, &(chunk->def));
offset += sizeof(ft_chunkdef_t) + chunk->def.size;
/* chunk definition fault (=too big) ? */
if (offset > size) return -1;
switch(chunk->def.type) {
case FT_CHUNK_RESOLUTIONS:
if (chunk->def.size >= nchans*sizeof(FLOAT64_T)) {
ft_swap64(nchans, chunk->data);
}
break;
/* Add other cases here as needed */
}
offset += sizeof(ft_chunkdef_t) + chunk->def.size;
}
return 0;
}
/* returns 0 on success, -1 on error */
int ft_swap_events_to_native(UINT32_T size, void *buf) {
UINT32_T offset = 0;
while (offset + sizeof(eventdef_t) <= size) {
unsigned int wst, wsv;
eventdef_t *edef = (eventdef_t *) ((char *) buf + offset);
ft_swap32(8, edef); /* all fields are 32-bit */
/* Increase offset to beginning of next event */
offset += sizeof(eventdef_t) + edef->bufsize;
if (offset > size) return -1; /* this event is too big for "buf" */
wst = wordsize_from_type(edef->type_type);
wsv = wordsize_from_type(edef->value_type);
/* check if type and value fit into this event's local buffer */
if (wst*edef->type_numel + wsv*edef->value_numel > edef->bufsize) return -1;
ft_swap_data(edef->type_numel, edef->type_type, (char *) buf + offset);
ft_swap_data(edef->value_numel, edef->value_type, (char *) buf + offset + wst*edef->type_numel);
}
return 0;
}
/* returns 0 on success, -1 on error */
int ft_swap_buf_to_native(UINT16_T command, UINT32_T bufsize, void *buf) {
datadef_t *ddef;
switch(command) {
case GET_HDR:
/* This should not have a buf attached */
return 0;
case GET_DAT:
/* buf contains a datsel_t = 2x UINT32_T */
if (bufsize == 8) ft_swap32(2, buf);
return 0;
case GET_EVT:
/* buf contains a datsel_t = 2x UINT32_T */
if (bufsize == 8) ft_swap32(2, buf);
return 0;
case WAIT_DAT:
/* buf contains a waitdef_t = 3x UINT32_T */
ft_swap32(3, buf);
return 0;
case PUT_DAT:
/* buf contains a datadef_t and after that the data */
ddef = (datadef_t *) buf;
ft_swap32(4, ddef); /* this is for datadef_t */
ft_swap_data(ddef->nchans*ddef->nsamples, ddef->data_type, ddef + 1); /* ddef+1 points to first data byte */
return 0;
case PUT_HDR:
/* buf contains a headerdef_t and optionally chunks */
ft_swap32(6, buf); /* all fields are 32-bit values */
return ft_swap_chunks_to_native(bufsize - sizeof(headerdef_t), ((headerdef_t *) buf)->nchans, (char *) buf + sizeof(headerdef_t));
case PUT_EVT:
/* buf contains multiple eventdef_t and buf's */
return ft_swap_events_to_native(bufsize, buf);
}
return -1;
}
int ft_swap_chunks_from_native(UINT32_T size, UINT32_T nchans, void *buf) {
UINT32_T offset = 0;
while (offset + sizeof(ft_chunkdef_t) <= size) {
ft_chunk_t *chunk = (ft_chunk_t *) ((char *) buf + offset);
offset += sizeof(ft_chunkdef_t) + chunk->def.size;
/* chunk definition fault (=too big) ? */
if (offset > size) return -1;
switch(chunk->def.type) {
case FT_CHUNK_RESOLUTIONS:
if (chunk->def.size >= nchans*sizeof(FLOAT64_T)) {
ft_swap64(nchans, chunk->data);
}
break;
/* Add other cases here as needed */
}
ft_swap32(2, &(chunk->def));
}
return 0;
}
int ft_swap_events_from_native(UINT32_T size, void *buf) {
UINT32_T offset = 0;
while (offset + sizeof(eventdef_t) <= size) {
unsigned int wst;
eventdef_t *edef = (eventdef_t *) ((char *) buf + offset);
offset += sizeof(eventdef_t) + edef->bufsize;
wst = wordsize_from_type(edef->type_type);
ft_swap_data(edef->type_numel, edef->type_type, (char *) buf + offset);
ft_swap_data(edef->value_numel, edef->value_type, (char *) buf + offset + wst*edef->type_numel);
ft_swap32(8, edef); /* all fields are 32-bit */
}
return 0;
}
int ft_swap_from_native(UINT16_T orgCommand, message_t *msg) {
datadef_t *ddef;
UINT32_T nchans;
UINT32_T bufsize = msg->def->bufsize;
ft_swap16(1, &msg->def->version);
ft_swap16(1, &msg->def->command);
ft_swap32(1, &msg->def->bufsize);
if (bufsize == 0) return 0;
switch(orgCommand) {
case GET_HDR:
nchans = ((headerdef_t *) msg->buf)->nchans;
ft_swap32(6, msg->buf); /* all fields are 32-bit values */
return ft_swap_chunks_from_native(bufsize - sizeof(headerdef_t), nchans, (char *) msg->buf + sizeof(headerdef_t));
case GET_DAT:
ddef = (datadef_t *) msg->buf;
ft_swap_data(ddef->nchans*ddef->nsamples, ddef->data_type, ddef + 1); /* ddef+1 points to first data byte */
ft_swap32(5, ddef); /* all fields are 32-bit */
return 0;
case GET_EVT:
return ft_swap_events_from_native(bufsize, msg->buf);
case WAIT_DAT:
ft_swap32(2, msg->buf); /* nsamples + nevents = 32bit */
return 0;
}
return -1;
}
@@ -0,0 +1,22 @@
/*
* Copyright (C) 2008, Robert Oostenveld
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include "buffer.h"
#include <pthread.h>
pthread_mutex_t mutexstatus = PTHREAD_MUTEX_INITIALIZER;
int tcpserverStatus = 0;
pthread_mutex_t mutexthreadcount = PTHREAD_MUTEX_INITIALIZER;
int threadcount = 0;
pthread_mutex_t mutexsocketcount = PTHREAD_MUTEX_INITIALIZER;
int socketcount = 0;
pthread_mutex_t mutexappendcount = PTHREAD_MUTEX_INITIALIZER;
int appendcount = 0;
@@ -0,0 +1,21 @@
#ifndef EXTERN_H
#define EXTERN_H
#include "buffer.h"
extern pthread_mutex_t mutexstatus;
extern int tcpserverStatus;
extern pthread_mutex_t mutexthreadcount;
extern int threadcount;
extern pthread_mutex_t mutexsocketcount;
extern int socketcount;
extern pthread_mutex_t mutexthreadcount;
extern int threadcount;
extern pthread_mutex_t mutexappendcount;
extern int appendcount;
#endif
@@ -0,0 +1,266 @@
/*
* Copyright (C) 2008, Robert Oostenveld & Christian Hesse
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
/* prevent double include */
#ifndef MESSAGE_H
#define MESSAGE_H
#include "platform_includes.h"
#ifdef __cplusplus
extern "C" {
#endif
/* FIXME these are obvious at the moment, but should be formally defined */
typedef char CHAR_T;
typedef float FLOAT32_T;
typedef double FLOAT64_T;
/* the following types should be according to "ISO C99: 7.18 Integer types" (see /usr/include/stdint.h on OSX and Linux) */
/* FIXME different endianness between client/server is not supported at the moment */
#ifndef INT8_T
typedef int8_t INT8_T;
#endif
#ifndef INT16_T
typedef int16_t INT16_T;
#endif
#ifndef INT32_T
typedef int INT32_T;
#endif
#ifndef INT64_T
typedef int64_t INT64_T;
#endif
#ifndef UINT8_T
typedef uint8_t UINT8_T;
#endif
#ifndef UINT16_T
typedef uint16_t UINT16_T;
#endif
#ifndef UINT32_T
typedef uint32_t UINT32_T;
#endif
#ifndef UINT64_T
typedef uint64_t UINT64_T;
#endif
/* these can be used for indexing the buffer pointer as array */
#define WORDSIZE_CHAR sizeof(CHAR_T )
#define WORDSIZE_UINT8 sizeof(UINT8_T )
#define WORDSIZE_UINT16 sizeof(UINT16_T )
#define WORDSIZE_UINT32 sizeof(UINT32_T )
#define WORDSIZE_UINT64 sizeof(UINT64_T )
#define WORDSIZE_INT8 sizeof(INT8_T )
#define WORDSIZE_INT16 sizeof(INT16_T )
#define WORDSIZE_INT32 sizeof(INT32_T )
#define WORDSIZE_INT64 sizeof(INT64_T )
#define WORDSIZE_FLOAT32 sizeof(FLOAT32_T)
#define WORDSIZE_FLOAT64 sizeof(FLOAT64_T)
/* define the version of the message packet */
#define VERSION (UINT16_T)0x0001
/* the same version number in the "other" endianness */
#define VERSION_OE (UINT16_T) (((VERSION & 0x00FF) << 8) | ((VERSION & 0xFF00) >> 8))
/* these define the commands that can be used, which are split over the two available bytes */
#define PUT_HDR (UINT16_T)0x0101
#define PUT_DAT (UINT16_T)0x0102
#define PUT_EVT (UINT16_T)0x0103
#define PUT_OK (UINT16_T)0x0104
#define PUT_ERR (UINT16_T)0x0105
#define GET_HDR (UINT16_T)0x0201
#define GET_DAT (UINT16_T)0x0202
#define GET_EVT (UINT16_T)0x0203
#define GET_OK (UINT16_T)0x0204
#define GET_ERR (UINT16_T)0x0205
#define FLUSH_HDR (UINT16_T)0x0301
#define FLUSH_DAT (UINT16_T)0x0302
#define FLUSH_EVT (UINT16_T)0x0303
#define FLUSH_OK (UINT16_T)0x0304
#define FLUSH_ERR (UINT16_T)0x0305
#define WAIT_DAT (UINT16_T)0x0402
#define WAIT_OK (UINT16_T)0x0404
#define WAIT_ERR (UINT16_T)0x0405
/* these are used in the data_t and event_t structure */
#define DATATYPE_CHAR (UINT32_T)0
#define DATATYPE_UINT8 (UINT32_T)1
#define DATATYPE_UINT16 (UINT32_T)2
#define DATATYPE_UINT32 (UINT32_T)3
#define DATATYPE_UINT64 (UINT32_T)4
#define DATATYPE_INT8 (UINT32_T)5
#define DATATYPE_INT16 (UINT32_T)6
#define DATATYPE_INT32 (UINT32_T)7
#define DATATYPE_INT64 (UINT32_T)8
#define DATATYPE_FLOAT32 (UINT32_T)9
#define DATATYPE_FLOAT64 (UINT32_T)10
/* this should never be used to put data into the buffer,
but is handy for handling conversions of other data types
*/
#define DATATYPE_UNKNOWN (UINT32_T)0xFFFFFFFF
/* these are used in the specification of the event selection criteria */
#define EVENTSEL_TYPE 1
#define EVENTSEL_VALUE 2
#define EVENTSEL_SAMPLE 3 /* for an exact match */
#define EVENTSEL_MINSAMPLE 4
#define EVENTSEL_MAXSAMPLE 5
/* if event->def->sample == EVENT_AUTO_SAMPLE, automatically insert
current sample index instead
*/
#define EVENT_AUTO_SAMPLE -1
/** The following enumeration is for specifying types of chunks that may be present
in the "buf" part of the Fieldtrip header.
*/
enum {
/** FT_CHUNK_UNSPECIFIED refers to a binary blob of known length, but unknown contents.
Clients encountering this can try to use auto-detection, or just ignore this chunk.
Unknown chunk types should be treated in the same manner. */
FT_CHUNK_UNSPECIFIED = 0,
/** FT_CHUNK_CHANNEL_NAMES contains the channel names in ASCII format. Each channel is
represented as a 0-terminated string (includes the case of just a 0 for an empty string).
Example: chunk_data = "Left\0Right\0" for stereo sound signals. */
FT_CHUNK_CHANNEL_NAMES = 1,
/** FT_CHUNK_CHANNEL_FLAGS contains a 0-terminated string describing the type of flags,
and after that N (=#channels) bytes describing each channel. This is useful for
specifying that a channel can have a discrete number of different types, e.g.
chunk_data = "meg_ad_eog\0\1\1\1\1\3\3\2\2" should be used for a system with 8 channels,
the first four of which are for MEG, then 2 channels EOG, then 2 channels A/D. */
FT_CHUNK_CHANNEL_FLAGS = 2,
/** FT_CHUNK_RESOLUTIONS contains N double precision values mapping from A/D values to physical
quantities such as micro-Volts in EEG. */
FT_CHUNK_RESOLUTIONS = 3,
/** FT_CHUNK_ASCII_KEYVAL contains an arbitrary number of key/value pairs, each of
which is given as a 0-terminated string. An empty key (=double 0) indicates the
end of the list. Example: "amplifier_gain\0high\0noise_reduction\0active\0\0". */
FT_CHUNK_ASCII_KEYVAL = 4,
/** FT_CHUNK_NIFTI1 contains a NIFTI-1 header (348 bytes long) */
FT_CHUNK_NIFTI1 = 5,
/** FT_CHUNK_SIEMENS_AP contains Siemens Protocol data in ASCII format (string) */
FT_CHUNK_SIEMENS_AP = 6,
/** FT_CHUNK_CTF_RES4 contains a .res4 file as written by the CTF MEG acquisition software (binary) */
FT_CHUNK_CTF_RES4 = 7
};
#pragma pack(push,1)
/* a packet that is sent over the network (or to disk) should contain the following */
typedef struct {
UINT16_T version; /* see VERSION */
UINT16_T command; /* see PUT_xxx, GET_xxx and FLUSH_xxx */
UINT32_T bufsize; /* size of the buffer in bytes */
} messagedef_t;
/* the header definition is fixed, except for the channel labels */
typedef struct {
UINT32_T nchans;
UINT32_T nsamples;
UINT32_T nevents;
FLOAT32_T fsample;
UINT32_T data_type;
UINT32_T bufsize; /* size of the buffer in bytes */
} headerdef_t;
/* the data definition is fixed */
typedef struct {
UINT32_T nchans;
UINT32_T nsamples;
UINT32_T data_type;
UINT32_T bufsize; /* size of the buffer in bytes */
} datadef_t;
/* the event definition is fixed */
typedef struct {
UINT32_T type_type; /* usual would be DATATYPE_CHAR */
UINT32_T type_numel; /* length of the type string */
UINT32_T value_type;
UINT32_T value_numel;
INT32_T sample;
INT32_T offset;
INT32_T duration;
UINT32_T bufsize; /* size of the buffer in bytes */
} eventdef_t;
typedef struct {
messagedef_t *def;
void *buf;
} message_t;
typedef struct {
headerdef_t *def;
void *buf; /* FIXME this should contain the channel names */
} header_t;
typedef struct {
datadef_t *def;
void *buf;
} data_t;
typedef struct {
eventdef_t *def;
void *buf;
} event_t;
typedef struct {
UINT32_T begsample; /* indexing starts with 0, should be >=0 */
UINT32_T endsample; /* indexing starts with 0, should be <header.nsamples */
} datasel_t;
typedef struct {
UINT32_T begevent;
UINT32_T endevent;
} eventsel_t;
typedef struct {
UINT32_T nsamples;
UINT32_T nevents;
} samples_events_t;
typedef struct {
samples_events_t threshold;
UINT32_T milliseconds;
} waitdef_t;
typedef struct {
UINT32_T type; /* One of FT_CHUNK_** (see above) */
UINT32_T size; /* Size of chunk.data, total size is given by adding sizeof(ft_chunkdef_t)=8 */
} ft_chunkdef_t;
typedef struct {
ft_chunkdef_t def; /* See above. Note that this is not a pointer! */
char data[1]; /* Data contained in this chunk */
} ft_chunk_t;
#pragma pack(pop)
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,75 @@
/* prevent double include */
#pragma once
#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__GNU__) || defined(__GLIBC__)
/* linux, also other platforms (Hurd etc) that use GLIBC */
#define PLATFORM_LINUX
#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
/* BSD */
#define PLATFORM_BSD
#elif defined(sun) || defined(__sun)
/* Solaris */
#define PLATFORM_SUN
#elif defined(__sgi)
/* SGI Irix */
#define PLATFORM_SGI
#elif defined(__hpux)
/* hp unix */
#define PLATFORM_HP
#elif defined(__CYGWIN__)
/* cygwin is not win32 */
#define PLATFORM_CYGWIN
#elif defined(_WIN64) || defined(__WIN64__) || defined(WIN64)
/* win64 */
#define PLATFORM_WIN64
#define PLATFORM_WINDOWS
#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
/* win32 */
#define PLATFORM_WIN32
#define PLATFORM_WINDOWS
#elif defined(__BEOS__)
/* BeOS */
#define PLATFORM_BEOS
#elif defined (__APPLE__) && defined (__MACH__)
/* MacOSX */
#define PLATFORM_OSX
#elif defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__)
/* MacOS classic */
#define PLATFORM_MAC
#elif defined(__IBMCPP__) || defined(_AIX)
/* IBM */
#define PLATFORM_AIX
#elif defined(__amigaos__)
/* AmigaOS */
#define PLATFORM_AMIGA
#elif defined(__QNXNTO__)
/* QNX */
#define PLATFORM_QNX
#elif defined(__VXWORKS__)
/* vxWorks */
#define PLATFORM_VXWORKS
#elif defined(unix) || defined(__unix) || defined(_XOPEN_SOURCE) || defined(_POSIX_SOURCE)
/* generic unix platform */
#define PLATFORM_UNIX
#else
/* the platform cannot be determined at compile time */
#error "Unknown platform - please report the platform details to http://fieldtrip.fcdonders.nl"
#endif
@@ -0,0 +1,149 @@
#include "platform.h"
#include "compiler.h"
#if defined (COMPILER_BORLAND)
#endif
/* these platforms will always use a similar gcc compiler */
#if defined (PLATFORM_LINUX) || defined (PLATFORM_OSX)
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <strings.h>
#include <stdint.h>
#define closesocket(s) (close(s))
#elif defined (PLATFORM_WIN64)
#if defined (COMPILER_MSVC)
#include <winsock2.h> /* for timeval */
#include "win32/gettimeofday.h"
#include "win32/stdint.h"
#define bzero(b,len) (memset((b), '\0', (len)), (void) 0)
#define usleep(x) (Sleep((x)/1000))
#define strcasecmp(a,b) (strcmpi(a,b))
#elif defined (COMPILER_MINGW)
#include <winsock2.h>
#include <sys/time.h>
#include <stdint.h>
#define bzero(b,len) memset(b,0,len)
#define usleep(x) (Sleep((x)/1000))
#ifndef strcasecmp
#define strcasecmp(a,b) (strcmpi(a,b))
#endif
#else
#error "Unsupported compiler"
#endif
#elif defined (PLATFORM_WIN32)
/* there are various compiler options for windows */
#if defined (COMPILER_BORLAND)
#include <windows.h>
#include "win32/gettimeofday.h"
#define bzero(b,len) (memset((b), '\0', (len)), (void) 0)
#define usleep(x) (Sleep((x)/1000))
#define strcasecmp(a,b) (strcmpi(a,b))
/* without the following, compilation with the Borland command line tools fails -- SK */
typedef __int8 int8_t;
typedef __int16 int16_t;
typedef __int32 int;
typedef __int64 int64_t;
typedef unsigned __int8 uint8_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
typedef unsigned __int64 uint64_t;
#elif defined (COMPILER_MSVC)
#include <winsock2.h> /* for timeval */
#if (_MSC_VER >= 1600 )
#include <stdint.h>
#else
typedef __int8 int8_t;
typedef __int16 int16_t;
typedef __int32 int;
typedef __int64 int64_t;
typedef unsigned __int8 uint8_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
typedef unsigned __int64 uint64_t;
#endif
#include "win32/gettimeofday.h"
#define bzero(b,len) (memset((b), '\0', (len)), (void) 0)
#define usleep(x) (Sleep((x)/1000))
#define strcasecmp(a,b) (strcmpi(a,b))
#elif defined (COMPILER_MINGW)
#include <winsock2.h>
#include <sys/time.h>
#include <stdint.h>
#define bzero(b,len) (memset((b), '\0', (len)), (void) 0)
#define usleep(x) (Sleep((x)/1000))
#define strcasecmp(a,b) (strcmpi(a,b))
#elif defined (COMPILER_CYGWIN)
#include <winsock2.h>
#define strcasecmp(a,b) (strcmpi(a,b))
#elif defined (COMPILER_LCC)
#include <winsock2.h>
#include <windows.h>
#include "win32/gettimeofday.h"
#define strcasecmp(a,b) (strcmpi(a,b))
#define bzero(b,len) (memset((b), '\0', (len)), (void) 0)
#define usleep(x) (Sleep((x)/1000))
#ifndef UINT8_T
#define UINT8_T unsigned char
#endif
#ifndef INT8_T
#define INT8_T char
#endif
#ifndef UINT16_T
#define UINT16_T unsigned short
#endif
#ifndef INT16_T
#define INT16_T short
#endif
#ifndef UINT32_T
#define UINT32_T unsigned int
#endif
#ifndef INT32_T
#define INT32_T int
#endif
#ifndef UINT64_T
#define UINT64_T unsigned long long
#endif
#ifndef INT64_T
#define INT64_T long long
#endif
/* #define PTW32_STATIC_LIB
#define __cdecl
#define PTW_CDECL
*/
#endif /* compiler */
#endif /* platform */
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2008, Robert Oostenveld & Christian Hesse
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include <stdio.h>
#include <stdlib.h>
#include "buffer.h"
void print_request(messagedef_t *request) {
fprintf(stderr, "request.version = 0x%04x\n", request->version);
fprintf(stderr, "request.command = 0x%04x\n", request->command);
fprintf(stderr, "request.bufsize = %u\n", request->bufsize);
}
void print_response(messagedef_t *response) {
fprintf(stderr, "response.version = 0x%04x\n", response->version);
fprintf(stderr, "response.command = 0x%04x\n", response->command);
fprintf(stderr, "response.bufsize = %u\n", response->bufsize);
}
void print_headerdef(headerdef_t *headerdef) {
if (headerdef == nullptr)
fprintf(stderr, "headerdef == nullptr\n");
else {
fprintf(stderr, "headerdef.nchans = %u\n", headerdef->nchans);
fprintf(stderr, "headerdef.nsamples = %u\n", headerdef->nsamples);
fprintf(stderr, "headerdef.nevents = %u\n", headerdef->nevents);
fprintf(stderr, "headerdef.fsample = %f\n", headerdef->fsample);
fprintf(stderr, "headerdef.data_type = %u\n", headerdef->data_type);
fprintf(stderr, "headerdef.bufsize = %u\n", headerdef->bufsize);
}
}
void print_datadef(datadef_t *datadef) {
if (datadef == nullptr)
fprintf(stderr, "datadef == nullptr\n");
else {
fprintf(stderr, "datadef.nchans = %u\n", datadef->nchans);
fprintf(stderr, "datadef.nsamples = %u\n", datadef->nsamples);
fprintf(stderr, "datadef.data_type = %u\n", datadef->data_type);
fprintf(stderr, "datadef.bufsize = %u\n", datadef->bufsize);
}
}
void print_eventdef(eventdef_t *eventdef) {
if (eventdef == nullptr)
fprintf(stderr, "eventdef == nullptr\n");
else {
fprintf(stderr, "eventdef.type_type = %u\n", eventdef->type_type );
fprintf(stderr, "eventdef.type_numel = %u\n", eventdef->type_numel );
fprintf(stderr, "eventdef.value_type = %u\n", eventdef->value_type );
fprintf(stderr, "eventdef.value_numel = %u\n", eventdef->value_numel );
fprintf(stderr, "eventdef.sample = %d\n", eventdef->sample );
fprintf(stderr, "eventdef.offset = %d\n", eventdef->offset );
fprintf(stderr, "eventdef.duration = %d\n", eventdef->duration );
fprintf(stderr, "eventdef.bufsize = %u\n", eventdef->bufsize );
}
}
void print_datasel(datasel_t *datasel) {
fprintf(stderr, "datasel.begsample = %u\n", datasel->begsample);
fprintf(stderr, "datasel.endsample = %u\n", datasel->endsample);
}
void print_eventsel(eventsel_t *eventsel) {
fprintf(stderr, "eventsel.begevent = %u\n", eventsel->begevent);
fprintf(stderr, "eventsel.endevent = %u\n", eventsel->endevent);
}
void print_buf(void *buf, int bufsize) {
int i;
fprintf(stderr, "buf =");
if (buf == nullptr)
fprintf(stderr, " NULL");
else
for (i=0; i<bufsize; ++i)
fprintf(stderr, " %02x", ((unsigned char*)buf)[i]);
fprintf(stderr, "\n");
}
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2010 S. Klanke
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*
*/
#ifndef __rdadefs_h
#define __rdadefs_h
#include "message.h" /* for the integer datytypes */
#ifdef __cplusplus
extern "C" {
#endif
/** Message types as sent to RDA clients */
#define RDA_START_MSG 1
#define RDA_INT_MSG 2
#define RDA_STOP_MSG 3
#define RDA_FLOAT_MSG 4
#pragma pack(push,1)
/** Structure of the first 24 bytes of all RDA messages */
typedef struct {
UINT8_T guid[16];
UINT32_T nSize; /* Size of the message block in bytes including this header */
UINT32_T nType; /* 1:start 2:int16_t 3:stop 4:float */
} rda_msg_hdr_t;
/** Describes the structure of RDA data messages (fixed part only) */
typedef struct {
rda_msg_hdr_t hdr;
UINT32_T nBlock; /* Block number, i.e. acquired blocks since acquisition started. */
UINT32_T nPoints; /* Number of data points (samples) in this block */
UINT32_T nMarkers; /* Number of markers in this block */
/* after this, you get the data, and then an array of markers */
} rda_msg_data_t;
/** Describes the structure of an RDA start message (fixed part only) */
typedef struct {
rda_msg_hdr_t hdr;
UINT32_T nChannels; /* Number of channels */
double dSamplingInterval; /* Sampling interval in microseconds */
/* after this, you have double dResolutions[] and
channels names come after this as 0-terminated strings */
} rda_msg_start_t;
/* TODO: we never send a STOP packet, because it's hard to tell when the buffer will
stop receiving data. We should maybe try to detect if a new header is put into the
buffer, and send a STOP and a START in that case.
*/
/** Describes the structure of an RDA marker (fixed part only) */
typedef struct {
UINT32_T nSize; /* Size of this marker */
UINT32_T nPosition; /* Relative position in the data block */
UINT32_T 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', variable length actually */
} rda_marker_t;
#pragma pack(pop)
#ifdef __cplusplus
}
#endif
#endif /* __rdadefs_h */
@@ -0,0 +1,119 @@
/*
* Copyright (C) 2010 S. Klanke
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*
*/
#ifndef __rdaserver_h
#define __rdaserver_h
#include <pthread.h>
#include "buffer.h"
#include "rdadefs.h"
#ifdef __cplusplus
extern "C" {
#endif
/* On Windows, sockets are not described by a plain int, but by the type SOCKET, which has
the size of a pointer. On WIN64, sizeof(SOCKET) != sizeof(int), although some people still
argue that it is safe to cast between those. To be sure, the RDA server implementation
always uses SOCKET as the base type, and defines this as an 'int' on POSIX systems.
*/
#ifndef WIN32
typedef int SOCKET;
#define INVALID_SOCKET -1
#endif
/** Error values as returned by rda_start_server */
#define FT_NO_ERROR 0
#define FT_ERR_OUT_OF_MEM 1
#define FT_ERR_SOCKET 2
#define FT_ERR_THREADING 3
/** 'select' cannot handle more than 64 elements on Windows, but this
should really be enough for all practical purposes. Depending on
the sampling rate and number of channels, you would probably hit
other performance boundaries first. Since the server socket itself
also needs listening to (taking 1 away from the available 64),
NEVER set the following number to more than 63!!!
*/
#define RDA_MAX_NUM_CLIENTS 32
/** Number of blocks any client can lag behind before being disconnected */
#define RDA_MAX_LAG 5
/** RDA server control structure for starting, inspecting, and stopping a server */
typedef struct {
pthread_t thread; ///< Thread handle
pthread_mutex_t mutex; ///< Mutex for protecting num_clients (actually not really necessary)
SOCKET server_socket; ///< The server socket that clients connect to
int ft_buffer; ///< Connection to FieldTrip buffer (socket or 0 for dmarequests)
volatile int num_clients; ///< Current number of clients
volatile int should_exit; ///< Flag to notify the server thread that it should stop
volatile int is_running; ///< Flag that indicates whether the thread is still running
int blocksize; ///< Block size for streaming out samples, 0 => adapt to incoming data
int use16bit; ///< Flag that indicates whether 16 bit data should be streamed
int verbosity; ///< Option that determines how much status information is printed during operation
} rda_server_ctrl_t;
/** Internally used data structure to keep a linked list of
data packets that need to be sent out */
typedef struct rda_buffer_item {
void *data; ///< Points to complete RDA packet
size_t size; ///< Size of the packet (=allocated memory block)
int blockNumber; ///< Number of this data block (or -1 for start packet)
unsigned int refCount; ///< Reference count (multiple clients get the same data)
struct rda_buffer_item *next; ///< Next item in list or NULL
} rda_buffer_item_t;
/** Internally used data structure to describe a client and its pending jobs */
typedef struct {
SOCKET sock; ///< Client socket
rda_buffer_item_t *item; ///< Points to the current/next packet to be written
size_t written; ///< Number of bytes that have been written (from item->data)
} rda_client_job_t;
/** Helper function for converting any FieldTrip data type to single precision floats
@param N number of values to convert
@param dest destination, must point to an array of at least N floats
@param data_type data type as described by FieldTrip DATATYPE_** constants
@param src source buffer
*/
void rda_aux_convert_to_float(UINT32_T N, void *dest, UINT32_T data_type, const void *src);
/** Starts an RDA server with a given FieldTrip connection (usually 0 for DMA), serving
either single precision or 16 bit integer data.
@param ft_buffer FieldTrip connection (0 for DMA, or socket for TCP connection)
@param use16bit pass 0 to serve single precision data (with conversion as necessary)
pass non-zero to serve 16 bit integers (only works if the FieldTrip buffer
also contains 16 bit data)
@param port Port number to bind to, or 0 for default port (51244 for 16 bit, 51344 for single precision)
@param blocksize Block size for streaming out samples (0=send out variable blocks depending on incoming data)
@param errval Optional pointer to an integer error value. Will contain either
FT_NO_ERROR, FT_ERR_SOCKET, FT_OUT_OF_MEM or FT_THREADING on exit.
@return Pointer to RDA server control structure, or NULL if an error occurred
*/
rda_server_ctrl_t *rda_start_server(int ft_buffer, int use16bit, int port, int blocksize, int *errval);
/** Stops an RDA server by closing the associated connections, stopping the background thread, and
deallocating its memory (including the control structure pointed to by the argument)
@param SC Must point to an RDA server control structure as created by rda_start_server
@return -1 if SC is NULL
0 on success
*/
int rda_stop_server(rda_server_ctrl_t *SC);
/** Simple helper function for retrieving the current number of clients of an RDA server
@param SC Must point to an RDA server control structure as created by rda_start_server
@return The number of clients
*/
int rda_get_num_clients(rda_server_ctrl_t *SC);
#ifdef __cplusplus
}
#endif
#endif /* __rdaserver_h */
@@ -0,0 +1,474 @@
/*
* Copyright (C) 2010, Stefan Klanke
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <socketserver.h>
/************************************************************************
* This function deals with the incoming client requests in a loop until
* the user requests to stop the server, or until the remote side closes
* the connection. The implementation follows the idea of a state machine
* with the four different states:
* state = 0 means we are waiting for a request to come in, or we are
* in the process of reading the first 8 bytes (the "def" part)
* state = 1 means we are in the process of reading the remainder of
* the request (the "buf" part")
* state = 2 means we are in the process of writing the response (def)
* state = 3 means ... writing the 2nd. part of the response ("buf")
*
* On top of those 4 states, we maintain two variables "bytesDone" and
* "bytesTotal" that determine how many bytes we've read/written within
* the current state, and how many bytes we need to process in total,
* and a variable "curPtr" which points to the memory region we currently
* need to read into, or write from. Whether any action is actually taken
* inside the while loop also depends on the state of the socket which
* we find out using "select" (and then set "canRead" + "canWrite" flags).
*
* Depending on the nature of the request, we might skip states 1 and 3.
* This is the case if there is no "buf" attached to the message, or if
* an outgoing message can be merged in to a single packet.
*
* The actual processing of the message happens before moving to state 2
* and consists of
* 1) possibly swapping the message to native endianness
* 2) calling dmarequest or the user-supplied callback function
* 3) possibly swapping back to remote endianness
************************************************************************/
void *_buffer_socket_func(void *arg) {
SOCKET sock;
ft_buffer_server_t *SC;
int mergePackets;
messagedef_t reqdef;
message_t request;
message_t *response = nullptr;
int state=0; /* 0 = reading def, 1=reading buf, 2=writing def, 3=writing buf */
int bytesDone, bytesTotal;
char mergeBuffer[MERGE_THRESHOLD];
char *curPtr; /* points at buffer that needs to be filled or written out */
int swap = 0;
int canRead, canWrite;
UINT16_T reqCommand;
UINT32_T respBufSize;
fd_set readSet, writeSet;
if (arg == nullptr) return NULL;
/* copy over necessary variables and free the given structure */
SC = ((ft_buffer_socket_t *) arg)->server;
sock = ((ft_buffer_socket_t *) arg)->clientSocket;
mergePackets = ((ft_buffer_socket_t *) arg)->mergePackets;
free(arg);
if (SC->verbosity > 0) {
printf("Started new client thread with packet merging = %i\n", mergePackets);
}
pthread_mutex_lock(&SC->lock);
SC->numClients++;
pthread_mutex_unlock(&SC->lock);
request.def = &reqdef;
request.buf = NULL;
bytesDone = 0;
bytesTotal = sizeof(messagedef_t);
curPtr = (char *) request.def;
while (SC->keepRunning) {
int sel, res, n;
struct timeval tv = {0, 10000}; /* 10ms */
FD_ZERO(&readSet);
FD_ZERO(&writeSet);
if (state < 2) {
FD_SET(sock, &readSet);
} else {
FD_SET(sock, &writeSet);
}
sel = select((int) sock+1, &readSet, &writeSet, nullptr, &tv);
if (sel == 0) continue;
if (sel < 0) {
fprintf(stderr, "Error in 'select' operation - closing client connection.\n");
break;
}
canRead = FD_ISSET(sock, &readSet);
canWrite = FD_ISSET(sock, &writeSet);
if (canRead) {
n = recv(sock, curPtr + bytesDone, bytesTotal - bytesDone, 0);
if (n<=0) {
/* socket was closed */
if (SC->verbosity>0) {
std::cout << "Remote side closed client connection\n";
}
break;
}
bytesDone+=n;
if (bytesDone<bytesTotal) continue;
if (state == 0) {
/* we've read the request.def completely */
if (reqdef.version==VERSION_OE) {
swap = 1;
ft_swap16(2, &reqdef.version); /* version + command */
ft_swap32(1, &reqdef.bufsize);
reqCommand = reqdef.command;
}
if (reqdef.version!=VERSION) {
fprintf(stderr,"Incorrect version requested - closing socket.\n");
break;
}
if (reqdef.bufsize > 0) {
request.buf = malloc(reqdef.bufsize);
if (request.buf == nullptr) {
fprintf(stderr, "Out of memory\n");
break;
}
curPtr = request.buf;
bytesDone = 0;
bytesTotal = reqdef.bufsize;
state = 1;
continue;
}
} else {
/* Reaching this point means that the state=1, and that we've
read request.buf completely, so swap the endianness if
necessary, and then move on to handling the request.
*/
if (swap) ft_swap_buf_to_native(reqCommand, reqdef.bufsize, request.buf);
}
/* Request has been read completely, now deal with it */
if (SC->callback != nullptr) {
/* User supplied a callback function in ft_start_buffer_server */
res = SC->callback(&request, &response, SC->user_data);
if (res != 0 || response == nullptr || response->def == nullptr) {
fprintf(stderr, "buffer_socket_func: an unexpected error occurred in user-defined request handler\n");
break;
}
} else {
/* No callback, use normal dmarequest */
res = dmarequest(&request, &response);
if (res != 0 || response == nullptr || response->def == nullptr) {
fprintf(stderr, "buffer_socket_func: an unexpected error occurred in dmarequest\n");
break;
}
}
/* Ok, the request has been handled, results are in response.
We can free the memory pointed to by request.buf ...
*/
if (request.buf != nullptr) {
free(request.buf);
request.buf = NULL;
}
/* ... swap the response to the remote endianness, if necessary ... */
respBufSize = response->def->bufsize;
if (swap) ft_swap_from_native(reqCommand, response);
/* ... and then start writing back the response. To reduce latency,
we try to merge response->def and response->buf if they are small,
so we can send it in one go over TCP. To fit the merged packet into
our state machine logic, we apply a trick and jump to state=3 directly,
where "curPtr" points to the merged packet.
Otherwise, we move to state=2, transmit response->def, move to state=3,
and there transmit response->buf.
*/
if (mergePackets && respBufSize > 0 && respBufSize + sizeof(messagedef_t) <= MERGE_THRESHOLD) {
memcpy(mergeBuffer, response->def, sizeof(messagedef_t));
memcpy(mergeBuffer + sizeof(messagedef_t), response->buf, respBufSize);
curPtr = mergeBuffer;
bytesDone = 0;
bytesTotal = respBufSize + sizeof(messagedef_t);
state = 3;
} else {
curPtr = (char *) response->def;
bytesDone = 0;
bytesTotal = sizeof(messagedef_t);
state = 2;
}
canWrite = 1;
}
if (state >= 2 && canWrite) {
n = send(sock, curPtr + bytesDone, bytesTotal - bytesDone, 0);
if (n<=0) {
/* socket was closed */
fprintf(stderr, "Cannot write to socket -- closing client connection.\n");
break;
}
bytesDone+=n;
if (bytesDone < bytesTotal) continue;
if (state==2 && respBufSize > 0) {
curPtr = (char *) response->buf;
bytesDone = 0;
bytesTotal = respBufSize;
state = 3;
continue;
}
/* Reaching this point means we are done with writing out the response,
so we will now free the allocated memory, and reset to state=0.
*/
if (response->buf) free(response->buf);
free(response->def);
free(response);
response = NULL;
state = 0;
curPtr = (char *) request.def;
bytesDone = 0;
bytesTotal = sizeof(messagedef_t);
}
}
pthread_mutex_lock(&SC->lock);
SC->numClients--;
pthread_mutex_unlock(&SC->lock);
closesocket(sock);
if (request.buf != nullptr) free(request.buf);
if (response != nullptr) {
if (response->buf != nullptr) free(response->buf);
if (response->def != nullptr) free(response->def);
free(response);
}
return NULL;
}
/***********************************************************************
* this thread listens to incoming TCP/UNIX domain socket connections
* if a connection is made by a client, it starts _buffer_socket_func
***********************************************************************/
void *_buffer_server_func(void *arg) {
ft_buffer_server_t *SC = (ft_buffer_server_t *) arg;
int n;
if (SC == nullptr) {
fprintf(stderr, "FieldTrip buffer server thread started with invalid argument\n");
return NULL;
}
while (SC->keepRunning) {
SOCKET c;
fd_set readSet;
int sel, rc, merge;
pthread_t tid;
ft_buffer_socket_t *CC;
struct timeval tv = {0,10000}; /* 10 ms for select timeout */
FD_ZERO(&readSet);
FD_SET(SC->serverSocket, &readSet);
sel = select((int) SC->serverSocket + 1, &readSet, nullptr, nullptr, &tv);
if (sel == 0) continue;
/* The following code portion looks weird because of the preprocessor
defines splitting an if/else clause, but it's just what we want:
On Windows, there is no if/else clause, and the second (TCP) bit
is always called. On POSIX systems, we branch depending on whether
the server runs a local domain socket or TCP socket.
*/
#ifndef WIN32
if (SC->isUnixDomain) {
struct sockaddr_un sa;
socklen_t size_sa = sizeof(sa);
c = accept(SC->serverSocket, (struct sockaddr *)&sa, &size_sa);
if (c == INVALID_SOCKET) {
perror("buffer_server, accept");
continue;
}
/* never merge packets for (local) UNIX sockets */
merge = 0;
} else
#endif
{
struct sockaddr_in sa;
socklen_t size_sa = sizeof(sa);
c = accept(SC->serverSocket, (struct sockaddr *)&sa, &size_sa);
if (c == INVALID_SOCKET) {
perror("buffer_server, accept");
continue;
}
/* enable packet merging only if it's not localhost */
merge = (sa.sin_addr.s_addr == htonl(INADDR_LOOPBACK)) ? 0 : 1;
}
CC = (ft_buffer_socket_t *) malloc(sizeof(ft_buffer_socket_t));
if (CC == nullptr) {
fprintf(stderr, "Out of memory\n");
closesocket(c);
continue;
}
CC->server = SC;
CC->clientSocket = c;
CC->mergePackets = merge;
rc = pthread_create(&tid, nullptr, _buffer_socket_func, CC);
if (rc) {
fprintf(stderr, "tcpserver: return code from pthread_create() is %d\n", rc);
closesocket(c);
free(CC);
}
}
while ((n=SC->numClients)>0) {
printf("Waiting for %i remaining client threads to stop...\n", n);
usleep(10000);
}
pthread_mutex_lock(&SC->lock);
SC->numClients--;
pthread_mutex_unlock(&SC->lock);
return NULL;
}
ft_buffer_server_t *ft_start_buffer_server(int port, const char *name, ft_request_callback_t callback, void *user_data) {
ft_buffer_server_t *SC;
int optval;
SOCKET s = INVALID_SOCKET;
SC = (ft_buffer_server_t *) malloc(sizeof(ft_buffer_server_t));
if (SC == nullptr) return NULL;
SC->callback = callback;
SC->user_data = user_data;
#ifdef WIN32
{
/* We only need to do this once ... and actually have a corresponding WSACleanup call somewhere */
static WSADATA wsa = {0,0};
if (wsa.wVersion == 0) {
if(WSAStartup(MAKEWORD(1, 1), &wsa)) {
fprintf(stderr, "ft_start_buffer_server: cannot start WIN32 sockets.\n");
goto cleanup;
}
}
}
#endif
/* setup socket */
if (port == 0) {
#ifdef WIN32
fprintf(stderr, "ft_start_buffer_server: invalid port number given.\n");
goto cleanup;
#else
struct sockaddr_un sa;
/* UNIX domain socket */
s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s == INVALID_SOCKET) {
perror("ft_start_buffer_server, socket");
goto cleanup;
}
sa.sun_family = AF_UNIX;
strncpy(sa.sun_path, name, sizeof(sa.sun_path));
if (bind(s, (struct sockaddr *) &sa, sizeof(sa)) == -1) {
perror("ft_start_buffer_server, bind");
goto cleanup;
}
SC->isUnixDomain = 0;
#endif
} else {
/* TCP socket */
struct sockaddr_in sa;
s = socket(PF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET) {
perror("ft_start_buffer_server, socket");
goto cleanup;
}
/* prevend "bind: address already in use" */
optval = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char*)&optval, sizeof(optval)) < 0) {
perror("ft_start_buffer_server, setsockopt");
goto cleanup;
}
bzero(&sa, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(s, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
perror("ft_start_buffer_server, bind");
goto cleanup;
}
SC->isUnixDomain = 0;
}
/* place the socket in non-blocking mode, required to do thread cancelation */
#ifdef WIN32
{
unsigned long enable = 0;
ioctlsocket(s, FIONBIO, &enable);
}
#else
optval = fcntl(s, F_GETFL, nullptr);
optval = optval | O_NONBLOCK;
if (fcntl(s, F_SETFL, optval)<0) {
perror("ft_start_buffer_server, fcntl");
goto cleanup;
}
#endif
if (listen(s, BACKLOG)<0) {
perror("ft_start_buffer_server, listen");
goto cleanup;
}
/* set some control variables */
SC->numClients = 0;
SC->keepRunning = 1;
SC->serverSocket = s;
SC->verbosity = 10; /* TODO: specify proper values */
/* create the mutex */
if (pthread_mutex_init(&SC->lock, nullptr) != 0) {
fprintf(stderr,"start_tcp_server: mutex could not be initialised\n");
goto cleanup;
}
/* create thread with default attributes */
if (pthread_create(&SC->threadID, nullptr, _buffer_server_func, SC) == 0) {
/* everything went fine - thread should be running now */
return SC;
}
fprintf(stderr,"start_tcp_server: could not spawn thread\n");
pthread_mutex_destroy(&SC->lock);
cleanup:
if (SC != nullptr) free(SC);
if (s != INVALID_SOCKET) {
#ifdef WIN32
shutdown(s, SD_BOTH);
#else
shutdown(s, SHUT_RDWR);
#endif
closesocket(s);
}
return NULL;
}
void ft_stop_buffer_server(ft_buffer_server_t *S) {
if (S == nullptr) return;
S->keepRunning = 0;
pthread_join(S->threadID, nullptr);
pthread_detach(S->threadID);
free(S);
}
@@ -0,0 +1,85 @@
/*
* Copyright (C) 2010, Stefan Klanke
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*/
#ifndef __socketserver_h
#define __socketserver_h
#include "buffer.h"
#include <pthread.h>
#define MERGE_THRESHOLD 4096 /* TODO: optimize this value? Maybe look at MTU size */
#ifdef __cplusplus
extern "C" {
#endif
#ifndef WIN32
#define INVALID_SOCKET -1
typedef int SOCKET;
#include <sys/un.h>
#else
typedef int socklen_t;
#endif
typedef int (*ft_request_callback_t)(const message_t *request, message_t **response, void *user_data);
/** The following structure is used for managing a server. The structure is
allocated and filled in ft_start_buffer_server and then passed on to the
actual server thread, as well as to all threads handling the client
connections. Thread functions are written such that they monitor the
"keepRunning" member of this structure, and once this is set to 0, the
threads stop and exit.
*/
typedef struct {
SOCKET serverSocket; ///< Socket the server listens on (TCP or UNIX domain)
int keepRunning; ///< Flag=1: thread functions keep looping, 0: threads exit
int numClients; ///< Current number of clients connected to the server
int verbosity; ///< Determines how much information is being printed during operation
int isUnixDomain; ///< 1: UNIX domain socket, 0: TCP socket
pthread_t threadID; ///< POSIX thread identifier of the server thread, client threads are detached immediately
pthread_mutex_t lock; ///< Mutex to protect the "numClients" member, commonly used by all threads
ft_request_callback_t callback; ///< Callback function to be called *instead* of dmarequest
void *user_data; ///< Pointer to user-defined data structure, passed on to callback
} ft_buffer_server_t;
/** Small helper structure that is passed to client threads. Get's allocated
using malloc() in the server thread, and disposed using free() in the client
thread.
*/
typedef struct {
ft_buffer_server_t *server; ///< Pointer to the common control structure
SOCKET clientSocket; ///< The newly created socket (from "accept")
int mergePackets; ///< 1: merge packets if total size below threshold, 0: never merge (=>local host)
} ft_buffer_socket_t;
/** Creates a server socket, binds it to the specified UNIX domain name or port,
starts listening on this socket, and spawns a background thread to serve
requests on this socket.
TCP sockets are created for positive port numbers (name is ignored),
UNIX domain sockets are created for port=0 (name needs to be a UNIX pathname).
If callback != nullptr, that function is called like
callback(request, &response, user_data)
for every request coming in over the socket, *instead* of the normal dmarequest.
Usually the user will call the latter function internally. Make sure your
callback is re-entrant!!!
Returns allocated control structure, or NULL in case of errors.
*/
ft_buffer_server_t *ft_start_buffer_server(int port, const char *name, ft_request_callback_t callback, void *user_data);
/** Stops background thread(s), closes the sockets, and disposes the control structure S.
S cannot be used anymore after this call.
*/
void ft_stop_buffer_server(ft_buffer_server_t *S);
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,104 @@
/*
* Copyright (C) 2008, Robert Oostenveld
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include <stdio.h>
#include <stdlib.h>
#include "buffer.h"
#define MERGE_THRESHOLD 4096 /* TODO: optimize this value? Maybe look at MTU size */
/*******************************************************************************
* communicate with the buffer through TCP
*******************************************************************************/
int tcprequest(int server, const message_t *request, message_t **response_ptr) {
unsigned int n, total;
/* this will hold the response */
message_t *response;
response = (message_t*)malloc(sizeof(message_t));
response->def = (messagedef_t*)malloc(sizeof(messagedef_t));
response->buf = NULL;
/* the response should be passed to the calling function, where it should be freed */
*response_ptr = response;
total = sizeof(messagedef_t) + request->def->bufsize;
/* Check whether request->def and request->buf are already contiguous in memory,
or whether request->buf is empty. If that's the case, we can write the request in one go.
*/
if (request->def->bufsize == 0 || (request->def+1) == (messagedef_t *) request->buf) {
if ((n = bufwrite(server, request->def, total)) != total) {
fprintf(stderr, "write size = %d, should be %d\n", n, total);
goto cleanup;
}
}
/* Now check whether the total size is below the merge threshold, in which case
we'll copy it to contiguous memory and again send it in one go
*/
else if (total <= MERGE_THRESHOLD) {
char merged[MERGE_THRESHOLD];
memcpy(merged, request->def, sizeof(messagedef_t));
memcpy(merged + sizeof(messagedef_t), request->buf, request->def->bufsize);
if ((n = bufwrite(server, merged, total)) != total) {
fprintf(stderr, "write size = %d, should be %d\n", n, total);
goto cleanup;
}
}
/* Otherwise, send "def" and "buf" in separate pieces. This might introduce latencies
if the other end runs Windows :-(
*/
else {
/* send the request to the server, first the message definition */
if ((n = bufwrite(server, request->def, sizeof(messagedef_t)))!=sizeof(messagedef_t)) {
fprintf(stderr, "write size = %d, should be %lu\n", n, (long unsigned int)sizeof(messagedef_t));
goto cleanup;
}
/* send the request to the server, then the message payload */
if ((n = bufwrite(server, request->buf, request->def->bufsize))!=request->def->bufsize) {
fprintf(stderr, "write size = %d, should be %d\n", n, request->def->bufsize);
goto cleanup;
}
}
/* read the response from the server, first the message definition */
if ((n = bufread(server, response->def, sizeof(messagedef_t))) != sizeof(messagedef_t)) {
fprintf(stderr, "packet size = %d, should be %lu\n", n, (long unsigned int)sizeof(messagedef_t));
goto cleanup;
}
if (response->def->version!=VERSION) {
fprintf(stderr, "incorrect version\n");
goto cleanup;
}
/* read the response from the server, then the message payload */
if (response->def->bufsize>0) {
response->buf = malloc(response->def->bufsize);
if ((n = bufread(server, response->buf, response->def->bufsize)) != response->def->bufsize) {
fprintf(stderr, "read size = %d, should be %d\n", n, response->def->bufsize);
goto cleanup;
}
}
/* everything went fine, return with the response */
/* print_response(response->def); */
return 0;
cleanup:
/* there was a problem, clear the response and return */
FREE(response->def);
FREE(response->buf);
FREE(response);
*response_ptr = NULL; /* SK: this was missing a "*", effectively never really returning 0 */
return -1;
}
@@ -0,0 +1,4 @@
/*
* This will contain the implementation of the TiA server on top of the FieldTrip buffer, similar to the RDA server.
*
*/
@@ -0,0 +1,4 @@
/*
* This will contain the implementation of the TiA server on top of the FieldTrip buffer, similar to the RDA server.
*
*/
@@ -0,0 +1,482 @@
/*
* Copyright (C) 2008, Robert Oostenveld & Christian Hesse
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* for strerror */
#include "buffer.h"
#include <pthread.h>
#include "extern.h"
unsigned int bufread(int s, void* buf, unsigned int numel)
{
unsigned int numcall = 0, numread = 0, verbose = 0;
int numthis = 0;
while (numread < numel)
{
numthis = recv(s, (char*)buf + numread, numel - numread, 0);
if (numthis < 0)
{
perror("bufread");
break;
}
else if (numthis == 0) { break; }
if (verbose > 0) { fprintf(stderr, "bufread: read %d bytes\n", numthis); }
numread += numthis;
numcall++;
#ifndef PLATFORM_WIN32 /* SK: I think this shouldn't be necessary on any platform: the sockets are blocking */
if (numread < numel) { usleep(1000); }
#endif
}
if (verbose > 1) { fprintf(stderr, "bufread: reading the complete buffer required %d calls\n", numcall); }
return numread;
}
unsigned int bufwrite(int s, const void* buf, unsigned int numel)
{
int numthis = 0;
unsigned int numcall = 0, numwrite = 0, verbose = 0;
while (numwrite < numel)
{
numthis = send(s, (char*)buf + numwrite, numel - numwrite, 0);
if (numthis < 0)
{
perror("bufwrite");
break;
}
else if (numthis == 0) { break; }
if (verbose) fprintf(stderr, "bufwrite: wrote %d bytes\n", numthis);
numwrite += numthis;
numcall++;
#ifndef PLATFORM_WIN32 /* SK: I think this shouldn't be necessary on any platform: the sockets are blocking */
if (numwrite < numel) { usleep(1000); }
#endif
}
if (verbose > 1) { fprintf(stderr, "bufwrite: writing the complete buffer required %d calls\n", numcall); }
return numwrite;
}
unsigned int append(void** buf1, unsigned int bufsize1, void* buf2, unsigned int bufsize2)
{
int verbose = 0;
if (verbose > 1)
{
pthread_mutex_lock(&mutexappendcount);
appendcount++;
fprintf(stderr, "append: appendcount = %d\n", appendcount);
pthread_mutex_unlock(&mutexappendcount);
}
if (((*buf1) != nullptr) && (bufsize1 == 0))
{
perror("append err1");
return 0; /* was -1, but this is never checked anyway */
}
else if (((*buf1) == nullptr) && (bufsize1 != 0))
{
perror("append err2");
return 0; /* was -1, but this is never checked anyway */
}
if ((*buf1) == nullptr)
{
if (verbose > 0) { fprintf(stderr, "append: allocating %d bytes\n", bufsize2); }
(*buf1) = malloc(bufsize2);
}
else if ((*buf1) != nullptr)
{
if (verbose > 0) { fprintf(stderr, "append: reallocating from %d to %d bytes\n", bufsize1, bufsize1 + bufsize2); }
(*buf1) = realloc((*buf1), bufsize1 + bufsize2);
}
memcpy((char*)(*buf1) + bufsize1, buf2, bufsize2);
return (bufsize1 + bufsize2);
}
int close_connection(int s)
{
int status = 0, verbose = 0;
if (verbose > 0) { fprintf(stderr, "close_connection: socket = %d\n", s); }
if (s > 0) status = closesocket(s); /* it is a TCP connection */
if (status != 0) { perror("close_connection"); }
return status;
}
int open_connection(const char* hostname, int port)
{
int verbose = 0;
int s, retry;
struct sockaddr_in sa;
struct hostent* host;
#ifdef WIN32
static WSADATA wsa = { 0, 0 }; /* check version fields to only initialise once */
#endif
if (port == 0)
{
if (verbose > 0) { fprintf(stderr, "open_connection: using direct memory copy\n"); }
return 0;
}
else { if (verbose > 0) { fprintf(stderr, "open_connection: server = %s, port = %d\n", hostname, port); } }
#ifdef WIN32
if (wsa.wVersion == 0)
{
/* We only need to do this once ... and actually have a corresponding WSACleanup call somewhere */
if (WSAStartup(MAKEWORD(1, 1), &wsa))
{
fprintf(stderr, "open_connection: cannot start sockets\n");
/* FIXME should this exception be handled more explicitely? */
}
}
#endif
if ((host = gethostbyname(hostname)) == nullptr)
{
fprintf(stderr, "open_connection: nslookup1 failed on '%s'\n", hostname);
return -1;
}
if (host->h_length == 0)
{
fprintf(stderr, "open_connection: nslookup2 failed on '%s'\n", hostname);
return -1;
}
bzero(&sa, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
memcpy(&(sa.sin_addr.s_addr), host->h_addr_list[0], sizeof(sa.sin_addr.s_addr));
if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
if (verbose > 0) { fprintf(stderr, "open_connection: socket = %d\n", s); }
perror("open_connection");
return -1;
}
retry = 10;
while (retry > 0)
{
if (connect(s, (struct sockaddr*)&sa, sizeof sa) < 0)
{
/* wait 5 miliseconds and try again */
usleep(5000);
retry--;
}
else
{
/* this signals that the connection has been made */
retry = -1;
}
}
if (retry == 0)
{
/* close the socket */
closesocket(s);
/* it failed on mutliple attempts, give up */
return -2;
}
/*
while (connect(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
perror("open_connection connect");
usleep(1000000);
}
*/
if (verbose > 0) { fprintf(stderr, "open_connection: connected to %s:%d on socket %d\n", hostname, port, s); }
#ifdef DISABLE_NAGLE
{
int optval = 1;
setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval));
}
#endif
return s;
}
void check_datatypes()
{
/* check datatypes */
if (WORDSIZE_CHAR != 1)
{
fprintf(stderr, "invalid size of CHAR (%d)\n", (int)WORDSIZE_CHAR);
exit(-1);
}
if (WORDSIZE_UINT8 != 1)
{
fprintf(stderr, "invalid size of UINT8 (%d)\n", (int)WORDSIZE_UINT8);
exit(-1);
}
if (WORDSIZE_UINT16 != 2)
{
fprintf(stderr, "invalid size of UINT16 (%d)\n", (int)WORDSIZE_UINT16);
exit(-1);
}
if (WORDSIZE_UINT32 != 4)
{
fprintf(stderr, "invalid size of UINT32 (%d)\n", (int)WORDSIZE_UINT32);
exit(-1);
}
if (WORDSIZE_UINT64 != 8)
{
fprintf(stderr, "invalid size of UINT64 (%d)\n", (int)WORDSIZE_UINT64);
exit(-1);
}
if (WORDSIZE_INT8 != 1)
{
fprintf(stderr, "invalid size of INT8 (%d)\n", (int)WORDSIZE_INT8);
exit(-1);
}
if (WORDSIZE_INT16 != 2)
{
fprintf(stderr, "invalid size of INT16 (%d)\n", (int)WORDSIZE_INT16);
exit(-1);
}
if (WORDSIZE_INT32 != 4)
{
fprintf(stderr, "invalid size of INT32 (%d)\n", (int)WORDSIZE_INT32);
exit(-1);
}
if (WORDSIZE_INT64 != 8)
{
fprintf(stderr, "invalid size of INT64 (%d)\n", (int)WORDSIZE_INT64);
exit(-1);
}
if (WORDSIZE_FLOAT32 != 4)
{
fprintf(stderr, "invalid size of FLOAT32 (%d)\n", (int)WORDSIZE_FLOAT32);
exit(-1);
}
if (WORDSIZE_FLOAT64 != 8)
{
fprintf(stderr, "invalid size of FLOAT64 (%d)\n", (int)WORDSIZE_FLOAT64);
exit(-1);
}
if (sizeof(messagedef_t) != 8)
{
fprintf(stderr, "invalid size of messagedef_t\n");
exit(-1);
}
if (sizeof(headerdef_t) != 24)
{
fprintf(stderr, "invalid size of headerdef_t \n");
exit(-1);
}
if (sizeof(datadef_t) != 16)
{
fprintf(stderr, "invalid size of datadef_t \n");
exit(-1);
}
if (sizeof(eventdef_t) != 32)
{
fprintf(stderr, "invalid size of eventdef_t \n");
exit(-1);
}
if (sizeof(datasel_t) != 8)
{
fprintf(stderr, "invalid size of datasel_t \n");
exit(-1);
}
if (sizeof(eventsel_t) != 8)
{
fprintf(stderr, "invalid size of eventsel_t \n");
exit(-1);
}
}
unsigned int wordsize_from_type(UINT32_T data_type)
{
switch (data_type)
{
case DATATYPE_CHAR: return WORDSIZE_CHAR;
case DATATYPE_UINT8:
case DATATYPE_INT8: return WORDSIZE_INT8;
case DATATYPE_UINT16:
case DATATYPE_INT16: return WORDSIZE_INT16;
case DATATYPE_UINT32:
case DATATYPE_INT32: return WORDSIZE_INT32;
case DATATYPE_UINT64:
case DATATYPE_INT64: return WORDSIZE_INT64;
case DATATYPE_FLOAT32: return WORDSIZE_FLOAT32;
case DATATYPE_FLOAT64: return WORDSIZE_FLOAT64;
}
return 0;
}
const ft_chunk_t* find_chunk(const void* buf, unsigned int offset0, unsigned int size, UINT32_T chunk_type)
{
unsigned int bufpos = offset0;
while (bufpos + sizeof(ft_chunkdef_t) <= size)
{
const ft_chunk_t* chunk = (ft_chunk_t*)((char*)buf + bufpos);
if (chunk->def.type == chunk_type) { return chunk; }
bufpos += sizeof(ft_chunkdef_t) + chunk->def.size;
}
return nullptr;
}
/** Iterate through an array of events and check whether all of them are properly defined,
that is, whether the "type" and "value" fields are of valid type and size, and whether the
"bufsize" fields are correct (that is, fully contained in the passed buffer, and big enough
to hold "type" and "value".
Returns the number of events on success (might also be 0), or
a negative number that indicates in which event definition an error happend,
for example, a return value of -2 means that the first event was ok, but the second event
definition was invalid. This function returns at the first error.
*/
int check_event_array(unsigned int size, const void* buf)
{
unsigned int offset = 0;
int numEvents = 0;
while (offset + sizeof(eventdef_t) <= size)
{
const eventdef_t* E;
unsigned int wsType, wsValue;
/* Set our event pointer to the current location within the array */
E = (const eventdef_t*)((char*)buf + offset);
/* Increase the offset by the size of this event, and check whether it's fully
contained within the given array.
*/
offset += sizeof(eventdef_t) + E->bufsize;
if (offset > size) { goto error; }
/* Check whether "type" and "value" are of known type */
wsType = wordsize_from_type(E->type_type);
if (wsType == 0) { goto error; }
wsValue = wordsize_from_type(E->value_type);
if (wsValue == 0) { goto error; }
/* Check whether "type" and "value" are contained in this event's "buf" */
if (wsType * E->type_numel + wsValue * E->value_numel > E->bufsize) { goto error; }
/* all checks passed, continue at next offset */
++numEvents;
}
return numEvents;
error:
return -(1 + numEvents);
}
#ifdef WIN32
int open_unix_connection(const char* name) { return -1; }
#else
int open_unix_connection(const char* name)
{
int verbose = 0;
int s, retry;
struct sockaddr_un sa;
bzero(&sa, sizeof(sa));
sa.sun_family = AF_UNIX;
strncpy(sa.sun_path, name, sizeof(sa.sun_path));
s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s < 0) {
perror("open_unix_connection, socket");
return -1;
}
retry = 10;
while (retry > 0) {
if (connect(s, (struct sockaddr*) & sa, sizeof(sa)) < 0) {
/* wait 5 miliseconds and try again */
perror("open_connection");
usleep(5000);
retry--;
}
else {
/* this signals that the connection has been made */
retry = -1;
}
}
if (retry == 0) {
/* it failed on mutliple attempts, give up */
return -2;
}
if (verbose > 0)
fprintf(stderr, "open_unix_connection: connected to %s on socket %d\n", name, s);
return s;
}
#endif
#ifdef WIN32
#ifndef COMPILER_MINGW
/*
* timeval.h 1.0 01/12/19
*
* Defines gettimeofday, timeval, etc. for Win32
*
* By Wu Yongwei
*
*/
//#define EPOCHFILETIME (116444736000000000i64)
#define EPOCHFILETIME ((INT64_T) 116444736000000000LL)
#ifdef COMPILER_LCC
VOID STDCALL GetSystemTimeAsFileTime(LPFILETIME);
#endif
int gettimeofday(struct timeval* tv, struct timezone* tz)
{
FILETIME ft;
LARGE_INTEGER li;
INT64_T t;
static int tzflag;
if (tv)
{
GetSystemTimeAsFileTime(&ft);
li.LowPart = ft.dwLowDateTime;
li.HighPart = ft.dwHighDateTime;
t = li.QuadPart; /* In 100-nanosecond intervals */
t -= EPOCHFILETIME; /* Offset to the Epoch time */
t /= 10; /* In microseconds */
tv->tv_sec = (long)(t / 1000000);
tv->tv_usec = (long)(t % 1000000);
}
#ifndef COMPILER_LCC
/* LCC that comes with Matlab has problems with _timezone and _daylight,
and we don't need it anyway */
if (tz)
{
if (!tzflag)
{
_tzset();
tzflag++;
}
tz->tz_minuteswest = _timezone / 60;
tz->tz_dsttime = _daylight;
}
#endif
return 0;
}
#endif
#endif
@@ -0,0 +1,80 @@
/* SK: This has now been moved into util.c for simplification of compile scripts and Makefiles */
/*
* timeval.h 1.0 01/12/19
*
* Defines gettimeofday, timeval, etc. for Win32
*
* By Wu Yongwei
*
*/
#ifndef _TIMEVAL_H
#define _TIMEVAL_H
#if TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#elif HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
#if ! HAVE_GETTIMEOFDAY || MINGW
#if __WIN32__
#include <windows.h>
#else
#include <errno.h>
#endif
#ifndef __GNUC__
#define EPOCHFILETIME (116444736000000000i64)
#else
#define EPOCHFILETIME (116444736000000000LL)
#endif
#include "gettimeofday.h"
/*!
\brief A Windows gettimeofday implementation.
*/
int gettimeofday(struct timeval *tv, struct timezone *tz)
{
#if __WIN32__
FILETIME ft;
LARGE_INTEGER li;
__int64 t;
static int tzflag;
if (tv) {
GetSystemTimeAsFileTime(&ft);
li.LowPart = ft.dwLowDateTime;
li.HighPart = ft.dwHighDateTime;
t = li.QuadPart; /* In 100-nanosecond intervals */
t -= EPOCHFILETIME; /* Offset to the Epoch time */
t /= 10; /* In microseconds */
tv->tv_sec = (long)(t / 1000000);
tv->tv_usec = (long)(t % 1000000);
}
if (tz) {
if (!tzflag) {
_tzset();
tzflag++;
}
tz->tz_minuteswest = _timezone / 60;
tz->tz_dsttime = _daylight;
}
return 0;
#else
errno = ENOSYS;
return -1;
#endif
}
#endif
#endif
@@ -0,0 +1,12 @@
#ifndef __gettimeofday_h
#define __gettimeofday_h
#include <time.h>
struct timezone {
int tz_minuteswest; /* minutes W of Greenwich */
int tz_dsttime; /* type of dst correction */
};
int gettimeofday(struct timeval *tv, struct timezone *tz);
#endif
@@ -0,0 +1,61 @@
#include <winsock2.h>
#include "poll.h"
int poll (struct pollfd *p, int num, int timeout)
{
struct timeval tv;
fd_set read, write, except;
int i, n, ret;
char buf[1024];
FD_ZERO (&read);
FD_ZERO (&write);
FD_ZERO (&except);
n = -1;
for (i = 0; i < num; ++i)
{
if (p[i].fd < 0)
continue;
if (p[i].events & POLLIN)
FD_SET (p[i].fd, &read);
if (p[i].events & POLLOUT)
FD_SET (p[i].fd, &write);
if (p[i].events & POLLERR)
FD_SET (p[i].fd, &except);
if (p[i].fd > n)
n = p[i].fd;
}
if (n == -1)
return (0);
if (timeout < 0)
ret = select (n+1, &read, &write, &except, nullptr);
else
{
tv.tv_sec = timeout / 1000;
tv.tv_usec = 1000 * (timeout % 1000);
ret = select (n+1, &read, &write, &except, &tv);
}
for (i = 0; ret >= 0 && i < num; ++i)
{
p[i].revents = 0;
if (FD_ISSET (p[i].fd, &read))
{
int j = recv(p[i].fd, buf, 1024, MSG_PEEK);
if(j>0)
p[i].revents |= POLLIN;
else
p[i].revents |= POLLHUP;
}
if (FD_ISSET (p[i].fd, &write))
p[i].revents |= POLLOUT;
if (FD_ISSET (p[i].fd, &except))
p[i].revents |= POLLERR;
}
return (ret);
}
@@ -0,0 +1,50 @@
#if defined(TARGET_HAS_PThread)
#include "ovasCConfigurationFieldtrip.h"
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <fstream>
#include <list>
namespace OpenViBE {
namespace AcquisitionServer {
bool CConfigurationFieldtrip::preConfigure()
{
const bool res = CConfigurationBuilder::preConfigure();
m_pHostName = GTK_WIDGET(gtk_builder_get_object(m_builder, "entry_host_name"));
m_pHostPort = GTK_WIDGET(gtk_builder_get_object(m_builder, "spinbutton_host_port"));
m_pMinSamples = GTK_WIDGET(gtk_builder_get_object(m_builder, "spinbutton_minSamples"));
m_pSRCorrection = GTK_WIDGET(gtk_builder_get_object(m_builder, "checkbutton_SRCorrection"));
gtk_spin_button_set_range(GTK_SPIN_BUTTON(m_pMinSamples), 1.0, 10000.0);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_pMinSamples), m_minSamples);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_pHostPort), m_hostPort);
gtk_entry_set_text(GTK_ENTRY(m_pHostName), m_hostName.toASCIIString());
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pSRCorrection), m_srCorrection);
return res;
}
bool CConfigurationFieldtrip::postConfigure()
{
if (m_applyConfig)
{
gtk_spin_button_update(GTK_SPIN_BUTTON(m_pMinSamples));
gtk_spin_button_update(GTK_SPIN_BUTTON(m_pHostPort));
m_minSamples = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(m_pMinSamples));
m_hostPort = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(m_pHostPort));
m_hostName = gtk_entry_get_text(GTK_ENTRY(m_pHostName));
m_srCorrection = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pSRCorrection)) > 0);
}
return CConfigurationBuilder::postConfigure();
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif //#if defined(TARGET_HAS_PThread)
@@ -0,0 +1,59 @@
#pragma once
#if defined(TARGET_HAS_PThread)
#include "../ovasCConfigurationBuilder.h"
namespace OpenViBE
{
namespace AcquisitionServer
{
/**
* \class CConfigurationFieldtrip
* \author Amelie Serpollet (CEA/LETI/CLINATEC)
* \date Mon May 23 09:48:21 2011
* \brief The CDriverFieldtrip allows the acquisition server to acquire data from a Fieldtrip buffer.
*
*/
class CConfigurationFieldtrip final : public CConfigurationBuilder
{
public:
CConfigurationFieldtrip(const char* gtkBuilderFilename) : CConfigurationBuilder(gtkBuilderFilename) { }
~CConfigurationFieldtrip() override { }
void setHostName(const CString& hostName) { m_hostName = hostName; }
void setHostPort(const uint32_t hostPort) { m_hostPort = hostPort; }
void setMinSamples(const uint32_t minSamples) { m_minSamples = minSamples; }
void setSRCorrection(const bool bSRCorrection) { m_srCorrection = bSRCorrection; }
CString getHostName() const { return m_hostName; }
uint32_t getHostPort() const { return m_hostPort; }
uint32_t getMinSamples() const { return m_minSamples; }
bool getSRCorrection() const { return m_srCorrection; }
protected:
bool preConfigure() override;
bool postConfigure() override;
private:
CConfigurationFieldtrip();
protected:
GtkWidget* m_pHostName = nullptr;
GtkWidget* m_pHostPort = nullptr;
GtkWidget* m_pMinSamples = nullptr;
GtkWidget* m_pSRCorrection = nullptr;
CString m_hostName = "localhost";
uint32_t m_hostPort = 4000;
uint32_t m_minSamples = 1;
bool m_srCorrection = true;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // #if defined(TARGET_HAS_PThread)
@@ -0,0 +1,533 @@
/* This driver uses the FieldTrip buffer open source library.
* See http://www.ru.nl/fcdonders/fieldtrip for details.
*/
#if defined(TARGET_HAS_PThread)
#include "ovasCDriverFieldtrip.h"
#include "ovasCConfigurationFieldtrip.h"
#include <toolkit/ovtk_all.h>
#include <pthread.h>
#include "fieldtrip/buffer.h"
#include "fieldtrip/extern.h"
#include "fieldtrip/extern.c"
#include "fieldtrip/util.c"
#include "fieldtrip/printstruct.c"
#include "fieldtrip/tcprequest.c"
#include "fieldtrip/dmarequest.c"
#include "fieldtrip/clientrequest.c"
#include <system/ovCTime.h>
//#include "GetCpuTime.h"
namespace OpenViBE {
namespace AcquisitionServer {
CDriverFieldtrip::CDriverFieldtrip(IDriverContext& ctx)
: IDriver(ctx), m_settings("AcquisitionServer_Driver_FieldTrip", m_driverCtx.getConfigurationManager())
{
m_header.setSamplingFrequency(0);
m_header.setChannelCount(0);
m_waitDataRequest = new message_t();
m_waitDataRequest->def = new messagedef_t();
m_waitDataRequest->buf = nullptr;
m_getDataRequest = new message_t();
m_getDataRequest->def = new messagedef_t();
m_getDataRequest->buf = nullptr;
m_settings.add("Header", &m_header);
m_settings.add("MinSamples", &m_minSamples);
m_settings.add("PortNumber", &m_portNumber);
m_settings.add("HostName", &m_hostName);
m_settings.add("CorrectNonIntegerSR", &m_correctNonIntegerSR);
m_settings.load();
}
CDriverFieldtrip::~CDriverFieldtrip()
{
if (m_waitDataRequest)
{
//m_waitDataRequest->buf deleted with m_waitDataRequest->def
if (m_waitDataRequest->def) { delete m_waitDataRequest->def; }
delete m_waitDataRequest;
}
if (m_getDataRequest)
{
//m_getDataRequest->buf deleted with m_getDataRequest->def
if (m_getDataRequest->def) { delete m_getDataRequest->def; }
delete m_getDataRequest;
}
}
const char* CDriverFieldtrip::getName() { return "Fieldtrip Driver"; }
//___________________________________________________________________//
// //
bool CDriverFieldtrip::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
{
if (m_driverCtx.isConnected()) { return false; }
// ...
// initialize hardware and get available header information
// from it :
// connect to buffer
if (m_connectionID != -1)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Already connected to Fieldtrip buffer " << m_hostName << ":" << m_portNumber << "\n";
return false;
}
m_connectionID = open_connection(m_hostName.toASCIIString(), int(m_portNumber));
if (m_connectionID < 0)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to connect to Fieldtrip buffer :\n" << m_hostName << ":" << m_portNumber << "\n";
m_connectionID = -1;
return false;
}
else { m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Connection to Fieldtrip buffer succeeded !\n"; }
// request header
if (!requestHeader())
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Request header failed, disconnecting.\n";
if (close_connection(m_connectionID) != 0)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to disconnect correctly from Fieldtrip buffer\n";
}
m_connectionID = -1;
return false;
}
if (!m_header.isChannelCountSet() || !m_header.isSamplingFrequencySet()) { return false; }
// Builds up a buffer to store acquired samples. This buffer
// will be sent to the acquisition server later...
m_sample = new float[m_header.getChannelCount() * nSamplePerSentBlock];
if (!m_sample)
{
delete [] m_sample;
m_sample = nullptr;
return false;
}
// Saves parameters
m_callback = &callback;
m_nSamplePerSentBlock = nSamplePerSentBlock;
if (m_minSamples < 1) { m_minSamples = 1; }
if (m_minSamples > m_nSamplePerSentBlock) { m_minSamples = m_nSamplePerSentBlock; }
return true;
}
bool CDriverFieldtrip::start()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// ...
// request hardware to start
// sending data
// ...
m_firstGetDataRequest = true;
m_waitingTimeMs = (m_header.getSamplingFrequency() > 1000 ? 1 : (1000 / m_header.getSamplingFrequency())
); //time for 1 sample if >= 1ms //(1000*m_nSamplePerSentBlock)
m_nTotalSample = 0;
m_diffPerSample = (m_realSampling - m_header.getSamplingFrequency()) / m_realSampling;
if (m_diffPerSample <= 0.0) { m_diffPerSample = 0.0; }
m_driftSinceLastCorrection = 0.0;
return true;
}
bool CDriverFieldtrip::loop()
{
if (!m_driverCtx.isConnected()) { return false; }
if (!m_driverCtx.isStarted()) { return true; }
CStimulationSet stimSet;
stimSet.setStimulationCount(0);
// ...
// receive samples from hardware
// put them the correct way in the sample array
// whether the buffer is full, send it to the acquisition server
//...
const int count = requestChunk(stimSet);
if (count < 0) { return false; }
if (count == 0) { return true; }
m_callback->setSamples(m_sample, count);
m_callback->setStimulationSet(stimSet);
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
return true;
}
bool CDriverFieldtrip::stop()
{
if (!m_driverCtx.isConnected()) { return false; }
if (!m_driverCtx.isStarted()) { return false; }
return true;
}
bool CDriverFieldtrip::uninitialize()
{
if (!m_driverCtx.isConnected()) { return false; }
if (m_driverCtx.isStarted()) { return false; }
if (close_connection(m_connectionID) != 0) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to disconnect correctly from Fieldtrip buffer\n"; }
m_connectionID = -1;
delete [] m_sample;
m_sample = nullptr;
m_callback = nullptr;
return true;
}
//___________________________________________________________________//
// //
bool CDriverFieldtrip::isConfigurable()
{
return true; // change to false if your device is not configurable
}
bool CDriverFieldtrip::configure()
{
CConfigurationFieldtrip config(Directories::getDataDir() + "/applications/acquisition-server/interface-Fieldtrip.ui");
config.setMinSamples(m_minSamples);
config.setHostPort(m_portNumber);
config.setHostName(m_hostName);
config.setSRCorrection(m_correctNonIntegerSR);
if (config.configure(m_header))
{
m_minSamples = config.getMinSamples();
m_portNumber = config.getHostPort();
m_hostName = config.getHostName();
m_correctNonIntegerSR = config.getSRCorrection();
m_settings.save();
return true;
}
return false;
}
//___________________________________________________________________//
// //
bool CDriverFieldtrip::requestHeader()
{
m_waitDataRequest->def->command = GET_HDR;
m_waitDataRequest->def->version = VERSION;
m_waitDataRequest->def->bufsize = 0;
m_waitDataRequest->buf = nullptr;
message_t* response = nullptr;
const int res = clientrequest(m_connectionID, m_waitDataRequest, &response);
if (res != 0)
{
FreeResponse(response, "Error while asking for header. Buffer aborted ?");
return false;
}
else if (response == nullptr || response->def == nullptr)
{
FreeResponse(response, "Error while asking for header");
return false;
}
else if (response->def->command != GET_OK || response->def->bufsize == 0)
{
FreeResponse(response, "No header in the buffer");
return false;
}
else
{
const unsigned int size = response->def->bufsize;
headerdef_t* headerDef = (headerdef_t*)response->buf;
if (size < sizeof(headerdef_t))
{
FreeResponse(response, "Header received has wrong format");
return false;
}
m_header.setSamplingFrequency(uint32_t(headerDef->fsample));
m_realSampling = headerDef->fsample;
m_header.setChannelCount(headerDef->nchans);
m_dataType = headerDef->data_type;
if (m_dataType != DATATYPE_FLOAT32 && m_dataType != DATATYPE_FLOAT64)
{
FreeResponse(response, "Data type is not supported");
return false;
}
if (size == sizeof(headerdef_t)) //no chunk attached to the header
{
for (uint32_t i = 0; i < headerDef->nchans; i++) { m_header.setChannelName(i, ("Channel " + std::to_string(i)).c_str()); }
}
else //chunk(s) attached to the header, maybe channel names
{
int bytesInHeaderBuffer = headerDef->bufsize;
void* chunk = (headerdef_t*)headerDef + 1;
bool foundChannelNames = false;
while (bytesInHeaderBuffer > 0)
{
if (((ft_chunk_t*)chunk)->def.type == FT_CHUNK_CHANNEL_NAMES)
{
foundChannelNames = true;
char* chunkdata = ((ft_chunk_t*)chunk)->data;
for (uint32_t i = 0; i < headerDef->nchans; i++)
{
std::string name = chunkdata;
m_header.setChannelName(i, name.c_str());
chunkdata = (char*)chunkdata + name.size() + 1;
}
}
bytesInHeaderBuffer -= ((ft_chunk_t*)chunk)->def.size + sizeof(ft_chunkdef_t);
if (bytesInHeaderBuffer > 0) { chunk = (char*)chunk + ((ft_chunk_t*)chunk)->def.size + sizeof(ft_chunkdef_t); }
}
if (!foundChannelNames)
{
for (uint32_t i = 0; i < headerDef->nchans; i++) { m_header.setChannelName(i, ("Channel " + std::to_string(i)).c_str()); }
}
}
} /* end valid header */
FreeResponse(response, nullptr);
return true;
}
int CDriverFieldtrip::requestChunk(CStimulationSet& oStimulationSet)
{
//There are two basic opertations:
// - configure m_getDataRequest and m_waitDataRequest
// - use m_getDataRequest to call fieldtrip clientrequest() that gets the data from fieldtrip
// - transpose the data to fill m_sample in the correct way expected by OpenVube
//The rest are data validity checks and correction for non-integer sampling frequency
//configure "wait data" request
m_waitDataRequest->def->command = WAIT_DAT;
m_waitDataRequest->def->version = VERSION;
if (m_waitDataRequest->buf == nullptr)
{
m_waitDataRequest->def->bufsize = 0;
waitdef_t* waitDef = new waitdef_t();
unsigned int requestSize = 0;
requestSize = append((void**)&m_waitDataRequest->def, sizeof(messagedef_t), waitDef, sizeof(waitdef_t));
m_waitDataRequest->def->bufsize = requestSize - sizeof(messagedef_t);
m_waitDataRequest->buf = (messagedef_t*)m_waitDataRequest->def + 1;
}
waitdef_t* waitDef = (waitdef_t*)m_waitDataRequest->buf;
waitDef->threshold.nevents = 0xFFFFFFFF;
waitDef->threshold.nsamples = m_nSamplePerSentBlock;
waitDef->milliseconds = m_waitingTimeMs;
message_t* response = nullptr;
int res = clientrequest(m_connectionID, m_waitDataRequest, &response);
uint32_t nDataReceived = 0;
uint32_t nDataToSend = 0;
if (res)
{
FreeResponse(response, "Error while asking for data. Buffer aborted ?");
return -1;
}
else if (response == nullptr || response->def == nullptr)
{
FreeResponse(response, "Error while asking for data");
return -1;
}
else if (response->def->command != WAIT_OK || response->def->bufsize != 8 || response->buf == nullptr)
{
FreeResponse(response, "No header in buffer anymore");
return -1;
}
else
{
// new header received ? stop acquisition
if (((samples_events_t*)response->buf)->nsamples < m_nTotalSample)
{
FreeResponse(response, "End of data");
return -1;
}
// no new data
if (((samples_events_t*)response->buf)->nsamples <= m_nTotalSample + m_minSamples)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "No new data\n";
FreeResponse(response, nullptr);
return 0;
}
// get data
uint32_t lastSample = ((samples_events_t*)response->buf)->nsamples;
if (lastSample > m_nTotalSample + m_nSamplePerSentBlock)
{
if (m_firstGetDataRequest) { m_nTotalSample = lastSample - m_nSamplePerSentBlock; }
else { lastSample = m_nTotalSample + m_nSamplePerSentBlock; }
}
if (m_firstGetDataRequest) { m_firstGetDataRequest = false; }
FreeResponse(response, nullptr); //prevents memory leak
// "get data" request
m_getDataRequest->def->command = GET_DAT;
m_getDataRequest->def->version = VERSION;
if (m_getDataRequest->buf == nullptr)
{
m_getDataRequest->def->bufsize = 0;
datasel_t* dataSel = new datasel_t();
unsigned int requestSize = 0;
requestSize = append((void**)&m_getDataRequest->def, sizeof(messagedef_t), dataSel, sizeof(datasel_t));
m_getDataRequest->def->bufsize = requestSize - sizeof(messagedef_t);
m_getDataRequest->buf = (messagedef_t*)m_getDataRequest->def + 1;
}
datasel_t* dataSel = (datasel_t*)m_getDataRequest->buf;
dataSel->begsample = m_nTotalSample;
dataSel->endsample = lastSample - 1;
//actual data acquisition
res = clientrequest(m_connectionID, m_getDataRequest, &response);
if (res || !response || !response->def || response->def->version != VERSION)
{
FreeResponse(response, "Error while asking for data");
return -1;
}
else if (response->def->command != GET_OK || response->def->bufsize == 0 || response->buf == nullptr)
{
FreeResponse(response, "Data are not available anymore");
return -1;
}
else // data received
{
datadef_t* datadef = (datadef_t*)response->buf;
void* databuf = (datadef_t*)response->buf + 1;
if (datadef->bufsize / (wordsize_from_type(datadef->data_type) * datadef->nchans) != lastSample - m_nTotalSample)
{
FreeResponse(response, "Data received from buffer are invalid");
return -1;
}
else // data correct
{
nDataReceived = lastSample - m_nTotalSample;
// Delete some samples if necessary.
// Sampling rate is converted into integer in openvibe,
// so we can have up to 1 sample too many per second.
nDataToSend = nDataReceived;
if (m_correctNonIntegerSR)
{
m_driftSinceLastCorrection += (m_diffPerSample * nDataReceived);
if (m_driftSinceLastCorrection >= 1.0)
{
// delete samples
const uint32_t diffSamples = uint32_t(m_driftSinceLastCorrection);
nDataToSend -= diffSamples;
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Correction for non-integer sampling rate : " << diffSamples << " samples deleted\n";
m_driftSinceLastCorrection -= double(diffSamples);
//oStimulationSet.appendStimulation(OVTK_GDF_Missing, CTime(m_header.getSamplingFrequency(), nDataToSend).time(), CTime(m_header.getSamplingFrequency(), diffSamples)).time();
}
}
// set data in m_sample
double* buffer64;
float* buffer32;
switch (m_dataType)
{
case DATATYPE_FLOAT64:
buffer64 = (double*)databuf;
for (size_t j = 0; j < m_header.getChannelCount(); j++)
{
for (uint32_t i = 0; i < nDataToSend; i++)
{
const double value = buffer64[i * m_header.getChannelCount() + j];
/*if ( _isnan(value) || !_finite(value) || value==DBL_MAX )
{
m_sample[j*nDataToSend + i] = FLT_MAX;
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "NaN or infinite sample received.\n";
}
else
{
//data from IHM implant are in volts, must be in uvolts in openvibe
m_sample[j*nDataToSend + i] = (float) 1000000.0f*value;
}*/
m_sample[j * nDataToSend + i] = float(value);
}
}
break;
case DATATYPE_FLOAT32:
buffer32 = (float*)databuf;
for (size_t j = 0; j < m_header.getChannelCount(); j++)
{
for (uint32_t i = 0; i < nDataToSend; i++)
{
const float value = buffer32[i * m_header.getChannelCount() + j];
/*if ( _isnan(value) || !_finite(value) || value==FLT_MAX )
{
m_sample[j*nDataToSend + i] = FLT_MAX;
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "NaN or infinite sample received.\n";
}
else
{
//data from IHM implant are in volts, must be in uvolts in openvibe
m_sample[j*nDataToSend + i] = 1000000.0f*value;
}*/
m_sample[j * nDataToSend + i] = value;
}
}
break;
default:
FreeResponse(response, "DEV ERROR : data type not suppported");
return -1;
}//end switch
}//end data correct
}//end data received
FreeResponse(response, nullptr);//we copied the data from response, so now we need to release this memory to avoid memory leak
m_nTotalSample = lastSample;
}
return nDataToSend; // no error
}
void CDriverFieldtrip::FreeResponse(message_t* response, const char* message)
{
if (message != nullptr) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << message << "\n"; }
if (response->buf) { free(response->buf); }
if (response->def) { free(response->def); }
free(response);
response = nullptr;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif
@@ -0,0 +1,93 @@
/* This driver uses the FieldTrip buffer open source library.
* See http://www.ru.nl/fcdonders/fieldtrip for details.
*/
#pragma once
#if defined(TARGET_HAS_PThread)
#include "ovasIDriver.h"
#include "../ovasCHeader.h"
#include <openvibe/ov_all.h>
#include "fieldtrip/message.h"
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
// for GET_CPU_TIME
#include <iostream>
#include <fstream>
namespace OpenViBE
{
namespace AcquisitionServer
{
/**
* \class CDriverFieldtrip
* \author Amelie Serpollet (CEA/LETI/CLINATEC)
* \date Mon May 23 09:48:21 2011
* \brief The CDriverFieldtrip allows the acquisition server to acquire data from a Fieldtrip buffer.
*
* TODO: details
*
*/
class CDriverFieldtrip final : public IDriver
{
public:
CDriverFieldtrip(IDriverContext& ctx);
~CDriverFieldtrip() override;
const char* getName() override;
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
bool uninitialize() override;
bool start() override;
bool stop() override;
bool loop() override;
bool isConfigurable() override;
bool configure() override;
const IHeader* getHeader() override { return &m_header; }
void FreeResponse(message_t* response, const char* message);
protected:
bool requestHeader();
int requestChunk(CStimulationSet& oStimulationSet);
IDriverCallback* m_callback = nullptr;
CHeader m_header;
SettingsHelper m_settings;
uint32_t m_nSamplePerSentBlock = 0;
float* m_sample = nullptr;
uint32_t m_dataType = DATATYPE_UNKNOWN;
// Connection to Fieldtrip buffer
CString m_hostName = "localhost";
uint32_t m_portNumber = 1979;
int m_connectionID = -1;
uint32_t m_minSamples = 1;
// Avoid frequent memory allocation
message_t* m_waitDataRequest = nullptr;
message_t* m_getDataRequest = nullptr;
uint32_t m_nTotalSample = 0;
uint32_t m_waitingTimeMs = 0;
bool m_firstGetDataRequest = false;
bool m_correctNonIntegerSR = true; // ???
double m_realSampling = 0;
double m_diffPerSample = 0; // ???
double m_driftSinceLastCorrection = 0;
// count time lost for "get cpu time" :
//double m_mesureLostTime = 0;
//uint32_t m_mesureNumber = 0;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // #if defined(TARGET_HAS_PThread)
@@ -0,0 +1,88 @@
The MIT License (MIT)
Copyright (c) 2015 Thomas Stewart
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Acknowledgements:
This driver was written by Tom Stewart under the supervision of
Dr. Tomasz M. Rutkowski in the University of Tsukuba's BCI Laboratory
http://bci-lab.info/
Installation:
The driver was developed using gtec's Linux C API 1.14.02. For it build properly,
the install.sh supplied with the gtec API needs to have been run which should
have created the following files:
/usr/lib/libgusbampapiso.so.1.14.02
/usr/include/gAPI.h
/etc/gtec/filter_files/DSPfilter.bin
/etc/gtec/filter_files/DSPNotchfilter.bin
To access the USB without being root, you'll need to add yourself to the
plugdev group and write a udev rule.
To do that, first check if there is a plugdev group:
$ groups
If there isn't already a plugdev group, then add it using:
$ sudo groupadd plugdev
Now execute the following:
$ sudo usermod -a -G plugdev <your-username>
If you're not sure what your username is, check it with:
$ whoami
Next create a file named /etc/udev/rules.d/10-gusbamp-usb.rules
$ sudo touch /etc/udev/rules.d/10-gusbamp-usb.rules
And open it with nano editor
$ sudo nano /etc/udev/rules.d/10-gusbamp-usb.rules
Now paste the following line into the editor:
ATTRS{idProduct}=="0001", ATTRS{idVendor}=="153c", MODE="666", GROUP="plugdev"
The values for idProduct and idVendor should match the ones that show up just
after you've plugged in the amplifier and executed:
$ dmesg
Lastly, be sure to log out and back in.
Useage:
The driver gives access to everything in the API with the exception of the channel
calibration and asynchronous configuration for digital outputs.
To run multiple gUSBAmps in parallel, use multiple instances of the acquisition server
and configure each instance as Master / Slave according to gtec's documentation. When
starting the acquisition, first connect to all the devices then press play on each device
starting with the master.
@@ -0,0 +1,120 @@
#pragma once
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI_Linux
#include <stdint.h>
#include <string.h>
#ifndef MIN
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#endif
template <class T>
class Queue
{
T* buffer = nullptr;
int head = 0, tail = 0, size = 0;
public:
Queue() { }
Queue(T* array, const int len)
{
size = len;
buffer = array;
}
void SetBuffer(T* array, const int len)
{
size = len;
head = tail = 0;
buffer = array;
}
int Get(T* elements, int len)
{
// Trim the length if necessary to only as large as the number of available elements in the buffer
len = MIN(len, Avail());
int nonwrapped = MIN((size - tail), len);
const int wrapped = len - nonwrapped;
// memcpy the data starting at the head all the way up to the last element *(storage - 1)
memcpy(elements, (buffer + tail), nonwrapped * sizeof(T));
// If there's still data to copy memcpy whatever remains, starting at the first element *(begin) until the end of data. The first step will have ensured
// that we don't crash into the tail during this process.
memcpy((elements + nonwrapped), buffer, wrapped * sizeof(T));
// Recalculate head
tail = (tail + nonwrapped + wrapped) % size;
return len;
}
// Returns the number of bytes actually placed in the array
int Put(const T* elements, int len)
{
// Trim the length if necessary to only as large as the nuber of free elements in the buffer
len = MIN(len, Free());
// Figure out how much to append to the end of the buffer and how much will overlap onto the start
int nonwrapped = MIN((size - head), len);
const int wrapped = len - nonwrapped;
// memcpy the data starting at the head all the way up to the last element *(storage - 1)
memcpy((buffer + head), elements, nonwrapped * sizeof(T));
// If there's still data to copy memcpy whatever remains onto the beginning of the array
memcpy(buffer, (elements + nonwrapped), wrapped * sizeof(T));
// Re-recalculate head
head = (head + nonwrapped + wrapped) % size;
return len;
}
// Expand the size of queue without actually modifying any of the contents - useful for copying directly onto the queu buffer
int Pad(int len)
{
// Trim the length if necessary to only as large as the nuber of free elements in the buffer
len = MIN(len, Free());
// Figure out how much to append to the end of the buffer and how much will overlap onto the start
const int nonwrapped = MIN((size - head), len), wrapped = len - nonwrapped;
// Re-recalculate head
head = (head + nonwrapped + wrapped) % size;
return len;
}
// Removes the oldest entry from the Queue
void Pop() { if (Avail()) tail = (tail + 1) % size; }
// Returns the oldest element in the array (the one added before any other)
T& Tail() { return buffer[tail]; }
// Returns the newest element in the array (the one added after every other)
T& Head() { return buffer[(head + size - 1) % size]; }
T& operator[](int n) { return buffer[tail + n % size]; }
void Clear() { head = tail = 0; }
int Avail() const { return (size + head - tail) % size; }
int Free() const { return (size - 1 - Avail()); }
// Gets the number of free elements that can be stored contiguously
int FreeContiguous() { return head < tail ? tail - head - 1 : MIN(size - head, Free()); }
// Gets a pointer to the next free address in the buffer
T* NextFreeAddress() { return buffer + head; }
};
#endif // TARGET_HAS_ThirdPartyGUSBampCAPI_Linux
@@ -0,0 +1,545 @@
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI_Linux
#include "ovasCConfigurationGTecGUSBampLinux.h"
namespace OpenViBE {
namespace AcquisitionServer {
/*_________________________________________________
Callbacks to specific widgets
_________________________________________________*/
void button_apply_bipolar_pressed_cb(GtkButton* button, void* data)
{
CConfigurationGTecGUSBampLinux* config = static_cast<CConfigurationGTecGUSBampLinux*>(data);
config->OnButtonApplyConfigPressed(CConfigurationGTecGUSBampLinux::BipolarColumn);
}
void button_apply_bandpass_pressed_cb(GtkButton* button, void* data)
{
CConfigurationGTecGUSBampLinux* config = static_cast<CConfigurationGTecGUSBampLinux*>(data);
config->OnButtonApplyConfigPressed(CConfigurationGTecGUSBampLinux::BandpassColumn);
}
void button_apply_notch_pressed_cb(GtkButton* button, void* data)
{
CConfigurationGTecGUSBampLinux* config = static_cast<CConfigurationGTecGUSBampLinux*>(data);
config->OnButtonApplyConfigPressed(CConfigurationGTecGUSBampLinux::NotchColumn);
}
void button_check_impedance_pressed_cb(GtkButton* button, void* data) { gtk_button_set_label(button, "Checking..."); }
void button_check_impedance_clicked_cb(GtkButton* button, void* data)
{
CConfigurationGTecGUSBampLinux* config = static_cast<CConfigurationGTecGUSBampLinux*>(data);
config->OnButtonCheckImpedanceClicked();
}
void combobox_sampling_frequency_changed_cb(GtkComboBox* pCombobox, void* data)
{
CConfigurationGTecGUSBampLinux* config = static_cast<CConfigurationGTecGUSBampLinux*>(data);
config->OnComboboxSamplingFrequencyChanged();
}
void DeviceChangedCB(GtkComboBox* pCombobox, void* data)
{
CConfigurationGTecGUSBampLinux* config = static_cast<CConfigurationGTecGUSBampLinux*>(data);
config->OnComboboxDeviceChanged();
}
void entry_impedance_activate_cb(GtkEntry* pEntry, void* data)
{
// Reset the background colour and stick a question mark on the box so the impedance check function will check this channel
GdkColor color;
color.red = color.green = color.blue = 0xFFFF;
gtk_entry_set_text(pEntry, "?");
gtk_widget_modify_base(GTK_WIDGET(pEntry), GTK_STATE_NORMAL, &color);
}
/*_________________________________________________*/
// If you added more reference attribute, initialize them here
CConfigurationGTecGUSBampLinux::CConfigurationGTecGUSBampLinux(IDriverContext& ctx, const char* gtkBuilderFilename, std::string* deviceName,
gt_usbamp_config* config)
: CConfigurationBuilder(gtkBuilderFilename), m_driverCtx(ctx), m_deviceName(deviceName), m_config(config) {}
void CConfigurationGTecGUSBampLinux::UpdateFilters()
{
GtkListStore* listStore = GTK_LIST_STORE(gtk_builder_get_object(m_builder,"model_channel_config"));
GtkTreeView* treeView = GTK_TREE_VIEW(gtk_builder_get_object(m_builder,"treeview_channel_config"));
GtkTreeModel* treeModel = gtk_tree_view_get_model(treeView);
GtkTreeIter iter;
// If the sampling frequency changes the filters may no longer be valid, so clear them all and have the user start again
for (gboolean end = gtk_tree_model_get_iter_first(treeModel, &iter); end; end = gtk_tree_model_iter_next(treeModel, &iter))
{
gtk_list_store_set(listStore, &iter, NotchColumn, "none", -1);
gtk_list_store_set(listStore, &iter, NotchIdColumn, GT_FILTER_NONE, -1);
gtk_list_store_set(listStore, &iter, BandpassColumn, "none", -1);
gtk_list_store_set(listStore, &iter, BandpassIdColumn, GT_FILTER_NONE, -1);
}
// Get pointers to the comboboxes
GtkComboBox* comboBoxDevice = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_device"));
GtkComboBox* comboBoxSampling = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_sampling_frequency"));
// Get the device name and the sample rate from the comboboxes
char* deviceName = gtk_combo_box_get_active_text(comboBoxDevice);
gt_size sampling = (gt_size)strtol(gtk_combo_box_get_active_text(comboBoxSampling), nullptr, 10);
// This takes a while so we'll keep the user informed via the console - might have to put this in a thread at some point though
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Opening device [" << deviceName << "] to query filters ...\n";
// Try opening the device
if (GT_OpenDevice(deviceName))
{
GtkTreeIter iterBandpass, iterNotch;
GtkListStore* listStoreBandpass = GTK_LIST_STORE(gtk_builder_get_object(m_builder,"model_bandpass"));
GtkListStore* listStoreNotch = GTK_LIST_STORE(gtk_builder_get_object(m_builder,"model_notch"));
// Clear all the entries from the filter comboboxes
gtk_list_store_clear(listStoreBandpass);
gtk_list_store_clear(listStoreNotch);
// Add the none and autoset configurations back in
// none
gtk_list_store_append(listStoreBandpass, &iterBandpass);
gtk_list_store_set(listStoreBandpass, &iterBandpass, 0, "none", 1, GT_FILTER_NONE, -1);
gtk_list_store_append(listStoreNotch, &iterNotch);
gtk_list_store_set(listStoreNotch, &iterNotch, 0, "none", 1, GT_FILTER_NONE, -1);
// autoset
gtk_list_store_append(listStoreBandpass, &iterBandpass);
gtk_list_store_set(listStoreBandpass, &iterBandpass, 0, "autoset", 1, GT_FILTER_AUTOSET, -1);
gtk_list_store_append(listStoreNotch, &iterNotch);
gtk_list_store_set(listStoreNotch, &iterNotch, 0, "autoset", 1, GT_FILTER_AUTOSET, -1);
// Set the combo boxes to show none, it's a bit tidier that way
gtk_combo_box_set_active(GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_bandpass")), 0);
gtk_combo_box_set_active(GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_notch")), 0);
// Get the sizes of the lists
gt_size bandpassListSize = GT_GetBandpassFilterListSize(deviceName, sampling);
gt_size notchListSize = GT_GetNotchFilterListSize(deviceName, sampling);
// Allocate them
gt_filter_specification* bandpassList = new gt_filter_specification[bandpassListSize];
gt_filter_specification* notchList = new gt_filter_specification[notchListSize];
// Get the lists themselves - note the last parameter to these two should be specified in bytes
GT_GetBandpassFilterList(deviceName, sampling, bandpassList, bandpassListSize * sizeof(gt_filter_specification));
GT_GetNotchFilterList(deviceName, sampling, notchList, notchListSize * sizeof(gt_filter_specification));
// Repopulate the comboboxes - for each returned filter make a description and put it in the combobox
// Bandpass
for (uint32_t i = 0; i < bandpassListSize; ++i)
{
std::stringstream ss;
ss << "HP: " << bandpassList[i].f_lower << " / LP: " << bandpassList[i].f_upper; // HP = High Pass, LP = Low Pass
gtk_list_store_append(listStoreBandpass, &iterBandpass);
gtk_list_store_set(listStoreBandpass, &iterBandpass, 0, ss.str().c_str(), 1, bandpassList[i].id, -1);
}
// Notch
for (uint32_t i = 0; i < notchListSize; ++i)
{
std::stringstream ss;
ss << "HS: " << notchList[i].f_lower << " / LS: " << notchList[i].f_upper; // HS = High Stop, LS = Low Stop
gtk_list_store_append(listStoreNotch, &iterNotch);
gtk_list_store_set(listStoreNotch, &iterNotch, 0, ss.str().c_str(), 1, notchList[i].id, -1);
}
GT_CloseDevice(deviceName);
}
else { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not open device\n"; }
}
bool CConfigurationGTecGUSBampLinux::preConfigure()
{
char** deviceList = nullptr;
size_t size = 0;
if (!CConfigurationBuilder::preConfigure()) return false;
// Refresh and get the list of currently connnected devices
GT_UpdateDevices();
size = GT_GetDeviceListSize();
deviceList = GT_GetDeviceList();
GtkComboBox* comboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_builder,"combobox_device"));
for (uint32_t i = 0; i < size; ++i) { gtk_combo_box_append_text(comboBox, deviceList[i]); }
GT_FreeDeviceList(deviceList, size);
// Connect all the callbacks
g_signal_connect(gtk_builder_get_object(m_builder, "button_apply_bipolar"), "pressed", G_CALLBACK(button_apply_bipolar_pressed_cb), this);
g_signal_connect(gtk_builder_get_object(m_builder, "button_apply_bandpass"), "pressed", G_CALLBACK(button_apply_bandpass_pressed_cb), this);
g_signal_connect(gtk_builder_get_object(m_builder, "button_apply_notch"), "pressed", G_CALLBACK(button_apply_notch_pressed_cb), this);
g_signal_connect(gtk_builder_get_object(m_builder, "button_check_impedance"), "pressed", G_CALLBACK(button_check_impedance_pressed_cb), this);
g_signal_connect(gtk_builder_get_object(m_builder, "button_check_impedance"), "clicked", G_CALLBACK(button_check_impedance_clicked_cb), this);
g_signal_connect(gtk_builder_get_object(m_builder, "combobox_sampling_frequency"), "changed", G_CALLBACK(combobox_sampling_frequency_changed_cb), this);
g_signal_connect(gtk_builder_get_object(m_builder, "combobox_device"), "changed", G_CALLBACK(DeviceChangedCB), this);
// Connect up all the activate methods for the text entries so the user can select them and read the associated channel impedance
for (int i = 0; i < (GT_USBAMP_NUM_ANALOG_IN + GT_USBAMP_NUM_REFERENCE); ++i)
{
std::stringstream ss;
// Compile the string corresponding to the name of the widget displaying the impedance information
ss << "entry_impedance" << i + 1;
g_signal_connect(gtk_builder_get_object(m_builder, ss.str().c_str()), "focus-in-event", G_CALLBACK(entry_impedance_activate_cb), this);
}
// Couldn't work out how to do this in the designer, set the treeview box to be able to select multiple items at once
GtkTreeView* treeView = GTK_TREE_VIEW(gtk_builder_get_object(m_builder,"treeview_channel_config"));
GtkTreeSelection* selection = gtk_tree_view_get_selection(treeView);
gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
// Now apply all the configs recovered from the settings helper to the GUI - don't have to worry about the sampling rate and number of channels though since they're already taken care of
// First look through all the device names returned from the API. If any match the one recovered from the settings manager set that as active
GtkTreeModel* treeModelName = gtk_combo_box_get_model(comboBox);
GtkTreeIter itName;
for (gboolean end = gtk_tree_model_get_iter_first(treeModelName, &itName); end; end = gtk_tree_model_iter_next(treeModelName, &itName))
{
gchar* filterDesc;
gtk_tree_model_get(treeModelName, &itName, 0, &filterDesc, -1);
// If the name in the combo box matches the one passed in then make that entry active
if (*m_deviceName == filterDesc) // todo: check that this works with the API
{
gtk_combo_box_set_active_iter(comboBox, &itName);
}
// Free the string now that we're finished with it
g_free(filterDesc);
}
// todo: when we change the devicename combo box we should refresh all the filters as per sampling frequency changed.
// And since we might have changed to a valid device name, let's update the filter combo boxes
UpdateFilters();
// Fill out the analog output configs
// Shape
GtkComboBox* comboBoxShape = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_analog_out_shape"));
gtk_combo_box_set_active(comboBoxShape, m_config->ao_config->shape);
// Amplitude
GtkSpinButton* buttonAmplitude = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder,"spinbutton_analog_out_amplitude"));
gtk_spin_button_set_value(buttonAmplitude, m_config->ao_config->amplitude);
// Offset
GtkSpinButton* buttonOffset = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder,"spinbutton_analog_out_offset"));
gtk_spin_button_set_value(buttonOffset, m_config->ao_config->offset);
// Frequency
GtkSpinButton* buttonFrequency = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder,"spinbutton_analog_out_frequency"));
gtk_spin_button_set_value(buttonFrequency, m_config->ao_config->frequency);
// Fill out the options
// Slave
GtkToggleButton* checkButtonSlave = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_slave"));
gtk_toggle_button_set_active(checkButtonSlave, m_config->slave_mode);
// Shortcut
GtkToggleButton* buttonShortcut = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_shortcut"));
gtk_toggle_button_set_active(buttonShortcut, m_config->enable_sc);
// Shortcut
GtkToggleButton* buttonDio = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_dio"));
gtk_toggle_button_set_active(buttonDio, m_config->scan_dio);
// Trigger
GtkToggleButton* buttonTrigger = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_trigger"));
gtk_toggle_button_set_active(buttonTrigger, m_config->enable_trigger_line);
// Mode
GtkComboBox* comboBoxMode = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_mode"));
gtk_combo_box_set_active(comboBoxMode, m_config->mode);
// Set all the blocks A-D to use the common ground and reference voltages
for (uint32_t i = 0; i < GT_USBAMP_NUM_GROUND; ++i)
{
GtkToggleButton* buttonGnd = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, ("checkbutton_block_gnd" + std::to_string(i + 1)).c_str()));
GtkToggleButton* buttonRef = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, ("checkbutton_block_ref" + std::to_string(i + 1)).c_str()));
gtk_toggle_button_set_active(buttonGnd, m_config->common_ground[i]);
gtk_toggle_button_set_active(buttonRef, m_config->common_reference[i]);
}
// Config each channel with the info from the configs, no need to check the filters and what not since they must have been correct to have been stored alongside eachother
GtkListStore* listStore = GTK_LIST_STORE(gtk_builder_get_object(m_builder,"model_channel_config"));
int i = 0;
GtkTreeModel* treeModelChannel = gtk_tree_view_get_model(treeView);
GtkTreeIter itChannel;
for (gboolean end = gtk_tree_model_get_iter_first(treeModelChannel, &itChannel); end; end = gtk_tree_model_iter_next(treeModelChannel, &itChannel), i++)
{
// Look through each value in the bandpass model and if there's one that has an id that matches, set it's text to the channel configs
GtkTreeModel* treeModelBandpass = GTK_TREE_MODEL(gtk_builder_get_object(m_builder,"model_bandpass"));
GtkTreeIter itBandpass;
for (gboolean end2 = gtk_tree_model_get_iter_first(treeModelBandpass, &itBandpass); end2; end2 = gtk_tree_model_iter_next(treeModelBandpass, &itBandpass))
{
gchar* filterDesc;
gint filterID;
gtk_tree_model_get(treeModelBandpass, &itBandpass, 0, &filterDesc, 1, &filterID, -1);
// If the id is the same as the one in the config, we've found the filter that was set last time
if (filterID == m_config->bandpass[i]) { gtk_list_store_set(listStore, &itChannel, BandpassColumn, filterDesc, BandpassIdColumn, filterID, -1); }
// Free the string now that we're finished with it
g_free(filterDesc);
}
// Look through each value in the notch model and if there's one that has an id that matches, set it's text to the channel configs
GtkTreeModel* treeModelNotch = GTK_TREE_MODEL(gtk_builder_get_object(m_builder,"model_notch"));
GtkTreeIter iterNotch;
for (gboolean end2 = gtk_tree_model_get_iter_first(treeModelNotch, &iterNotch); end2; end2 = gtk_tree_model_iter_next(treeModelNotch, &iterNotch))
{
gchar* filterDesc;
gint filterID;
gtk_tree_model_get(treeModelNotch, &iterNotch, 0, &filterDesc, 1, &filterID, -1);
// If the id is the same as the one in the config, we've found the filter that was set last time
if (filterID == m_config->notch[i]) { gtk_list_store_set(listStore, &itChannel, NotchColumn, filterDesc, NotchIdColumn, filterID, -1); }
// Free the string now that we're finished with it
g_free(filterDesc);
}
// And just straight out set the bipolar channel config
gtk_list_store_set(listStore, &itChannel, BipolarColumn, gint(m_config->bipolar[i] == GT_BIPOLAR_DERIVATION_NONE ? 0 : m_config->bipolar[i]), -1);
}
return true;
}
void CConfigurationGTecGUSBampLinux::OnButtonApplyConfigPressed(ChannelTreeViewColumn type)
{
// Get the tree view widget
GtkTreeView* treeView = GTK_TREE_VIEW(gtk_builder_get_object(m_builder,"treeview_channel_config"));
// Now get it's model, both as a list store so we can set the entries and it's "base class" tree model so we can iterate through it
GtkTreeModel* treeModel = gtk_tree_view_get_model(treeView);
GtkListStore* listStore = GTK_LIST_STORE(gtk_builder_get_object(m_builder,"model_channel_config"));
// Also get the subset of paths that are selected, if any
GtkTreeSelection* selection = gtk_tree_view_get_selection(treeView);
// Iterate through them and set the fields
GtkTreeIter iter;
for (gboolean end = gtk_tree_model_get_iter_first(treeModel, &iter); end; end = gtk_tree_model_iter_next(treeModel, &iter))
{
// If the given row is selected
if (gtk_tree_selection_iter_is_selected(selection, &iter))
{
// Fill in the bipolar field with the spin button contents if the bipolar apply button was pressed
if (type == BipolarColumn)
{
GtkSpinButton* button = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder,"spinbutton_bipolar"));
gtk_list_store_set(listStore, &iter, BipolarColumn, gint(gtk_spin_button_get_value(button)), -1);
}
// Fill in the filter field with the combobox contents and fill in a hidden field id with another hidden field in the combobox model that stores the filter's id
gint filterId;
GtkTreeIter selectedComboIter;
if (type == NotchColumn)
{
GtkComboBox* comboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_notch"));
// Get the iterator for the active row in the combo box's model
gtk_combo_box_get_active_iter(comboBox, &selectedComboIter);
// Get the value of the id column of that row
gtk_tree_model_get(gtk_combo_box_get_model(comboBox), &selectedComboIter, 1, &filterId, -1);
// Put the combo box text in the tree view and the id value in the hidden column of the tree view's model
gtk_list_store_set(listStore, &iter, NotchColumn, gtk_combo_box_get_active_text(comboBox), NotchIdColumn, filterId, -1);
}
if (type == BandpassColumn)
{
GtkComboBox* comboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_bandpass"));
// Get the iterator for the active row in the combo box's model
gtk_combo_box_get_active_iter(comboBox, &selectedComboIter);
// Get the value of the id column of that row
gtk_tree_model_get(gtk_combo_box_get_model(comboBox), &selectedComboIter, 1, &filterId, -1);
// Put the combo box text in the tree view and the id value in the hidden column of the tree view's model
gtk_list_store_set(listStore, &iter, BandpassColumn, gtk_combo_box_get_active_text(comboBox), BandpassIdColumn, filterId, -1);
}
}
}
}
// We'll make this just check the impedance of the selcected box.
void CConfigurationGTecGUSBampLinux::OnButtonCheckImpedanceClicked()
{
int impedance;
GdkColor color;
// Get the name of the selected device
GtkComboBox* comboBoxDevice = GTK_COMBO_BOX(gtk_builder_get_object(m_builder,"combobox_device"));
char* deviceName = gtk_combo_box_get_active_text(comboBoxDevice);
// This takes a while so we'll keep the user informed via the console - might have to put this in a thread at some point though
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Opening device [" << deviceName << "] for impedance check ...\n";
// Try opening the device
if (GT_OpenDevice(deviceName))
{
// If that worked then for each channel
for (uint32_t i = 0; i < (GT_USBAMP_NUM_ANALOG_IN + GT_USBAMP_NUM_REFERENCE); ++i)
{
// Reset the color so we don't get one impedance's color bleeding into the next
color.red = color.green = color.blue = color.pixel = 0;
// Compile the string corresponding to the name of the widget displaying the impedance information
std::string name = "entry_impedance" + std::to_string(i + 1);
// Get the relevant text entry
GtkEntry* text = GTK_ENTRY(gtk_builder_get_object(m_builder, name.c_str()));
if (strcmp(gtk_entry_get_text(text), "?") == 0)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Reading from channel " << i + 1 << "...\n";
// Try to get the impedance for each channel (channels 17 - 20 correspond to references A through D)
if (GT_GetImpedance(deviceName, i + 1, &impedance))
{
// Convert the impedance into kohms
impedance /= 1000;
// impedance is good
if (impedance < LowImpedance) { color.green = 0xFFFF; }
// impedance is moderate
else if (impedance < ModerateImpedance) { color.red = color.green = 0xFFFF; }
// impedance is high
else if (impedance < HighImpedance) { color.red = 0xFFFF; }
// impedance is so high that the channel is probably not connected
else { color.blue = 0xFFFF; }
// If the impedance is larger than 100kohm just write NC
std::string tmp = (impedance < HighImpedance) ? std::to_string(impedance) : "NC";
// Set the text
gtk_entry_set_text(text, tmp.c_str());
}
else
{
// If impedance reading fails, produce an error message and set the relevant cell to black
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to read from channel " << i + 1 << "...\n";
color.red = color.green = color.blue = 1;
gtk_entry_set_text(text, "");
}
// Then get it as just a widget so we can set the background colour
GtkWidget* widget = GTK_WIDGET(gtk_builder_get_object(m_builder, name.c_str()));
gtk_widget_modify_base(widget, GTK_STATE_NORMAL, &color);
}
}
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Closing device...\n";
GT_CloseDevice(deviceName);
}
else { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not open device\n"; }
// Set the text back so the user knows the test is over
GtkButton* buttonCheckImpedance = GTK_BUTTON(gtk_builder_get_object(m_builder,"button_check_impedance"));
gtk_button_set_label(buttonCheckImpedance, "Check Impedance");
}
// Open the device, get all the possible filters given the sampling frequency and we also blank out all the preset filters since they might not be valid or the same for the new frequency
void CConfigurationGTecGUSBampLinux::OnComboboxSamplingFrequencyChanged()
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Changing sampling frequency invalidates filters\n";
UpdateFilters();
}
// Same again
void CConfigurationGTecGUSBampLinux::OnComboboxDeviceChanged()
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Changing device invalidates filters\n";
UpdateFilters();
}
bool CConfigurationGTecGUSBampLinux::postConfigure()
{
if (m_applyConfig)
{
// Fill in all the parts of the config that differ from the default configuration we've set out in the CGTecGUSBampLinux constructor
GtkComboBox* comboBoxDevice = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_device"));
// If there's any active text in the device combo box then set it to the
if (char* deviceName = gtk_combo_box_get_active_text(comboBoxDevice)) { *m_deviceName = deviceName; }
// Get the sample rate and the number of channels
GtkComboBox* comboBoxSampling = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_sampling_frequency"));
m_config->sample_rate = (gt_size)strtol(gtk_combo_box_get_active_text(comboBoxSampling), nullptr, 10);
GtkSpinButton* buttonChannel = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_number_of_channels"));
m_config->num_analog_in = gtk_spin_button_get_value(buttonChannel);
// Fill out the analog output configs
// Shape
GtkComboBox* comboBoxShape = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_analog_out_shape"));
m_config->ao_config->shape = usbamp_analog_out_shape(gtk_combo_box_get_active(comboBoxShape));
// Amplitude
GtkSpinButton* buttonAmplitude = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder,"spinbutton_analog_out_amplitude"));
m_config->ao_config->amplitude = gtk_spin_button_get_value(buttonAmplitude);
// Offset
GtkSpinButton* buttonOffset = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder,"spinbutton_analog_out_offset"));
m_config->ao_config->offset = gtk_spin_button_get_value(buttonOffset);
// Frequency
GtkSpinButton* buttonFrequency = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder,"spinbutton_analog_out_frequency"));
m_config->ao_config->frequency = gtk_spin_button_get_value(buttonFrequency);
// Fill out the options
// Slave
GtkToggleButton* buttonSlave = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_slave"));
m_config->slave_mode = gtk_toggle_button_get_active(buttonSlave);
// Shortcut
GtkToggleButton* buttonShortcut = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_shortcut"));
m_config->enable_sc = gtk_toggle_button_get_active(buttonShortcut);
// Shortcut
GtkToggleButton* buttonDio = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_dio"));
m_config->scan_dio = gtk_toggle_button_get_active(buttonDio);
// Trigger
GtkToggleButton* buttonTrigger = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_trigger"));
m_config->enable_trigger_line = gtk_toggle_button_get_active(buttonTrigger);
// Mode
GtkComboBox* comboBoxMode = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_mode"));
m_config->mode = usbamp_device_mode(gtk_combo_box_get_active(comboBoxMode));
// Set all the blocks A-D to use the common ground and reference voltages
for (uint32_t i = 0; i < GT_USBAMP_NUM_GROUND; ++i)
{
GtkToggleButton* buttonGnd = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, ("checkbutton_block_gnd" + std::to_string(i + 1)).c_str()));
GtkToggleButton* buttonRef = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, ("checkbutton_block_ref" + std::to_string(i + 1)).c_str()));
m_config->common_ground[i] = gtk_toggle_button_get_active(buttonGnd);
m_config->common_reference[i] = gtk_toggle_button_get_active(buttonRef);
}
// Config each channel with the info
GtkTreeView* treeView = GTK_TREE_VIEW(gtk_builder_get_object(m_builder,"treeview_channel_config"));
GtkTreeModel* treeModel = gtk_tree_view_get_model(treeView);
gint value;
GtkTreeIter iter;
int i = 0;
for (gboolean end = gtk_tree_model_get_iter_first(treeModel, &iter); end; end = gtk_tree_model_iter_next(treeModel, &iter), i++)
{
gtk_tree_model_get(treeModel, &iter, BipolarColumn, &value, -1);
m_config->bipolar[i] = (value == 0 ? GT_BIPOLAR_DERIVATION_NONE : value);
gtk_tree_model_get(treeModel, &iter, NotchIdColumn, &value, -1);
m_config->notch[i] = value;
gtk_tree_model_get(treeModel, &iter, BandpassIdColumn, &value, -1);
m_config->bandpass[i] = value;
}
}
if (!CConfigurationBuilder::postConfigure()) // normal header is filled (Subject ID, Age, Gender, channels, sampling frequency), ressources are realesed
return false;
return true;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGUSBampCAPI_Linux
@@ -0,0 +1,65 @@
#pragma once
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI_Linux
#include "../ovasCConfigurationBuilder.h"
#include "ovasIDriver.h"
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <gAPI.h>
#include <cstdio>
#include <iostream>
#include <string>
#include <cstring>
#include <sstream>
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CConfigurationGTecGUSBampLinux
* \author Tom Stewart (University of Tsukuba)
* \date Mon Feb 9 18:59:22 2015
* \brief The CConfigurationGTecGUSBampLinux handles the configuration dialog specific to the g.tec g.USBamp for Linux device.
*
* TODO: details
*
* \sa CDriverGTecGUSBampLinux
*/
class CConfigurationGTecGUSBampLinux final : public CConfigurationBuilder
{
public:
// Thresholds for reporting on measured impedance, these are the same as the ones that the simulink driver uses
static const int LowImpedance = 5, ModerateImpedance = 7, HighImpedance = 100;
enum ChannelTreeViewColumn { ChannelColumn = 0, BipolarColumn, NotchColumn, NotchIdColumn, BandpassColumn, BandpassIdColumn };
// you may have to add to your constructor some reference parameters
// for example, a connection ID:
CConfigurationGTecGUSBampLinux(IDriverContext& ctx, const char* gtkBuilderFilename, std::string* deviceName, gt_usbamp_config* config);
bool preConfigure() override;
bool postConfigure() override;
void OnButtonApplyConfigPressed(ChannelTreeViewColumn type);
void OnButtonCheckImpedanceClicked();
void OnComboboxSamplingFrequencyChanged();
void OnComboboxDeviceChanged();
protected:
IDriverContext& m_driverCtx;
private:
/*
* Insert here all specific attributes, such as a connection ID.
* use references to directly modify the corresponding attribute of the driver
* Example:
*/
std::string* m_deviceName = nullptr;
gt_usbamp_config* m_config;
void UpdateFilters();
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGUSBampCAPI_Linux
@@ -0,0 +1,351 @@
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI_Linux
#include "ovasCDriverGTecGUSBampLinux.h"
#include "ovasCConfigurationGTecGUSBampLinux.h"
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace AcquisitionServer {
CDriverGTecGUSBampLinux::CDriverGTecGUSBampLinux(IDriverContext& ctx)
: IDriver(ctx), m_settings("AcquisitionServer_Driver_GTecGUSBampLinux", m_driverCtx.getConfigurationManager()),
m_nSamplePerSentBlock(0), m_sampleSend(nullptr), m_sampleReceive(nullptr), m_sampleBuffer(nullptr), m_currentSample(0), m_currentChannel(0)
{
// Default values
m_header.setSamplingFrequency(512);
m_header.setChannelCount(16);
m_config.ao_config = &m_analogOutConfig;
// Configure some defaults so the settings are reasonable as soon as the driver loads and the user can tweak them from there
// Configure the analog waveform to be created by the internal signal generator
m_analogOutConfig.shape = GT_ANALOGOUT_SINE;
m_analogOutConfig.frequency = 1;
m_analogOutConfig.amplitude = 0;
m_analogOutConfig.offset = 0;
// This pretty much has to be GT_NOS_AUTOSET, don't know why, so says the documentation
m_config.number_of_scans = GT_NOS_AUTOSET;
// Disable the trigger line, digital io scan, slave mode and the shortcut
m_config.enable_trigger_line = m_config.scan_dio = m_config.slave_mode = m_config.enable_sc = GT_FALSE;
// Set the mode to just take readings
m_config.mode = GT_MODE_NORMAL;
// Set all the blocks A-D to use the common ground and reference voltages
for (uint32_t i = 0; i < GT_USBAMP_NUM_GROUND; ++i)
{
m_config.common_ground[i] = GT_TRUE;
m_config.common_reference[i] = GT_TRUE;
}
// Configure each input
for (unsigned char i = 0; i < GT_USBAMP_NUM_ANALOG_IN; ++i)
{
// Should be from 1 - 16, specifies which channel to observe as input i
m_config.analog_in_channel[i] = i + 1;
// Don't use any of the filters on channel i
m_config.bandpass[i] = GT_FILTER_NONE;
// Don't use any of the notch filters on channel i
m_config.notch[i] = GT_FILTER_NONE;
// Don't use any of the other channels for bi-polar derivation
m_config.bipolar[i] = GT_BIPOLAR_DERIVATION_NONE;
}
// Now look for any connected devices. If any exist we'll set the name to the first one found
char** devices = nullptr;
size_t nDevice = 0;
// Refresh and get the list of currently connnected devices
GT_UpdateDevices();
nDevice = GT_GetDeviceListSize();
devices = GT_GetDeviceList();
// If any devices were found at all, set the combo box to the first one listed
if (nDevice) { m_deviceName = devices[0]; }
GT_FreeDeviceList(devices, nDevice);
// Now retrieve all those configs from the settings file if they are there to be found (don't need to worry about sample rate or channel number though since they're already in the header)
m_settings.add("Header", &m_header);
m_settings.add("DeviceName", static_cast<std::string*>(&m_deviceName));
m_settings.add("Mode", static_cast<int*>(&m_config.mode));
m_settings.add("EnableTrigger", static_cast<bool*>(&m_config.enable_trigger_line));
m_settings.add("ScanDIO", static_cast<bool*>(&m_config.scan_dio));
m_settings.add("SlaveMode", static_cast<bool*>(&m_config.slave_mode));
m_settings.add("EnableShortcut", static_cast<bool*>(&m_config.enable_sc));
m_settings.add("AnalogOutShape", static_cast<int*>(&m_analogOutConfig.shape));
m_settings.add("AnalogOutFrequency", static_cast<int*>(&m_analogOutConfig.frequency));
m_settings.add("AnalogOutAmplitude", static_cast<int*>(&m_analogOutConfig.amplitude));
m_settings.add("AnalogOutOffset", static_cast<int*>(&m_analogOutConfig.offset));
// Set all the blocks A-D to use the common ground and reference voltages
for (uint32_t i = 0; i < GT_USBAMP_NUM_GROUND; ++i)
{
std::stringstream gndConfigName, configName;
gndConfigName << "CommonGround" << i;
configName << "CommonReference" << i;
m_settings.add(gndConfigName.str().c_str(), static_cast<bool*>(&m_config.common_ground[i]));
m_settings.add(configName.str().c_str(), static_cast<bool*>(&m_config.common_reference[i]));
}
// Configure each input
for (uint32_t i = 0; i < GT_USBAMP_NUM_ANALOG_IN; ++i)
{
std::stringstream bandpassConfigName, notchConfigName, bipolarConfigName;
bandpassConfigName << "Bandpass" << i;
notchConfigName << "Notch" << i;
bipolarConfigName << "Bipolar" << i;
m_settings.add(bandpassConfigName.str().c_str(), static_cast<int*>(&m_config.bandpass[i]));
m_settings.add(notchConfigName.str().c_str(), static_cast<int*>(&m_config.notch[i]));
m_settings.add(bipolarConfigName.str().c_str(), static_cast<int*>(&m_config.bipolar[i]));
}
// This restores saved settings if any, such as sampling rate
m_settings.load();
// Set the sampling rate that may have been changed by load
m_config.sample_rate = m_header.getSamplingFrequency();
// Number of channels that may have been changed by load
m_config.num_analog_in = m_header.getChannelCount();
}
//___________________________________________________________________//
// //
bool CDriverGTecGUSBampLinux::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
{
if (m_driverCtx.isConnected()) return false;
if (!m_header.isChannelCountSet() || !m_header.isSamplingFrequencySet()) return false;
// If the scan digital inputs flag is set, the API will return one extra channel outside of the analog data requested, so we need to match that on the header
if (m_config.scan_dio == GT_TRUE)
{
m_header.setChannelCount(m_config.num_analog_in + 1);
m_header.setChannelName(m_config.num_analog_in, "Digital");
}
// Allocate buffers for...
// Sending to OpenViBE
m_sampleSend = new float[m_header.getChannelCount() * nSamplePerSentBlock];
// Receiving from the hardware,
m_sampleReceive = new float[m_header.getChannelCount() * nSamplePerSentBlock];
// Storing the data so we pass it between the two threads - we're using the recommended buffer size put out by gtec, which is enormous
m_sampleBuffer = new float[GT_USBAMP_RECOMMENDED_BUFFER_SIZE / sizeof(float)];
// Set up the queue to help pass the data out of the hardware thread
m_sampleQueue.SetBuffer(m_sampleBuffer, m_header.getChannelCount() * m_header.getSamplingFrequency() / 8);
// If any of that allocation fails then give up. Not sure what setting it all to NULL is for, but we'll go with it.
if (!m_sampleSend || !m_sampleReceive || !m_sampleBuffer)
{
delete[] m_sampleSend;
delete[] m_sampleReceive;
delete[] m_sampleBuffer;
m_sampleSend = m_sampleReceive = m_sampleBuffer = nullptr;
return false;
}
// Apparently this causes the API to print debug info to the console, I'm yet to see any though
GT_ShowDebugInformation(GT_TRUE);
// Try to open the device with the configured name, let the user know how it goes
if (!GT_OpenDevice(m_deviceName.c_str()))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not open device: " << m_deviceName << "\n";
return false;
}
if (!GT_SetConfiguration(m_deviceName.c_str(), &m_config))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not apply configuration to device: " << m_deviceName << "\n";
return false;
}
GT_SetDataReadyCallBack(m_deviceName.c_str(), &OnDataReady, static_cast<void*>(this));
// Saves parameters
m_callback = &callback;
m_nSamplePerSentBlock = nSamplePerSentBlock;
return true;
}
bool CDriverGTecGUSBampLinux::start()
{
if (!m_driverCtx.isConnected()) return false;
if (m_driverCtx.isStarted()) return false;
// ...
// request hardware to start
// sending data
// ...
// Need to reset these in case the device is stopped mid-sample and then started again
m_currentChannel = m_currentSample = 0;
GT_StartAcquisition(m_deviceName.c_str());
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Acquisition Started\n";
return true;
}
// So when the gtec buffer grows larger than a send buffer, copy it all to a send buffer sized array, then copy it into the actual send buffer one by one.
bool CDriverGTecGUSBampLinux::loop()
{
if (!m_driverCtx.isConnected()) return false;
if (!m_driverCtx.isStarted()) return true;
const CStimulationSet stimSet;
// while there's new data available on the queue
while (m_sampleQueue.Avail())
{
// take it off and put it in the appropriate element in the outgoing buffer
m_sampleQueue.Get(m_sampleSend + m_currentChannel * m_nSamplePerSentBlock + m_currentSample, 1);
// Increment the current channel
m_currentChannel++;
// If the current channel reaches the channel count then move to the next sample
if (m_currentChannel == m_header.getChannelCount())
{
m_currentChannel = 0;
m_currentSample++;
}
// If the sample count reaches the number per sent block, then send it and start again
if (m_currentSample == m_nSamplePerSentBlock)
{
m_callback->setSamples(m_sampleSend); // it looks as if this copies the buffer, so we're free modify it as soon as it executes
// When your sample buffer is fully loaded,
// it is advised to ask the acquisition server
// to correct any drift in the acquisition automatically.
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
// ...
// receive events from hardware
// and put them the correct way in a CStimulationSet object
//...
m_callback->setStimulationSet(stimSet);
m_currentSample = 0;
}
}
return true;
}
bool CDriverGTecGUSBampLinux::stop()
{
if (!m_driverCtx.isConnected()) return false;
if (!m_driverCtx.isStarted()) return false;
// ...
// request the hardware to stop
// sending data
// ...
GT_StopAcquisition(m_deviceName.c_str());
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Acquisition Stopped";
return true;
}
bool CDriverGTecGUSBampLinux::uninitialize()
{
if (!m_driverCtx.isConnected()) return false;
if (m_driverCtx.isStarted()) return false;
GT_CloseDevice(m_deviceName.c_str());
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Closed Device: " << m_deviceName << "\n";
// ...
// uninitialize hardware here
// ...
m_sampleQueue.SetBuffer(nullptr, 0);
delete[] m_sampleSend;
delete[] m_sampleBuffer;
delete[] m_sampleReceive;
m_sampleSend = m_sampleReceive = m_sampleBuffer = nullptr;
m_callback = nullptr;
return true;
}
//___________________________________________________________________//
// //
bool CDriverGTecGUSBampLinux::isConfigurable()
{
return true; // change to false if your device is not configurable
}
bool CDriverGTecGUSBampLinux::configure()
{
// Change this line if you need to specify some references to your driver attribute that need configuration, e.g. the connection ID.
CConfigurationGTecGUSBampLinux config(m_driverCtx, Directories::getDataDir() + "/applications/acquisition-server/interface-GTecGUSBampLinux.ui", &m_deviceName, &m_config);
if (!config.configure(m_header)) { return false; }
m_header.setChannelCount(m_config.num_analog_in);
m_header.setSamplingFrequency(m_config.sample_rate);
m_settings.save();
return true;
}
/*void AcquisitionServer::OnDataReady(void *param)
{
// Like the 'this' pointer, but for a friend function
CDriverGTecGUSBampLinux *that = (CDriverGTecGUSBampLinux*)param;
// This is pretty tricky to know in advance, the API decides how many values to spit out depnding on a few factors it seems.
// We'll allocate a reasonble buffer and call GT_GetData as many times as is necessary
while(size_t nSamplesToRead = GT_GetSamplesAvailable(that->m_deviceName.c_str()))
{
// If there are more samples than will fit in the buffer, just get as many as possible and we can get the rest next iteration
if(nSamplesToRead > CDriverGTecGUSBampLinux::ReceiveBufferSize * sizeof(float))
nSamplesToRead = CDriverGTecGUSBampLinux::ReceiveBufferSize * sizeof(float);
// Get the data -- TODO: rewrite this algorithm such that we can copy directly from GT_GetData into the buffer read in the loop() function - is this a bug?? Maybe, but probably not since the calibration mode was always perfect
GT_GetData(that->m_deviceName.c_str(), reinterpret_cast<unsigned char*>(that->m_sampleReceive), nSamplesToRead);
// Put it on the sample queue
that->m_sampleQueue.Put(that->m_sampleReceive, nSamplesToRead / sizeof(float));
}
}*/
void AcquisitionServer::OnDataReady(void* param)
{
// Like the 'this' pointer, but for a friend function
CDriverGTecGUSBampLinux* that = static_cast<CDriverGTecGUSBampLinux*>(param);
// This is pretty tricky to know in advance, the API decides how many values to spit out depnding on a few factors it seems.
// We'll allocate a reasonble buffer and call GT_GetData as many times as is necessary
while (size_t samplesToRead = GT_GetSamplesAvailable(that->m_deviceName.c_str()))
{
// If there are more samples than will fit in the buffer, just get as many as possible and we can get the rest next iteration
if (samplesToRead > that->m_sampleQueue.FreeContiguous() * sizeof(float)) samplesToRead = that->m_sampleQueue.FreeContiguous() * sizeof(float);
// Get the data and put it directly onto the queue
GT_GetData(that->m_deviceName.c_str(), reinterpret_cast<unsigned char*>(that->m_sampleQueue.NextFreeAddress()), samplesToRead);
// Pad the queue so it recognises how much data was just added to it
that->m_sampleQueue.Pad(samplesToRead / sizeof(float));
}
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGUSBampCAPI_Linux
@@ -0,0 +1,78 @@
#pragma once
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI_Linux
#include "ovasIDriver.h"
#include "../ovasCHeader.h"
#include <openvibe/ov_all.h>
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
#include <gAPI.h>
#include "Queue.h"
namespace OpenViBE {
namespace AcquisitionServer {
void OnDataReady(void* param);
/**
* \class CDriverGTecGUSBampLinux
* \author Tom Stewart (University of Tsukuba)
* \date Mon Feb 9 18:59:22 2015
* \brief The CDriverGTecGUSBampLinux allows the acquisition server to acquire data from a g.tec g.USBamp from Linux.
*
* \sa CConfigurationGTecGUSBampLinux
*/
class CDriverGTecGUSBampLinux final : public IDriver
{
static const int ReceiveBufferSize = 8192;
public:
friend void OnDataReady(void* param);
explicit CDriverGTecGUSBampLinux(IDriverContext& ctx);
~CDriverGTecGUSBampLinux() override {}
const char* getName() override { return "g.tec g.USBamp Linux BCI-Lab"; }
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
bool uninitialize() override;
bool start() override;
bool stop() override;
bool loop() override;
bool isConfigurable() override;
bool configure() override;
const IHeader* getHeader() override { return &m_header; }
bool isFlagSet(const EDriverFlag flag) const override { return flag == EDriverFlag::IsUnstable; }
protected:
SettingsHelper m_settings;
IDriverCallback* m_callback = nullptr;
// Replace this generic Header with any specific header you might have written
CHeader m_header;
uint32_t m_nSamplePerSentBlock;
float *m_sampleSend, *m_sampleReceive, *m_sampleBuffer;
Queue<float> m_sampleQueue;
private:
/*
* Insert here all specific attributes, such as USB port number or device ID.
*/
std::string m_deviceName;
gt_usbamp_config m_config;
gt_usbamp_analog_out_config m_analogOutConfig;
// Keeps track of where we are with filling up the buffer
uint32_t m_currentSample, m_currentChannel;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGUSBampCAPI_Linux
@@ -0,0 +1,151 @@
//CRingBuffer provided by g.tec
//website: http://www.gtec.at
//redistribution of this file has been offcially granted by g.tec
#pragma once
#include <algorithm> // std::min
#include <winnt.h>
#include <stdlib.h>
//#include <afxwin.h>
/*
* Class representing a ring buffer with elements of type float.
*/
template <typename T>
class CRingBuffer
{
public:
//Constructor. Creates an empty buffer with an initial capacity of zero.
CRingBuffer() : _buffer(nullptr) { }
//Destructor. Frees the allocated buffer.
~CRingBuffer()
{
if (_buffer != nullptr) { VirtualFree(_buffer, 0, MEM_RELEASE); }
_buffer = nullptr;
}
/*
* Initializes the buffer with the specified capacity representing the number of elements that the buffer can contain.
* Returns false if the memory couldn't be allocated (e.g. because of not enough free disk space); true, if the call succeeded.
*/
bool Initialize(const uint32_t capacity)
{
//if the buffer has been allocated before, release this memory first
if (_buffer != nullptr)
{
VirtualFree(_buffer, 0, MEM_RELEASE);
_buffer = nullptr;
}
if (capacity > 0)
{
//allocate memory for the buffer
_buffer = static_cast<T*>(VirtualAlloc(nullptr, capacity * sizeof(T), MEM_COMMIT, PAGE_READWRITE));
//check if allocation succeeded
if (_buffer == nullptr) { return false; }
_capacity = capacity;
}
//reset the buffer positions
Reset();
return true;
}
//Clears the buffer by resetting both the start and end position to zero.
void Reset()
{
_start = 0;
_end = 0;
_isEmpty = true;
}
//Returns the buffer's capacity it has been initialized to, i.e. the number of elements the buffer can contain.
int GetCapacity() const { return _capacity; }
//Returns the free space of the buffer, i.e. the number of new elements that can be enqueued before the buffer will overrun.
int GetFreeSize() const { return _capacity - GetSize(); }
//Returns the number of elements that the buffer currently contains (don't confuse the size (number of ACTUALLY contained elements) with the capacity (maximum number of elements that the buffer CAN contain)!).
int GetSize() const
{
if (_isEmpty) { return 0; }
if (_start < _end) { return _end - _start; }
return _capacity - (_start - _end);
}
/*
* Writes the specified number of elements from the specified source array into the ring buffer. If the number of elements to copy exceeds the free buffer space, only the free buffer space will be written, existing elements will NOT be overwritten.
* float* source: pointer to the first element of the source array whose elements should be stored into the ring buffer.
* uint32_t length: the number of elements from the source array that should be copied into the ring buffer.
*/
void Write(T* source, const uint32_t length)
{
//if buffer is full or no elements should be written, no elements can be written
if ((!_isEmpty && _start == _end) || length <= 0) { return; }
//if _start <= _end, split the free buffer space into two parts
uint32_t firstPartCapacity = (_start <= _end) ? _capacity - _end : _start - _end;
const uint32_t secondPartCapacity = (_start <= _end) ? _start : 0;
//copy first part
CopyMemory(&_buffer[_end], source, std::min(firstPartCapacity, length) * sizeof(T));
//if a second part exists, copy second part
if (length > firstPartCapacity)
{
CopyMemory(&_buffer[0], &source[firstPartCapacity], std::min(secondPartCapacity, length - firstPartCapacity) * sizeof(T));
}
//update buffer positions
_end = (_end + std::min(length, firstPartCapacity + secondPartCapacity)) % _capacity;
_isEmpty = false;
}
/*
* Copys the specified number of elements from the ring buffer into the specified destination array.
* If there are less elements in the buffer than the to read, only available elements will be copied.
* float *destination: The array where to copy the elements from the ring buffer to.
* uint32_t length: The number of elements to copy from the ring buffer into the destination array.
*/
void Read(T* destination, const uint32_t length)
{
if (length <= 0) { return; }
//if _start >= _end, split the read operation into two parts
int firstPartSize = (_start < _end) ? std::min(length, _end - _start) : std::min(length, _capacity - _start);
const int secondPartSize = (_start < _end) ? 0 : std::min(_end, length - firstPartSize);
//copy first part
CopyMemory(destination, &_buffer[_start], firstPartSize * sizeof(T));
//if a second part exists, copy second part
if (secondPartSize > 0) { CopyMemory(&destination[firstPartSize], &_buffer[0], secondPartSize * sizeof(T)); }
//update the buffer positions
_start = (_start + (firstPartSize + secondPartSize)) % _capacity;
if (_start == _end) { _isEmpty = true; }
}
protected:
//the buffer array
T* _buffer = nullptr;
//the number of elements the buffer can contain
uint32_t _capacity = 0;
//the position of the first contained element of the buffer in the internal array
uint32_t _start = 0;
//the position of the first free element of the buffer in the internal array (this position - 1 equals the position of the last contained element of the buffer)
uint32_t _end = 0;
//flag indicating if the buffer is empty. Necessary because when _start == _end it is undefined if the buffer is full or empty.
bool _isEmpty = true;
};
@@ -0,0 +1,999 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkAdjustment" id="adjustment1">
<property name="lower">1</property>
<property name="upper">999</property>
<property name="value">4</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment3">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkDialog" id="dialog-common-gnd-ref">
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Common Ground and Reference</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">3</property>
<property name="n_columns">2</property>
<property name="column_spacing">5</property>
<property name="row_spacing">5</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;big&gt;Connect to Common Ground&lt;/big&gt;</property>
<property name="use_markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-gnd-blockA">
<property name="label" translatable="yes">Block A (yellow)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-gnd-blockB">
<property name="label" translatable="yes">Block B (red)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-gnd-blockC">
<property name="label" translatable="yes">Block C (blue)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-gnd-blockD">
<property name="label" translatable="yes">Block D (green)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator4">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkTable" id="table3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">3</property>
<property name="n_columns">2</property>
<property name="column_spacing">5</property>
<property name="row_spacing">5</property>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;big&gt;Connect to Common Reference&lt;/big&gt;</property>
<property name="use_markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-ref-blockA">
<property name="label" translatable="yes">Block A (yellow)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-ref-blockB">
<property name="label" translatable="yes">Block B (red)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-ref-blockC">
<property name="label" translatable="yes">Block C (blue)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-ref-blockD">
<property name="label" translatable="yes">Block D (green)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkDialog" id="dialog-filters">
<property name="can_focus">False</property>
<property name="border_width">7</property>
<property name="title" translatable="yes">Amplifier Filters</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;big&gt;Notch filter&lt;/big&gt;
Type - Order - [Low Pass; High Pass] - Frequency</property>
<property name="use_markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox-notch">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">liststore1</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator5">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;big&gt;Band Pass filter&lt;/big&gt;
Type - Order - [Low Cut; High Cut] - Frequency</property>
<property name="use_markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox-band-pass">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">liststore2</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkListStore" id="liststore1">
<columns>
<!-- column-name filter -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkListStore" id="liststore2">
<columns>
<!-- column-name filter -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkListStore" id="model1">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">32</col>
</row>
<row>
<col id="0" translatable="yes">64</col>
</row>
<row>
<col id="0" translatable="yes">128</col>
</row>
<row>
<col id="0" translatable="yes">256</col>
</row>
<row>
<col id="0" translatable="yes">512</col>
</row>
<row>
<col id="0" translatable="yes">600</col>
</row>
<row>
<col id="0" translatable="yes">1200</col>
</row>
<row>
<col id="0" translatable="yes">2400</col>
</row>
<row>
<col id="0" translatable="yes">4800</col>
</row>
<row>
<col id="0" translatable="yes">9600</col>
</row>
<row>
<col id="0" translatable="yes">19200</col>
</row>
<row>
<col id="0" translatable="yes">38400</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model2">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">unspecified</col>
</row>
<row>
<col id="0" translatable="yes">female</col>
</row>
<row>
<col id="0" translatable="yes">male</col>
</row>
<row>
<col id="0" translatable="yes">unknown</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model3">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkDialog" id="openvibe-acquisition-server-settings">
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Device configuration</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="spacing">8</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_apply">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_title">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">g.Tec gUSBamp</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">8</property>
<property name="spacing">8</property>
<child>
<object class="GtkHBox" id="hbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">8</property>
<child>
<object class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">10</property>
<property name="n_columns">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkSpinButton" id="spinbutton_number_of_channels">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_number_of_channels">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Number of channels :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_master_device">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Master device :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_sampling_frequency">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">model1</property>
<child>
<object class="GtkCellRendererText" id="renderer1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_sampling_frequency">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Sampling frequency :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_gender">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">model2</property>
<child>
<object class="GtkCellRendererText" id="renderer2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_master_device">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">model3</property>
<child>
<object class="GtkCellRendererText" id="renderer3"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_identifier">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment2</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_age">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment3</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_identifier">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Identifier :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label_age">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Age :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_gender">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Gender :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_EventChannel">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_event_channel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Event channel</property>
</object>
<packing>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_bipolar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Bipolar electrodes</property>
</object>
<packing>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_Bipolar">
<property name="label" translatable="yes">1-2=1,...,15-16=15</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
<property name="x_padding">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_Calibrationsignal">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Calibration signal</property>
</object>
<packing>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_CalibrationSignal">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">8</property>
<property name="bottom_attach">9</property>
<property name="x_padding">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label-impedance">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Check impedance :</property>
</object>
<packing>
<property name="top_attach">9</property>
<property name="bottom_attach">10</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_impedance">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">9</property>
<property name="bottom_attach">10</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_ShowDeviceName">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">10</property>
<property name="bottom_attach">11</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_show_device_name">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Show device name</property>
</object>
<packing>
<property name="top_attach">10</property>
<property name="bottom_attach">11</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_calibrate">
<property name="label" translatable="yes">Auto calibrate in 4 seconds...</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator2">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button-common-gnd-ref">
<property name="label" translatable="yes">Connect to Common Gnd and Ref</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button-filters">
<property name="label" translatable="yes">Set Amplifier Filters</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator3">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_change_channel_names">
<property name="label" translatable="yes">Change channel names...</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_action_appearance">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-10">button_apply</action-widget>
<action-widget response="-6">button_cancel</action-widget>
</action-widgets>
</object>
</interface>
@@ -0,0 +1,401 @@
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI
#include "CConfigurationGTecGUSBamp.hpp"
#include <windows.h>
#include <gUSBamp.h>
#include <iostream>
#include <sstream>
namespace OpenViBE {
namespace AcquisitionServer {
static void ApplyFiltersPressedCB(GtkButton* /*button*/, void* data) { static_cast<CConfigurationGTecGUSBamp*>(data)->buttonFiltersApplyPressedCB(); }
static void CalibratePressedCB(GtkButton* /*button*/, void* data) { static_cast<CConfigurationGTecGUSBamp*>(data)->buttonCalibratePressedCB(); }
static void CommonGndRefPressedCB(GtkButton* /*button*/, void* data) { static_cast<CConfigurationGTecGUSBamp*>(data)->buttonCommonGndRefPressedCB(); }
static void FiltersPressedCB(GtkButton* /*button*/, void* data) { static_cast<CConfigurationGTecGUSBamp*>(data)->buttonFiltersPressedCB(); }
static gboolean idle_calibrate_cb(void* data)
{
static_cast<CConfigurationGTecGUSBamp*>(data)->idleCalibrateCB();
return FALSE;
}
CConfigurationGTecGUSBamp::CConfigurationGTecGUSBamp(const char* gtkBuilderFilename, uint8_t& commonGndAndRefBitmap, int& notchFilterIdx,
int& bandPassFilterIdx, bool& triggerInput, const std::vector<std::string>& devicesSerials,
std::string& masterDeviceIndex, bool& bipolar, bool& calibrationSignalEnabled, bool& showDeviceName)
: CConfigurationBuilder(gtkBuilderFilename), m_commonGndAndRefBitmap(commonGndAndRefBitmap), m_notchFilterIdx(notchFilterIdx),
m_bandPassFilterIdx(bandPassFilterIdx), m_triggerInput(triggerInput), m_devicesSerials(devicesSerials), m_masterDeviceIdx(masterDeviceIndex),
m_bipolarEnabled(bipolar), m_calibrationSignalEnabled(calibrationSignalEnabled), m_showDeviceName(showDeviceName) {}
bool CConfigurationGTecGUSBamp::preConfigure()
{
if (!CConfigurationBuilder::preConfigure()) { return false; }
GtkCheckButton* hardwareTagging = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_EventChannel"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(hardwareTagging), m_triggerInput);
GtkCheckButton* bipolar = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_Bipolar"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bipolar), m_bipolarEnabled);
GtkCheckButton* showDeviceName = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_ShowDeviceName"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(showDeviceName), m_showDeviceName);
GtkCheckButton* calibrationMode = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_CalibrationSignal"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(calibrationMode), m_calibrationSignalEnabled);
GtkComboBox* comboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_master_device"));
// Default active device is the last one
if (!m_devicesSerials.empty()) { gtk_combo_box_set_active(comboBox, m_devicesSerials.size() - 1); }
// If a device is already set, try to use that as the combo box selection; if not found, use last (set above).
int masterIndex = int(m_devicesSerials.size()) - 1;
for (size_t i = 0; i < m_devicesSerials.size(); ++i)
{
gtk_combo_box_append_text(comboBox, m_devicesSerials[i].c_str());
if (this->m_masterDeviceIdx == m_devicesSerials[i]) { masterIndex = i; }
}
if (masterIndex >= 0) { gtk_combo_box_set_active(comboBox, masterIndex); }
// Sets the channel limits depending on the number of amps
GtkSpinButton* numChannels = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_number_of_channels"));
gtk_spin_button_set_range(numChannels, 1, m_devicesSerials.size() * 16); // GTEC_NUM_CHANNELS
const size_t count = this->m_devicesSerials.size();
/*
char buffer[1024];
int count=0;
bool selected=false;*/
// autodetection of the connected device
/*for (uint32_t i=1; i<11; ++i)
{
::HANDLE handle=::GT_OpenDevice(i);
if(handle)
{
::GT_CloseDevice(&handle);
sprintf(buffer, "USB port %i", i);
::gtk_combo_box_append_text(comboBox, buffer);
if(m_usbIdx==i)
{
::gtk_combo_box_set_active(comboBox, count);
selected=true;
}
count++;
}
}
*/
//if(!selected && count!=0) { ::gtk_combo_box_set_active(comboBox, 0); }
g_signal_connect(gtk_builder_get_object(m_builder, "button_calibrate"), "pressed", G_CALLBACK(CalibratePressedCB), this);
g_signal_connect(gtk_builder_get_object(m_builder, "button-common-gnd-ref"), "pressed", G_CALLBACK(CommonGndRefPressedCB), this);
g_signal_connect(gtk_builder_get_object(m_builder, "button-filters"), "pressed", G_CALLBACK(FiltersPressedCB), this);
GtkWidget* dialog = GTK_WIDGET(gtk_builder_get_object(m_builder, "dialog-common-gnd-ref"));
gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_APPLY, GTK_RESPONSE_APPLY);
gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
dialog = GTK_WIDGET(gtk_builder_get_object(m_builder, "dialog-filters"));
GtkWidget* buttonApplyFilters = gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_APPLY, GTK_RESPONSE_APPLY);
g_signal_connect(buttonApplyFilters, "pressed", G_CALLBACK(ApplyFiltersPressedCB), this);
gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
GtkToggleButton* checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockA"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & 1));
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockB"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & (1 << 1)));
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockC"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & (1 << 2)));
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockD"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & (1 << 3)));
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockA"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & (1 << 4)));
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockB"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & (1 << 5)));
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockC"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & (1 << 6)));
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockD"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & (1 << 7)));
if (count == 0)
{
// deactivate the buttons
GtkWidget* button = GTK_WIDGET(gtk_builder_get_object(m_builder, "button-filters"));
gtk_widget_set_sensitive(button, false);
button = GTK_WIDGET(gtk_builder_get_object(m_builder, "button-common-gnd-ref"));
gtk_widget_set_sensitive(button, false);
}
return true;
}
bool CConfigurationGTecGUSBamp::postConfigure()
{
GtkComboBox* comboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_master_device"));
if (m_applyConfig)
{
/*
int usbIdx=0;
const char* usbIdx=::gtk_combo_box_get_active_text(comboBox);
if(usbIdx) { if(sscanf(usbIdx, "USB port %i", &usbIdx)==1) { m_usbIdx=(uint32_t)usbIdx; } }
*/
if (this->m_devicesSerials.size() > 1)
{
char* selectedSerial = gtk_combo_box_get_active_text(comboBox);
m_masterDeviceIdx = (selectedSerial == nullptr) ? "" : selectedSerial;
}
GtkToggleButton* checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockA"));
m_commonGndAndRefBitmap = (gtk_toggle_button_get_active(checkBox) ? 1 : 0);
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockB"));
m_commonGndAndRefBitmap = m_commonGndAndRefBitmap + (gtk_toggle_button_get_active(checkBox) ? (1 << 1) : 0);
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockC"));
m_commonGndAndRefBitmap = m_commonGndAndRefBitmap + (gtk_toggle_button_get_active(checkBox) ? (1 << 2) : 0);
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockD"));
m_commonGndAndRefBitmap = m_commonGndAndRefBitmap + (gtk_toggle_button_get_active(checkBox) ? (1 << 3) : 0);
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockA"));
m_commonGndAndRefBitmap = m_commonGndAndRefBitmap + (gtk_toggle_button_get_active(checkBox) ? (1 << 4) : 0);
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockB"));
m_commonGndAndRefBitmap = m_commonGndAndRefBitmap + (gtk_toggle_button_get_active(checkBox) ? (1 << 5) : 0);
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockC"));
m_commonGndAndRefBitmap = m_commonGndAndRefBitmap + (gtk_toggle_button_get_active(checkBox) ? (1 << 6) : 0);
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockD"));
m_commonGndAndRefBitmap = m_commonGndAndRefBitmap + (gtk_toggle_button_get_active(checkBox) ? (1 << 7) : 0);
GtkComboBox* notch = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox-notch"));
if (gtk_combo_box_get_active(notch) >= 0)
{
// Only update the filter index if the user chose something. This is needed so going to the Configuration menu doesn't
// change previous filter choices.
m_notchFilterIdx = ((gtk_combo_box_get_active(notch) == 0) ? -1 : m_comboBoxNotchFilterIdx[gtk_combo_box_get_active(notch) - 1]
); //-1 because there is one more in the beginning
}
GtkComboBox* bandPass = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox-band-pass"));
if (gtk_combo_box_get_active(bandPass) >= 0)
{
// Only update the filter index if the user chose something -1 because there is one more in the beginning
m_bandPassFilterIdx = ((gtk_combo_box_get_active(bandPass) == 0) ? -1 : int(m_comboBoxBandPassFilterIdx[gtk_combo_box_get_active(bandPass) - 1]));
}
GtkCheckButton* hardwareTagging = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_EventChannel"));
m_triggerInput = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(hardwareTagging)) ? true : false);
GtkCheckButton* showDeviceName = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_ShowDeviceName"));
m_showDeviceName = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(showDeviceName)) ? true : false);
GtkCheckButton* bipolar = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_Bipolar"));
m_bipolarEnabled = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bipolar)) ? true : false);
GtkCheckButton* calibrationSignal = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_CalibrationSignal"));
m_calibrationSignalEnabled = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(calibrationSignal)) ? true : false);
}
if (!CConfigurationBuilder::postConfigure()) { return false; }
return true;
}
void CConfigurationGTecGUSBamp::buttonCalibratePressedCB()
{
g_idle_add(idle_calibrate_cb, this);
m_calibrateDialog = gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, "Calibrating...");
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(m_calibrateDialog), "Please wait a few seconds...");
gtk_dialog_run(GTK_DIALOG(m_calibrateDialog));
gtk_widget_destroy(m_calibrateDialog);
if (m_calibrationDone)
{
GtkWidget* dialog = gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "Calibration finished !");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
}
else
{
GtkWidget* dialog = gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "Calibration failed !");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
}
}
void CConfigurationGTecGUSBamp::idleCalibrateCB()
{
GtkComboBox* comboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_master_device"));
m_calibrationDone = false;
//calibrate all detected devices
for (uint32_t i = 0; i < this->m_devicesSerials.size(); ++i)
{
LPSTR serial = const_cast<char*>(m_devicesSerials[i].c_str());
HANDLE handle = GT_OpenDeviceEx(serial);
if (handle)
{
m_calibrationDone = true;
SCALE calibration;
if (!GT_Calibrate(handle, &calibration)) { std::cout << "err GT_Calibrate\n", m_calibrationDone = false; }
if (!GT_SetScale(handle, &calibration))
{
std::cout << "err GT_SetScale\n";
m_calibrationDone = false;
}
GT_CloseDevice(&handle);
}
}
gtk_dialog_response(GTK_DIALOG(m_calibrateDialog), 0);
}
void CConfigurationGTecGUSBamp::buttonCommonGndRefPressedCB()
{
GtkWidget* dialog = GTK_WIDGET(gtk_builder_get_object(m_builder, "dialog-common-gnd-ref"));
gint resp = gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_hide(dialog);
}
void CConfigurationGTecGUSBamp::buttonFiltersPressedCB()
{
GtkWidget* dialog = GTK_WIDGET(gtk_builder_get_object(m_builder, "dialog-filters"));
setHardwareFiltersDialog();
gint resp = gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_hide(dialog);
}
void CConfigurationGTecGUSBamp::setHardwareFiltersDialog()
{
m_comboBoxBandPassFilterIdx.clear();
m_comboBoxNotchFilterIdx.clear();
GtkComboBox* comboBoxBandPass = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox-band-pass"));
GtkTreeModel* bandPassListStore = gtk_combo_box_get_model(comboBoxBandPass);
gtk_list_store_clear(GTK_LIST_STORE(bandPassListStore));
GtkComboBox* comboBoxNotch = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox-notch"));
GtkTreeModel* notchListStore = gtk_combo_box_get_model(comboBoxNotch);
gtk_list_store_clear(GTK_LIST_STORE(notchListStore));
// To check for available filters in the amplifier, we must connect to it.
if (!m_devicesSerials[0].empty())
{
LPSTR serial = const_cast<char*>(m_devicesSerials[0].c_str());
HANDLE handle = GT_OpenDeviceEx(serial);
int nBandPassFilters, nNotchFilters;
if (!GT_GetNumberOfFilter(&nBandPassFilters)) { std::cout << "err GT_GetNumberOfFilter\n"; }
if (nBandPassFilters == 0) { std::cout << "err No band pass filters found at all!\n"; }
if (!GT_GetNumberOfNotch(&nNotchFilters)) { std::cout << "err GT_GetNumberOfNotch\n"; }
if (nNotchFilters == 0) { std::cout << "err No notch filters found at all!\n"; }
FILT* bpFilterSpec = new FILT[nBandPassFilters];
FILT* notchFilterSpec = new FILT[nNotchFilters];
if (!GT_GetFilterSpec(bpFilterSpec)) { std::cout << "err GT_GetFilterSpec\n"; }
if (!GT_GetNotchSpec(notchFilterSpec)) { std::cout << "err GT_GetNotchSpec\n"; }
//Set BandPass filter list
std::stringstream desc;
desc << "no band pass filter.";
GtkTreeIter it;
gtk_list_store_append(GTK_LIST_STORE(bandPassListStore), &it);
gtk_list_store_set(GTK_LIST_STORE(bandPassListStore), &it, 0, desc.str().c_str(), -1);
desc.clear();
desc.str("");
gchar* sSampling = gtk_combo_box_get_active_text(GTK_COMBO_BOX(m_sampling));
const uint32_t sampling = (sSampling ? atoi(sSampling) : 0);
int cbBandPassSelectedIdx = -1;
// std::cout << "The device reports " << nBandPassFilters << " band pass filters and " << nNotchFilters << " notch filters\n";
if (nBandPassFilters > 0)
{
for (int i = 0; i < nBandPassFilters; ++i)
{
if (sampling == uint32_t(bpFilterSpec[i].fs))
{
if (bpFilterSpec[i].type == 1) { desc << "Butterworth - "; }
if (bpFilterSpec[i].type == 2) { desc << "Chebyshev - "; }
desc << bpFilterSpec[i].order << " - [" << bpFilterSpec[i].fu << "; " << bpFilterSpec[i].fo << "] - " << bpFilterSpec[i].fs;
GtkTreeIter iter;
gtk_list_store_append(GTK_LIST_STORE(bandPassListStore), &iter);
gtk_list_store_set(GTK_LIST_STORE(bandPassListStore), &iter, 0, desc.str().c_str(), -1);
desc.clear();
desc.str("");
m_comboBoxBandPassFilterIdx.push_back(i);
//here a previous selection is loaded
if (cbBandPassSelectedIdx == -1 && i == m_bandPassFilterIdx) { cbBandPassSelectedIdx = int(m_comboBoxBandPassFilterIdx.size()) - 1; }
}
}
}
gtk_combo_box_set_active(comboBoxBandPass, cbBandPassSelectedIdx + 1); // +1 because -1 is for "no filter".
//Set Notch filter List
desc << "no notch filter.";
gtk_list_store_append(GTK_LIST_STORE(notchListStore), &it);
gtk_list_store_set(GTK_LIST_STORE(notchListStore), &it, 0, desc.str().c_str(), -1);
desc.clear();
desc.str("");
int cbNotchSelectedIndex = -1;
if (nNotchFilters > 0)
{
for (int i = 0; i < nNotchFilters; ++i)
{
if (sampling == int(notchFilterSpec[i].fs))
{
if (notchFilterSpec[i].type == 1) { desc << "Butterworth - "; }
if (notchFilterSpec[i].type == 2) { desc << "Chebyshev - "; }
desc << notchFilterSpec[i].order << " - [" << notchFilterSpec[i].fu << "; " << notchFilterSpec[i].fo << "] - " << notchFilterSpec[i].fs;
GtkTreeIter iter;
gtk_list_store_append(GTK_LIST_STORE(notchListStore), &iter);
gtk_list_store_set(GTK_LIST_STORE(notchListStore), &iter, 0, desc.str().c_str(), -1);
desc.clear();
desc.str("");
m_comboBoxNotchFilterIdx.push_back(i);
//here a previous selection is loaded
if (cbNotchSelectedIndex == -1 && i == m_notchFilterIdx) { cbNotchSelectedIndex = int(m_comboBoxNotchFilterIdx.size()) - 1; }
}
}
}
gtk_combo_box_set_active(comboBoxNotch, cbNotchSelectedIndex + 1); // +1 because -1 is for "no filter".
delete bpFilterSpec;
delete notchFilterSpec;
GT_CloseDevice(&handle);
}
}
void CConfigurationGTecGUSBamp::buttonFiltersApplyPressedCB()
{
GtkComboBox* comboBoxBandPass = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox-band-pass"));
//-1 because there is one more in the beginning
m_bandPassFilterIdx = (gtk_combo_box_get_active(comboBoxBandPass) == -1 || gtk_combo_box_get_active(comboBoxBandPass) == 0) ? -1
: int(m_comboBoxBandPassFilterIdx[gtk_combo_box_get_active(comboBoxBandPass) - 1]);
GtkComboBox* comboBoxNotch = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox-notch"));
//-1 because there is one more in the beginning
m_notchFilterIdx = (gtk_combo_box_get_active(comboBoxNotch) == -1 || gtk_combo_box_get_active(comboBoxNotch) == 0) ? -1
: int(m_comboBoxNotchFilterIdx[gtk_combo_box_get_active(comboBoxNotch) - 1]);
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGUSBampCAPI
@@ -0,0 +1,53 @@
#pragma once
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI
#include "../ovasCConfigurationBuilder.h"
#include <gtk/gtk.h>
#include <string>
#include <vector>
namespace OpenViBE
{
namespace AcquisitionServer
{
class CConfigurationGTecGUSBamp final : public CConfigurationBuilder
{
public:
CConfigurationGTecGUSBamp(const char* gtkBuilderFilename, uint8_t& commonGndAndRefBitmap, int& notchFilterIdx, int& bandPassFilterIdx,
bool& triggerInput, const std::vector<std::string>& devicesSerials, std::string& masterDeviceIndex, bool& bipolar,
bool& calibrationSignalEnabled, bool& showDeviceName);
bool preConfigure() override;
bool postConfigure() override;
void buttonCalibratePressedCB();
void idleCalibrateCB();
void buttonCommonGndRefPressedCB();
void buttonFiltersPressedCB();
void setHardwareFiltersDialog();
void buttonFiltersApplyPressedCB();
protected:
uint8_t& m_commonGndAndRefBitmap;
int& m_notchFilterIdx;
int& m_bandPassFilterIdx;
bool& m_triggerInput;
std::vector<std::string> m_devicesSerials;
std::string& m_masterDeviceIdx;
std::vector<size_t> m_comboBoxBandPassFilterIdx;
std::vector<size_t> m_comboBoxNotchFilterIdx;
bool& m_bipolarEnabled;
bool& m_calibrationSignalEnabled;
bool& m_showDeviceName;
GtkWidget* m_calibrateDialog = nullptr;
bool m_calibrationDone = false;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGUSBampCAPI
@@ -0,0 +1,183 @@
#pragma once
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI
#include "ovasIDriver.h"
#include "../ovasCHeader.h"
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
#include <Windows.h>
#include "ringbuffer.h"
#include <gtk/gtk.h>
#include <vector>
//threading
#include <thread>
#include <mutex>
#include <condition_variable>
#include <memory> // unique_ptr
#include <deque>
namespace OpenViBE
{
namespace AcquisitionServer
{
/**
* \class CDriverGTecGUSBamp
* \author Anton Andreev, Gipsa-lab, VIBS team
* \date 19/07/2012
* \brief GTEC driver
*
* This driver was rewritten to match the code provided by Guger as much as possible. There are several things
* that all must work together so that higher frequencies are supported and no hardware triggers are lost.
*
* This driver supports several buffers so that the more than one GT_GetData can be executed in the beginning (QUEUE_SIZE)
* and then calls to GT_GetData are queued. This allows data to be processed by OpenVibe while waiting for the next result of
* a previously issued GT_GetData. The extra thread is added to support this and to allow for async IO.
*
* Hardware triggers on the parallel port are supported.
*
* The driver supports several g.tec devices working with the provided async cables. There are several requirements for async
* acquisition to work properly and these are checked in verifySyncMode().
*/
#ifndef __GDEVICE_H__
#define __GDEVICE_H__
struct GDevice
{
HANDLE handle;
std::string serial;
};
#endif
class CDriverGTecGUSBamp final : public IDriver
{
public:
explicit CDriverGTecGUSBamp(IDriverContext& ctx);
void release() { delete this; }
const char* getName() override { return "g.tec gUSBamp Gipsa-lab"; }
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
bool uninitialize() override;
bool start() override;
bool stop() override;
bool loop() override;
bool isConfigurable() override { return true; }
bool configure() override;
const IHeader* getHeader() override { return &m_header; }
bool CDriverGTecGUSBamp::acquire();
void configFiltering(HANDLE device);
protected:
static const int BUFFER_SIZE_SECONDS = 2; //the size of the GTEC ring buffer in seconds
static const int GTEC_NUM_CHANNELS = 16; //the number of channels without countig the trigger channel
static const int QUEUE_SIZE =
8; //4 default //the number of GT_GetData calls that will be queued during acquisition to avoid loss of data
static const int NUMBER_OF_SCANS =
32; //the number of scans that should be received simultaneously (depending on the _sampleRate; see C-API documentation for this value!)
size_t numDevices() const { return m_devices.size(); }
static const uint32_t N_POINTS = NUMBER_OF_SCANS * (GTEC_NUM_CHANNELS + 1);
int m_validPoints = 0;
static const DWORD BUFFER_SIZE_BYTES;
SettingsHelper m_settings;
IDriverCallback* m_callback = nullptr;
CHeader m_header;
float* m_sample = nullptr;
uint32_t m_nSamplePerSentBlock = 0;
uint32_t m_globalImpedanceIdx = 0;
uint8_t m_commonGndAndRefBitmap = 0;
int m_notchFilterIdx = -1;
int m_bandPassFilterIdx = -1;
bool m_triggerInputEnabled = false;
bool m_bipolarEnabled =
false; //electrodes are substracted in sepecific sequence 1-2=1, ... 15-16=15 which results in 8 instead of 16 electrodes - used for EMG
bool m_calibrationSignalEnabled = false;
bool m_showDeviceName = false; //adds the amplifier serial number to the name of the channel
bool m_reconfigurationRequired = false; // After some gt calls, we may need reconfig
uint32_t m_nAcquiredChannel = GTEC_NUM_CHANNELS; //number of channels specified by the user, never counts the event channels
uint32_t m_totalHardwareStimulations = 0; //since start button clicked
uint32_t m_totalDriverChunksLost = 0; //since start button clicked
uint32_t m_totalDriverTimeouts = 0; //since start button clicked
uint32_t m_totalRingBufferOverruns = 0;
uint32_t m_totalDataUnavailable = 0;
//contains buffer per device and then QUEUE_SIZE buffers so that several calls to GT_GetData can be supported
BYTE*** m_buffers = nullptr;
OVERLAPPED** m_overlapped = nullptr;
bool m_flagIsFirstLoop = true;
bool m_bufferOverrun = false;
//ring buffer provided by Guger
CRingBuffer<float> m_ringBuffer;
uint32_t m_currentQueueIdx = 0;
std::unique_ptr<std::thread> m_threadPtr;
bool m_isThreadRunning = false;
std::mutex m_io_mutex;
float* m_bufferReceivedData = nullptr;
std::condition_variable m_itemAvailable;
bool configureDevice(uint32_t deviceNumber);
bool verifySyncMode();//Checks if devices are configured correctly when acquiring data from multiple devices
//Selects which device to become the new master, used only when more than 1 device is available
bool setMasterDevice(const std::string& targetMasterSerial); //0 first device
void detectDevices();
uint32_t m_mastersCnt = 0;
uint32_t m_slavesCnt = 0;
std::string m_masterSerial = "";
void remapChannelNames(); // Converts channel names while appending the device name and handling event channels
void restoreChannelNames(); // Restores channel names without the device name
std::vector<std::string> m_originalChannelNames; // Channel names without the device name inserted
std::vector<GDevice> m_devices; // List of amplifiers
std::string CDriverGTecGUSBamp::getSerialByHandler(HANDLE device);
// Stores information related to each channel available in the recording system
struct SChannel
{
int idx; // Channel index in openvibe Designer, -1 is unused
int oldIdx; // Channel index in the user-settable channel name list
int gtecDeviceIdx; // Device index of the gtec amplifier this channel is in
int gtecChannelIdx; // Channel index in the device-specific numbering
bool isEventChannel; // Is this the special digital channel?
};
// Channel indexes are seen as a sequence [dev1chn1, dev1chn2,...,dev1chnN, dev2chn1, dev2chn2, ..., dev2chnN, ...]
// The following vector is used to map these 'system indexes' to openvibe channels
std::vector<SChannel> m_channels;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGUSBampCAPI
@@ -0,0 +1,458 @@
/**
* Software License Agreement (AGPL-3 License)
*
* \file CDriverGTecUnicorn.cpp
* \author Anton Andreev, Gipsa-lab, VIBS team
* \date 21/08/2020
* \brief Implementation of GTEC Unicorn Black Driver
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3,
* as published by the Free Software Foundation.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
#if defined TARGET_HAS_ThirdPartyGtecUnicron
#include "CDriverGTecUnicorn.hpp"
#include <toolkit/ovtk_all.h>
#include <system/ovCTime.h>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <limits>
#include <mutex>
#include <thread>
#include "unicorn.h"
namespace OpenViBE {
namespace AcquisitionServer {
#if defined(TARGET_OS_Windows)
#pragma warning(disable: 4800) // disable "forcing value to bool 'true' or 'false' (performance warning)" nag coming from BOOL->bool cast on e.g. VS2010
#endif
CDriverGTecUnicorn::CDriverGTecUnicorn(IDriverContext& rDriverContext)
: IDriver(rDriverContext), m_settings("AcquisitionServer_Driver_GTecGUSBamp", m_driverCtx.getConfigurationManager())
{
m_header.setSamplingFrequency(UNICORN_SAMPLING_RATE);
m_header.setChannelCount(0);
//m_settings.load();
}
//___________________________________________________________________//
// //
bool CDriverGTecUnicorn::initialize(
const uint32_t sampleCountPerSentBlock,
IDriverCallback& callback)
{
if (m_driverCtx.isConnected()) { return false; }
detectDevices();
if (numDevices() == 0) { return false; }
m_sampleCountPerSentBlock = sampleCountPerSentBlock;
m_callback = &callback;
// Set number of channels
int errorCode = UNICORN_GetNumberOfAcquiredChannels(m_devices[kSelectedDevice].handle, &m_acquiredChannelCount);
if (errorCode != UNICORN_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unable to get channel count.\n";
return false;
}
else { m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Number of available channels is: " << m_acquiredChannelCount << "\n"; }
m_header.setChannelCount(m_acquiredChannelCount); //only 8 channels are EEG, but currently we provide everything
m_lengthBufferRawUnicornDevice = m_acquiredChannelCount * kFrameLength;
/*
* Set channel names
* 17 channels from Unicorn Black on every sample: | EEG1| EEG2| EEG3| EEG4| EEG5| EEG6| EEG7| EEG8| ACCX|ACCY| ACCZ| GYRX|GYRY| GYRZ|CNT|BATLVL|VALID|
* Order might be different. This is why we need to use UNICORN_GetChannelIndex(channel name);
*/
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "EEG 1"), "EEG1");
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "EEG 2"), "EEG2");
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "EEG 3"), "EEG3");
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "EEG 4"), "EEG4");
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "EEG 5"), "EEG5");
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "EEG 6"), "EEG6");
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "EEG 7"), "EEG7");
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "EEG 8"), "EEG8");
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "Accelerometer X"), "ACCX");
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "Accelerometer Y"), "ACCY");
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "Accelerometer Z"), "ACCZ");
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "Gyroscope X"), "GYRX");
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "Gyroscope Y"), "GYRY");
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "Gyroscope Z"), "GYRZ");
m_channelCounterIndex = getChannelIndex(m_devices[kSelectedDevice].handle, "Counter");
m_header.setChannelName(m_channelCounterIndex, "Counter");
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "Battery Level"), "Battery");
m_header.setChannelName(getChannelIndex(m_devices[kSelectedDevice].handle, "Validation Indicator"), "VI");
// Initialize buffers
m_bufferReceivedDataFromRing = new float[m_lengthBufferRawUnicornDevice];
m_bufferForOpenVibe = new float[m_lengthBufferRawUnicornDevice];
m_ringBuffer.Initialize(static_cast<uint32_t>(kBufferSizeSeconds * m_header.getSamplingFrequency() * m_lengthBufferRawUnicornDevice));
// Configure each device
for (size_t i = 0; i < numDevices(); i++) { configureDevice(i); }
return true;
}
void CDriverGTecUnicorn::detectDevices()
{
int errorCode = UNICORN_ERROR_SUCCESS;
// Get number of available devices
unsigned int availableDevicesCount = 0;
errorCode = UNICORN_GetAvailableDevices(NULL, &availableDevicesCount, TRUE);
// Get serials of available devices
UNICORN_DEVICE_SERIAL* availableDevices = new UNICORN_DEVICE_SERIAL[availableDevicesCount];
errorCode = UNICORN_GetAvailableDevices(availableDevices, &availableDevicesCount, TRUE);
if (errorCode != UNICORN_ERROR_SUCCESS || availableDevicesCount < 1)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "No device available. Please pair with a Unicorn device first.\n";
}
else
{
// Create a GDevice list for all devices and print available device serials
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Available Unicorn devices:\n";
for (unsigned int i = 0; i < availableDevicesCount; i++)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "#" << i << ": " << availableDevices[i] << "\n";
GDevice device;
UNICORN_HANDLE deviceHandle;
int errorCode = UNICORN_OpenDevice(availableDevices[i], &deviceHandle);
if (errorCode != UNICORN_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unable to connect to device: '" << availableDevices[i] << "'\n";
}
device.handle = deviceHandle;
device.serial = availableDevices[i];
m_devices.push_back(device);
}
}
}
bool CDriverGTecUnicorn::configureDevice(size_t deviceNumber)
{
int errorCode = UNICORN_ERROR_SUCCESS;
UNICORN_HANDLE device = m_devices[deviceNumber].handle;
const std::string currentSerial = m_devices[deviceNumber].serial;
UNICORN_AMPLIFIER_CONFIGURATION configuration;
errorCode = UNICORN_GetConfiguration(device, &configuration);
if (errorCode != UNICORN_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unable to get configuration for: '" << currentSerial.c_str() << "'\n";
return false;
}
return true;
}
bool CDriverGTecUnicorn::start()
{
if (!m_driverCtx.isConnected()) { return false; }
if (m_driverCtx.isStarted()) { return false; }
m_totalHardwareStimulations = 0;
m_totalRingBufferOverruns = 0;
m_totalCounterErrors = 0;
{
std::lock_guard<std::mutex> lock(m_mutex);
m_ringBuffer.Reset();
}
for (size_t i = 0; i < numDevices(); i++)
{
UNICORN_HANDLE device = m_devices[i].handle;
UNICORN_StartAcquisition(device, kTestSignalEnabled);
}
m_isThreadRunning = true;
m_flagIsFirstLoop = true;
m_bufferOverrun = false;
m_thread.reset(new std::thread(std::bind(&CDriverGTecUnicorn::acquire, this)));
return true;
}
// This method is called by the AS and it supplies the acquired data to the AS
bool CDriverGTecUnicorn::loop()
{
if (m_driverCtx.isStarted())
{
{
std::unique_lock<std::mutex> lock(m_mutex);
while (m_ringBuffer.GetSize() < static_cast<int>(m_lengthBufferRawUnicornDevice)) { m_itemAvailable.wait(lock); }
try
{
if (m_bufferOverrun)
{
m_ringBuffer.Reset();
m_bufferOverrun = false;
m_totalRingBufferOverruns++;
return true;
}
m_ringBuffer.Read(m_bufferReceivedDataFromRing, m_lengthBufferRawUnicornDevice);
}
catch (std::exception& e)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error reading GTEC ring buffer! Error is:" << e.what() << "\n";
}
m_itemAvailable.notify_one();
}
// covert to openvibe format ch1,ch1,ch1,ch2,ch2,ch2 ...
for (size_t i = 0; i < m_acquiredChannelCount; i++)
{
for (size_t j = 0; j < kFrameLength; j++)
{
m_bufferForOpenVibe[kFrameLength * i + j] = m_bufferReceivedDataFromRing[j * m_acquiredChannelCount + i];
}
}
// verify counter
const size_t counterPos = kFrameLength * m_channelCounterIndex;
for (size_t i = counterPos; i < kFrameLength; i++) { if (!(m_bufferForOpenVibe[i] < m_bufferForOpenVibe[i + 1])) { m_totalCounterErrors++; } }
// forward data
m_callback->setSamples(m_bufferForOpenVibe, kFrameLength);
CStimulationSet stimulationSet;
m_callback->setStimulationSet(stimulationSet);
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
}
else { System::Time::sleep(20); }
return true;
}
// This function used by the thread
bool CDriverGTecUnicorn::acquire()
{
if (m_flagIsFirstLoop) //First time do some memory initialization, etc
{
m_bufferRawUnicornDevice = new float[m_lengthBufferRawUnicornDevice];
m_flagIsFirstLoop = false;
}
while (m_isThreadRunning == true)
{
try
{
bool flagChunkLostDetected = false;
bool flagChunkTimeOutDetected = false;
UNICORN_HANDLE device = m_devices[kSelectedDevice].handle;
// Get kFrameLength number of samples
if (UNICORN_GetData(device, kFrameLength, m_bufferRawUnicornDevice, m_lengthBufferRawUnicornDevice * sizeof(float)) != UNICORN_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error on GT_GetData\n";
return false;
}
// store to ring buffer, lock it during insertion
{
std::lock_guard<std::mutex> lock(m_mutex);
try
{
// if we are going to overrun on writing the received data into the buffer, set the appropriate flag; the reading thread will handle the overrun
m_bufferOverrun = (m_ringBuffer.GetFreeSize() < static_cast<int>(m_lengthBufferRawUnicornDevice));
m_ringBuffer.Write(m_bufferRawUnicornDevice, m_lengthBufferRawUnicornDevice);
}
catch (std::exception& e)
{
// buffer should be unclocked automatically once the scope is left
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error writing to GTEC ring buffer! Error is: " << e.what() << "\n";
}
// buffer should be unclocked automatically once the scope is left
m_itemAvailable.notify_one();
}
}
catch (std::exception& e)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error <<
"General error in the thread function acquiring data from GTEC! Acquisition interrupted. Error is: " << e.what() << "\n";
m_isThreadRunning = false;
return false;
}
}
// This code stops the amplifiers in the same thread:
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Stopping devices and cleaning up...\n";
// clean up allocated resources for each device
for (size_t i = 0; i < numDevices(); i++)
{
UNICORN_HANDLE device = m_devices[i].handle;
// stop device
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Sending stop command ...\n";
if (UNICORN_StopAcquisition(device) != UNICORN_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Stopping device failed! Serial = " << m_devices[numDevices() - 1].serial.c_str() << "\n";
}
// clear memory
delete[] m_bufferRawUnicornDevice;
}
m_flagIsFirstLoop = true;
m_isThreadRunning = false;
}
return true;
}
bool CDriverGTecUnicorn::stop()
{
if (!m_driverCtx.isConnected()) { return false; }
if (!m_driverCtx.isStarted()) { return false; }
// stop thread
m_isThreadRunning = false;
m_thread->join(); //wait until the thread has stopped data acquisition
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Data acquisition completed.\n";
if (m_totalRingBufferOverruns > 0)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Total internal ring buffer overruns: " << m_totalRingBufferOverruns << "\n";
}
if (m_totalCounterErrors > 0) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Total Unicorn counter errors: " << m_totalCounterErrors << "\n"; }
else { m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Total Unicorn counter errors: " << m_totalCounterErrors << "\n"; }
return true;
}
bool CDriverGTecUnicorn::uninitialize()
{
if (!m_driverCtx.isConnected()) { return false; }
if (m_driverCtx.isStarted()) { return false; }
const size_t totalDevices = numDevices();
size_t deviceClosed = 0;
for (std::vector<GDevice>::iterator it = m_devices.begin(); it != m_devices.end();)
{
if (UNICORN_CloseDevice(&it->handle) != UNICORN_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unable to close device: " << it->serial.c_str() << "\n";
}
else { deviceClosed++; }
it = m_devices.erase(it);
}
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Total devices closed: " << deviceClosed << " / " << totalDevices << "\n";
if (!m_devices.empty() || deviceClosed != totalDevices)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Some devices were not closed properly!\n";
}
m_devices.clear();
// clear memory
if (m_bufferReceivedDataFromRing != nullptr)
{
delete[] m_bufferReceivedDataFromRing;
m_bufferReceivedDataFromRing = nullptr;
}
if (m_bufferForOpenVibe != nullptr)
{
delete[] m_bufferForOpenVibe;
m_bufferForOpenVibe = nullptr;
}
m_callback = nullptr;
return true;
}
//___________________________________________________________________//
// //
inline size_t CDriverGTecUnicorn::getChannelIndex(UNICORN_HANDLE device, const char* name)
{
uint32_t* result = new uint32_t[1];
if (UNICORN_GetChannelIndex(device, name, result) != UNICORN_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error getting channel index for channel: '" << name << "'\n";
}
return static_cast<size_t>(*result);
}
inline std::ostream& operator<<(std::ostream& out, const std::vector<std::string>& var)
{
for (size_t i = 0; i < var.size(); i++) { out << var[i] << " "; }
return out;
}
inline std::istream& operator>>(std::istream& in, std::vector<std::string>& var)
{
var.clear();
std::string tmp;
while (in >> tmp) { var.push_back(tmp); }
return in;
}
inline std::ostream& operator<<(std::ostream& out, const std::vector<GDevice>& var)
{
for (size_t i = 0; i < var.size(); i++) { out << var[i].serial << " "; }
return out;
}
inline std::istream& operator>>(std::istream& in, std::vector<GDevice>& var)
{
// "Error not implemented operator >>!";
return in;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGUSBampCAPI
@@ -0,0 +1,144 @@
/**
* Software License Agreement (AGPL-3 License)
*
* \file CDriverGTecUnicorn.hpp
* \author Anton Andreev, Gipsa-lab, VIBS team
* \date 21/08/2020
* \brief GTEC Unicorn Black Driver
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3,
* as published by the Free Software Foundation.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#if defined TARGET_HAS_ThirdPartyGtecUnicron
#include "ovasIDriver.h"
#include "../ovasCHeader.h"
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
#include <Windows.h>
#include "ringbuffer.h"
#include <vector>
//threading
#include <thread>
#include <mutex>
#include <condition_variable>
#include <memory> // unique_ptr
#include <deque>
#include "unicorn.h"
namespace OpenViBE {
namespace AcquisitionServer {
#ifndef __GDEVICE_H__
#define __GDEVICE_H__
struct GDevice
{
UNICORN_HANDLE handle;
std::string serial;
};
#endif
class CDriverGTecUnicorn : public OpenViBE::AcquisitionServer::IDriver
{
public:
CDriverGTecUnicorn(OpenViBE::AcquisitionServer::IDriverContext& rDriverContext);
void release() { delete this; };
const char* getName() override { return "g.tec Unicorn Gipsa-lab"; };
bool initialize(const uint32_t sampleCountPerSentBlock, OpenViBE::AcquisitionServer::IDriverCallback& callback) override;
bool uninitialize() override;
bool start() override;
bool stop() override;
bool loop() override;
bool isConfigurable() override { return false; }
bool configure() override { return true; };
const OpenViBE::AcquisitionServer::IHeader* getHeader() override { return &m_header; }
bool acquire();
bool isFlagSet(const EDriverFlag flag) const override { return flag == EDriverFlag::IsUnstable; }
protected:
static const uint64_t kBufferSizeSeconds = 2; // The size of the GTEC ring buffer in seconds
static const uint32_t kGTecNumChannels = 17; // The number of channels
static const uint32_t kFrameLength = 8; // The number of samples acquired per Get_Data() call and the number of samples supplied to OpenVibe (per loop)
static const bool kTestSignalEnabled = FALSE; // Flag to enable or disable testsignal.
static const size_t kSelectedDevice = 0; // If several Unicorn devices are in range, the first one will be automatically selected
SettingsHelper m_settings;
OpenViBE::AcquisitionServer::IDriverCallback* m_callback = nullptr;
OpenViBE::AcquisitionServer::CHeader m_header;
size_t m_sampleCountPerSentBlock = 0;
// START declaration buffers
float* m_bufferRawUnicornDevice = nullptr; // buffer 1 : data from device to ring buffer
float* m_bufferReceivedDataFromRing = nullptr; // buffer 2 : data from ring buffer
float* m_bufferForOpenVibe = nullptr; // buffer 3 : data converted to OpenVibe format
// END declaration buffers
uint32_t m_acquiredChannelCount = kGTecNumChannels; //number of channels specified by the user, never counts the event channels
size_t m_totalHardwareStimulations = 0; //since start button clicked
size_t m_totalRingBufferOverruns = 0;
size_t m_totalCounterErrors = 0;
bool m_flagIsFirstLoop = true;
bool m_bufferOverrun = false;
// ring buffer provided by Guger
CRingBuffer<float> m_ringBuffer;
std::unique_ptr<std::thread> m_thread;
bool m_isThreadRunning = false;
std::mutex m_mutex;
std::condition_variable m_itemAvailable;
// List of amplifiers
std::vector<GDevice> m_devices;
uint32_t m_lengthBufferRawUnicornDevice = 0;
size_t m_channelCounterIndex = 0;
bool configureDevice(size_t deviceNumber);
void detectDevices();
size_t numDevices() const { return m_devices.size(); };
size_t getChannelIndex(UNICORN_HANDLE hDevice, const char *name);
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGtecUnicron
@@ -0,0 +1,410 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkAdjustment" id="adjustment1">
<property name="value">1</property>
<property name="lower">1</property>
<property name="upper">8</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment3">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkListStore" id="model1">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">unspecified</col>
</row>
<row>
<col id="0" translatable="yes">female</col>
</row>
<row>
<col id="0" translatable="yes">male</col>
</row>
<row>
<col id="0" translatable="yes">unknown</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model2">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">/dev/rfcomm0</col>
</row>
<row>
<col id="0" translatable="yes">/dev/rfcomm1</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model3">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">256</col>
</row>
</data>
</object>
<object class="GtkDialog" id="openvibe-acquisition-server-settings">
<property name="border_width">5</property>
<property name="title" translatable="yes">Device configuration</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel" id="label_title">
<property name="visible">True</property>
<property name="label" translatable="yes">g.Tec gMobi Lab+</property>
<property name="justify">center</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox">
<property name="visible">True</property>
<property name="border_width">8</property>
<property name="spacing">8</property>
<child>
<object class="GtkHBox" id="hbox">
<property name="visible">True</property>
<property name="spacing">8</property>
<child>
<object class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="n_rows">6</property>
<property name="n_columns">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkSpinButton" id="spinbutton_number_of_channels">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">0</property>
<property name="adjustment">adjustment1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_number_of_channels">
<property name="visible">True</property>
<property name="label" translatable="yes">Number of channels :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_gender">
<property name="visible">True</property>
<property name="model">model1</property>
<child>
<object class="GtkCellRendererText" id="renderer1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_age">
<property name="visible">True</property>
<property name="label" translatable="yes">Age :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_gender">
<property name="visible">True</property>
<property name="label" translatable="yes">Gender :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_age">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment2</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_identifier">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment3</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_identifier">
<property name="visible">True</property>
<property name="label" translatable="yes">Identifier :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label_port">
<property name="visible">True</property>
<property name="xpad">5</property>
<property name="ypad">5</property>
<property name="label" translatable="yes">Port name :</property>
<property name="justify">center</property>
<property name="selectable">True</property>
<property name="width_chars">10</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry_port">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
<property name="text" translatable="yes">/dev/rfcomm0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_testmode">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Use test mode :</property>
</object>
<packing>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_testmode">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
</packing>
</child>
<!--
<child>
<object class="GtkComboBoxEntry" id="combobox_port">
<property name="visible">True</property>
<property name="model">model2</property>
<property name="active">1</property>
<property name="button_sensitivity">on</property>
<property name="text_column">0</property>
<child>
<object class="GtkCellRendererText" id="renderer2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
-->
<child>
<object class="GtkComboBox" id="combobox_sampling_frequency">
<property name="visible">True</property>
<property name="model">model3</property>
<child>
<object class="GtkCellRendererText" id="renderer3"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_sampling_frequency">
<property name="visible">True</property>
<property name="label" translatable="yes">Sampling frequency :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_change_channel_names">
<property name="label" translatable="yes">Change channel names...</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">4</property>
</packing>
</child>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_apply">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-10">button_apply</action-widget>
<action-widget response="-6">button_cancel</action-widget>
</action-widgets>
</object>
</interface>
@@ -0,0 +1,14 @@
To use the gTec gMOBIlab+ device, it is necessary to connect it via Blutooth to the computer.
First the computer has to recognize the new peripheral (the gTec gMOBIlab+) and then the computer has to be connected to it.
The command on linux is :
rfcomm connect # xx:xx:xx:xx:xx:xx
Where # has to be replaced by a port number (0 if you want to connect to /dev/rfcomm0) and xx:xx:xx:xx:xx:xx is the mac address of the gTec module.
Once the device is connected, the OpenViBE-aquisition-server file can be run. A pop up appears with a menu containing "gTec gMOBIlab+", that has to be selected.
After checking in the properties that the port name is the same as the one you are connected to (default name is /dev/rfcomm0), it is possible to try to connect.
If the connection to the server fails, the state of the gMOBIlab+ should be checked : the diode of the gMOBIlab+ device should flash on and off.
The frequency of the gTecMobiLab+ is 256 Hz.
@@ -0,0 +1,46 @@
/**
* The gMobilab driver was contributed
* by Lucie Daubigney from Supelec Metz
*/
#include "ovasCConfigurationGTecGMobiLabPlus.h"
#if defined TARGET_HAS_ThirdPartyGMobiLabPlusAPI
namespace OpenViBE {
namespace AcquisitionServer {
CConfigurationGTecGMobiLabPlus::CConfigurationGTecGMobiLabPlus(const char* gtkBuilderFilename, std::string& portName, bool& testMode)
: CConfigurationBuilder(gtkBuilderFilename), m_portName(portName), m_testMode(testMode) {}
bool CConfigurationGTecGMobiLabPlus::preConfigure()
{
if (!CConfigurationBuilder::preConfigure()) { return false; }
GtkEntry* port = GTK_ENTRY(gtk_builder_get_object(m_builder, "entry_port"));
gtk_entry_set_text(port, m_portName.c_str());
GtkToggleButton* testMode = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_testmode"));
gtk_toggle_button_set_active(testMode, m_testMode);
return true;
}
bool CConfigurationGTecGMobiLabPlus::postConfigure()
{
if (m_applyConfig)
{
GtkEntry* port = GTK_ENTRY(gtk_builder_get_object(m_builder, "entry_port"));
m_portName = gtk_entry_get_text(port);
GtkToggleButton* testMode = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_testmode"));
m_testMode = (gtk_toggle_button_get_active(testMode) > 0);
}
return CConfigurationBuilder::postConfigure();
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGMobiLabPlusAPI
@@ -0,0 +1,37 @@
/**
* The gMobilab driver was contributed
* by Lucie Daubigney from Supelec Metz
*/
#pragma once
#include "../ovasCConfigurationBuilder.h"
#if defined TARGET_HAS_ThirdPartyGMobiLabPlusAPI
// #ifdef TARGET_OS_Windows
// #include <Windows.h>
// #endif
#include <gtk/gtk.h>
namespace OpenViBE {
namespace AcquisitionServer {
class CConfigurationGTecGMobiLabPlus final : public CConfigurationBuilder
{
public:
CConfigurationGTecGMobiLabPlus(const char* gtkBuilderFilename, std::string& portName, bool& testMode);
bool preConfigure() override;
bool postConfigure() override;
private:
std::string& m_portName;
bool& m_testMode;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGMobiLabPlusAPI
@@ -0,0 +1,432 @@
/**
* The gMobilab Linux driver was contributed by Lucie Daubigney from Supelec Metz
*
* Windows compatibility + gusbamp coexistence added by Jussi T. Lindgren / Inria
*
*/
#include "ovasCDriverGTecGMobiLabPlus.h"
#include "ovasCConfigurationGTecGMobiLabPlus.h"
#if defined TARGET_HAS_ThirdPartyGMobiLabPlusAPI
#include "ovasCDriverGTecGMobiLabPlusPrivate.h"
#include <toolkit/ovtk_all.h>
#include <system/ovCTime.h>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <iostream>
#if defined(TARGET_OS_Linux)
#include <dlfcn.h>
#endif
namespace OpenViBE {
namespace AcquisitionServer {
static const uint32_t N_ACQUIRED_CHANNEL = 8;
//constructor
CDriverGTecGMobiLabPlus::CDriverGTecGMobiLabPlus(IDriverContext& ctx)
: IDriver(ctx)
, m_settings("AcquisitionServer_Driver_GTecMobiLabPlus", m_driverCtx.getConfigurationManager())
, m_library(nullptr)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGTecGMobiLabPlus::CDriverGTecGMobiLabPlus\n";
m_gTec = new CDriverGTecGMobiLabPlusPrivate();
m_header = new CHeader();
m_header->setSamplingFrequency(256);
m_header->setChannelCount(8);
m_gTec->m_oBuffer.pBuffer = nullptr;
m_gTec->m_oBuffer.size = 0;
m_gTec->m_oBuffer.validPoints = 0;
#if defined(TARGET_OS_Windows)
m_portName = "//./COM1";
#else
m_portName="/dev/rfcomm0";
#endif
//initialisation of the analog channels of the gTec module : by default no analog exchange are allowed
m_gTec->m_analogIn.ain1 = false;
m_gTec->m_analogIn.ain2 = false;
m_gTec->m_analogIn.ain3 = false;
m_gTec->m_analogIn.ain4 = false;
m_gTec->m_analogIn.ain5 = false;
m_gTec->m_analogIn.ain6 = false;
m_gTec->m_analogIn.ain7 = false;
m_gTec->m_analogIn.ain8 = false;
m_settings.add("Header", m_header);
m_settings.add("PortName", &m_portName);
m_settings.add("TestMode", &m_testMode);
m_settings.load();
}
CDriverGTecGMobiLabPlus::~CDriverGTecGMobiLabPlus()
{
delete m_header;
delete m_gTec;
}
void CDriverGTecGMobiLabPlus::release()
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGTecGMobiLabPlus::release\n";
delete this;
}
const char* CDriverGTecGMobiLabPlus::getName()
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGTecGMobiLabPlus::getName\n";
return "g.tec gMOBIlab+";
}
//___________________________________________________________________//
// //
/*
* configuration
*/
bool CDriverGTecGMobiLabPlus::isConfigurable()
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGTecGMobiLabPlus::isConfigurable\n";
return true;
}
bool CDriverGTecGMobiLabPlus::configure()
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGTecGMobiLabPlus::configure\n";
// We use CConfigurationGTecMobilabPlus configuration which is a class that inheritate from the CConfigurationBuilder class
// The difference between these two classes is the addition of a member of class. This member allows to change the port where is connected the device.
CConfigurationGTecGMobiLabPlus config(Directories::getDataDir() + "/applications/acquisition-server/interface-GTec-GMobiLabPlus.ui", m_portName,
m_testMode);
// We configure the Header with it...
if (!config.configure(*m_header)) { return false; }
if (m_header->getChannelCount() > N_ACQUIRED_CHANNEL) { m_header->setChannelCount(N_ACQUIRED_CHANNEL); }
m_settings.save();
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Port name after configuration " << m_portName << " \n";
return true;
}
//___________________________________________________________________//
// //
#if defined(TARGET_OS_Linux)
#define GetProcAddress dlsym
#define FreeLibrary dlclose
#endif
bool CDriverGTecGMobiLabPlus::registerLibraryFunctions()
{
// Lets open the DLL
#if defined(TARGET_OS_Windows)
m_library = LoadLibrary("gMOBIlabplus.dll");
#else
m_library = dlopen("libgmobilabplusapi.so", RTLD_LAZY);
#endif
if (!m_library)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "CDriverGTecGMobiLabPlus:: Unable to open gMOBIlabplus.dll\n";
return false;
}
m_gTec->m_fOpenDevice = CDriverGTecGMobiLabPlusPrivate::OV_GT_OpenDevice(GetProcAddress(m_library, "GT_OpenDevice"));
m_gTec->m_fCloseDevice = CDriverGTecGMobiLabPlusPrivate::OV_GT_CloseDevice(GetProcAddress(m_library, "GT_CloseDevice"));
m_gTec->m_fSetTestmode = CDriverGTecGMobiLabPlusPrivate::OV_GT_SetTestmode(GetProcAddress(m_library, "GT_SetTestmode"));
m_gTec->m_fStartAcquisition = CDriverGTecGMobiLabPlusPrivate::OV_GT_StartAcquisition(GetProcAddress(m_library, "GT_StartAcquisition"));
m_gTec->m_fGetData = CDriverGTecGMobiLabPlusPrivate::OV_GT_GetData(GetProcAddress(m_library, "GT_GetData"));
m_gTec->m_fInitChannels = CDriverGTecGMobiLabPlusPrivate::OV_GT_InitChannels(GetProcAddress(m_library, "GT_InitChannels"));
m_gTec->m_fStopAcquisition = CDriverGTecGMobiLabPlusPrivate::OV_GT_StopAcquisition(GetProcAddress(m_library, "GT_StopAcquisition"));
m_gTec->m_fGetLastError = CDriverGTecGMobiLabPlusPrivate::OV_GT_GetLastError(GetProcAddress(m_library, "GT_GetLastError"));
m_gTec->m_fTranslateErrorCode = CDriverGTecGMobiLabPlusPrivate::OV_GT_TranslateErrorCode(GetProcAddress(m_library, "GT_TranslateErrorCode"));
if (!m_gTec->m_fOpenDevice || !m_gTec->m_fCloseDevice || !m_gTec->m_fSetTestmode
|| !m_gTec->m_fStartAcquisition || !m_gTec->m_fGetData || !m_gTec->m_fInitChannels
|| !m_gTec->m_fStopAcquisition || !m_gTec->m_fGetLastError || !m_gTec->m_fTranslateErrorCode)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "CDriverGTecGMobiLabPlus:: Unable to find all the required functions from the gMOBIlabplus.dll\n";
return false;
}
return true;
}
/*
* initialisation
*/
bool CDriverGTecGMobiLabPlus::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGTecGMobiLabPlus::initialize\n";
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Port name after initialisation " << m_portName << "\n";
if (m_driverCtx.isConnected()) { return false; }
if (!m_header->isChannelCountSet() || !m_header->isSamplingFrequencySet())
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Either channel count or sampling frequency is not set\n";
return false;
}
if (!registerLibraryFunctions()) { return false; }
const size_t nChannel = m_header->getChannelCount();
// analog exchanges allowed on the first "nChannel" channels:
for (uint32_t i = 1; i <= nChannel; ++i) { allowAnalogInputs(i); }
// then buffer of type _BUFFER_ST built to store acquired samples.
m_gTec->m_oBuffer.pBuffer = new short int[nChannel
];//allocate enough space for the buffer m_oBuffer.pBuffer ; only one set of mesures is acquired (channel 1 to 8) in a row
m_gTec->m_oBuffer.size = nChannel * sizeof(short int);
m_gTec->m_oBuffer.validPoints = 0;
#if defined(TARGET_OS_Windows)
m_gTec->m_oOverlap.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
m_gTec->m_oOverlap.Offset = 0;
m_gTec->m_oOverlap.OffsetHigh = 0;
#endif
// allocates enough space for m_sample
m_sample = new float[nSamplePerSentBlock * nChannel];
// if there is a problem while creating the two arrays
if (!m_gTec->m_oBuffer.pBuffer || !m_sample)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Memory allocation problem\n";
delete [] m_gTec->m_oBuffer.pBuffer;
delete [] m_sample;
m_sample = nullptr;
m_gTec->m_oBuffer.pBuffer = nullptr;
return false;
}
// initializes hardware and get
// available header information
// from it
#if defined(TARGET_OS_Windows)
m_gTec->m_device = m_gTec->m_fOpenDevice(LPSTR(m_portName.c_str()));
#else
m_gTec->m_device = m_gTec->m_fOpenDevice(m_portName.c_str());
#endif
if (m_gTec->m_device == nullptr)
{
#if defined(TARGET_OS_Windows)
UINT errorCode = 0;
#else
uint32_t errorCode = 0;
#endif
_ERRSTR error;
m_gTec->m_fGetLastError(&errorCode);
m_gTec->m_fTranslateErrorCode(&error, errorCode);
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unable to connect to [" << m_portName << "], error code " << errorCode << ": '" << error.Error
<< "'\n";
delete [] m_gTec->m_oBuffer.pBuffer;
delete [] m_sample;
return false;
}
// saves parameters
m_callback = &callback;
m_nSamplePerSentBlock = nSamplePerSentBlock;
return true;
}
bool CDriverGTecGMobiLabPlus::uninitialize()
{
bool ok = true;
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGTecGMobiLabPlus::uninitialize\n";
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// uninitializes hardware here
if (!m_gTec->m_fCloseDevice(m_gTec->m_device))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "GT_CloseDevice() failed\n";
ok = false;
}
// frees memory
delete [] m_sample;
delete [] m_gTec->m_oBuffer.pBuffer;
m_sample = nullptr;
m_gTec->m_oBuffer.pBuffer = nullptr;
m_callback = nullptr;
// uninitialisation of the analog channels : set valus to default ones
m_gTec->m_analogIn.ain1 = false;
m_gTec->m_analogIn.ain2 = false;
m_gTec->m_analogIn.ain3 = false;
m_gTec->m_analogIn.ain4 = false;
m_gTec->m_analogIn.ain5 = false;
m_gTec->m_analogIn.ain6 = false;
m_gTec->m_analogIn.ain7 = false;
m_gTec->m_analogIn.ain8 = false;
if (m_library)
{
FreeLibrary(m_library);
m_library = nullptr;
}
return ok;
}
const IHeader* CDriverGTecGMobiLabPlus::getHeader() { return m_header; }
//___________________________________________________________________//
// //
/*
* acquisition
*/
bool CDriverGTecGMobiLabPlus::start()
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGTecGMobiLabPlus::start\n";
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// we use none of the digital inputs/outputs
_DIO digitalInOut;
digitalInOut.dio1_enable = false;
digitalInOut.dio2_enable = false;
digitalInOut.dio3_enable = false;
digitalInOut.dio4_enable = false;
digitalInOut.dio5_enable = false;
digitalInOut.dio6_enable = false;
digitalInOut.dio7_enable = false;
digitalInOut.dio8_enable = false;
// channel initialisation
if (!m_gTec->m_fInitChannels(m_gTec->m_device, m_gTec->m_analogIn, digitalInOut))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "GT_InitChannels failed\n";
return false;
}
// are we interested in test signal?
m_gTec->m_fSetTestmode(m_gTec->m_device, m_testMode);
// requests hardware to start sending data
if (!m_gTec->m_fStartAcquisition(m_gTec->m_device))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "GT_StartAcquisition failed\n";
return false;
}
return true;
}
bool CDriverGTecGMobiLabPlus::loop()
{
// m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGTecGMobiLabPlus::loop\n";
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return true; }
const size_t nChannel = m_header->getChannelCount();
// only "l-nChannel" measures corresponding to one measure per channel are acquired in a row with the function GT_GetData()
// these measures are stored in m_oBuffer.pBuffer[]
// the acquisition is reapeted m_sampleCountPerSendBlock times to fill in the array "m_sample"
for (uint32_t i = 0; i < m_nSamplePerSentBlock; ++i)
{
#if defined(TARGET_OS_Windows)
if (!m_gTec->m_fGetData(m_gTec->m_device, &m_gTec->m_oBuffer, &m_gTec->m_oOverlap))// receive samples from hardware (one per channel)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "GT_GetData failed\n";
return false;
}
if (WaitForSingleObject(m_gTec->m_oOverlap.hEvent, 1000) == WAIT_TIMEOUT)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Timeout in reading from the device\n";
return false;
}
#else
if (!m_gTec->m_fGetData(m_gTec->m_device, &m_gTec->m_oBuffer))// receive samples from hardware (one per channel)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "GT_GetData failed\n";
return false;
}
#endif
// here the "nChannel" measures just acquired are stored in m_sample not to be deleted by the next acquisition
// m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Here are the " << nChannel << " measures of the " << i << " th sample\n" << Kernel::LogLevel_Debug;
for (uint32_t j = 0; j < nChannel; ++j)
{
// m_driverCtx.getLogManager() << (m_oBuffer.pBuffer[j]*0.5)/32768. << " ";
//operation made to modify the short int in a number between 0 and 500mV (in Volt)
m_sample[m_nSamplePerSentBlock * j + i] = float((m_gTec->m_oBuffer.pBuffer[j] * 0.5) / 32768.);
}
// m_driverCtx.getLogManager() << "\n";
}
// the buffer is full : it is send to the acquisition server
m_callback->setSamples(m_sample, m_nSamplePerSentBlock);
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
return true;
}
bool CDriverGTecGMobiLabPlus::stop()
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGTecGMobiLabPlus::stop\n";
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
// requests the hardware to stop sending data
if (!m_gTec->m_fStopAcquisition(m_gTec->m_device))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "GT_StopAcquisition failed\n";
return false;
}
return true;
}
// this function allows exchanges of data on the "index" channel
// function used to initialize the analog inputs according to the number "nChannel"
void CDriverGTecGMobiLabPlus::allowAnalogInputs(uint32_t index)
{
switch (index)
{
case 8: m_gTec->m_analogIn.ain8 = true;
break;
case 7: m_gTec->m_analogIn.ain7 = true;
break;
case 6: m_gTec->m_analogIn.ain6 = true;
break;
case 5: m_gTec->m_analogIn.ain5 = true;
break;
case 4: m_gTec->m_analogIn.ain4 = true;
break;
case 3: m_gTec->m_analogIn.ain3 = true;
break;
case 2: m_gTec->m_analogIn.ain2 = true;
break;
case 1: m_gTec->m_analogIn.ain1 = true;
break;
default: m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Unexpected value " << index << " in CDriverGTecGMobiLabPlus::allowAnalogInputs\n";
break;
}
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGMobiLabPlusAPI
@@ -0,0 +1,86 @@
/**
* The gMobilab driver was contributed
* by Lucie Daubigney from Supelec Metz
*/
#pragma once
#if defined TARGET_HAS_ThirdPartyGMobiLabPlusAPI
#include "ovasIDriver.h"
#include "../ovasCHeader.h"
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
#include <string>
#ifdef TARGET_OS_Windows
#include <Windows.h>
#endif
namespace OpenViBE {
namespace AcquisitionServer {
class CDriverGTecGMobiLabPlusPrivate; // fwd declare
/**
* \class CDriverGTecGMobiLabPlus
* \author Lucie Daubigney (Supelec Metz)
*/
class CDriverGTecGMobiLabPlus final : public IDriver
{
public:
explicit CDriverGTecGMobiLabPlus(IDriverContext& ctx);
~CDriverGTecGMobiLabPlus() override;
void release();
const char* getName() override;
bool isFlagSet(const EDriverFlag flag) const override { return flag == EDriverFlag::IsUnstable; }
//configuration
bool isConfigurable() override;
bool configure() override;
//initialisation
bool initialize(const uint32_t sampleCountPerChannel, IDriverCallback& callback) override;
bool uninitialize() override;
const IHeader* getHeader() override;
//acquisition
bool start() override;
bool stop() override;
bool loop() override;
protected:
SettingsHelper m_settings;
//usefull data to communicate with OpenViBE
IHeader* m_header = nullptr;
IDriverCallback* m_callback = nullptr;
uint32_t m_nSamplePerSentBlock = 0;//number of sample you want to send in a row
float* m_sample = nullptr;//array containing the data to sent to OpenViBE once they had been recovered from the gTec module
//params
std::string m_portName;
bool m_testMode = false;
// Pointers do gtec-specific data and function pointers
CDriverGTecGMobiLabPlusPrivate* m_gTec = nullptr;
// Register the function pointers from the dll. (The dll approach
// is used with gMobilab to avoid conflicts with the gUSBAmp lib)
bool registerLibraryFunctions();
#if defined(TARGET_OS_Windows)
HINSTANCE m_library;
#elif defined(TARGET_OS_Linux)
void* m_library = nullptr;
#endif
private:
void allowAnalogInputs(uint32_t index);
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGMobiLabPlusAPI
@@ -0,0 +1,85 @@
/*
* The only purpose of this class is to hide the gtec API
* data types from the ov driver header. An approach resembling
* this is sometimes called a 'd-pointer'.
*
* Previous situation:
*
* gUsbamp ov driver header depends on gtec gUSBAmp API types
* gMobilab ov driver header depends on gtec gMobilab API types
* Acquisition Server includes both these driver headers
*
* But the gtec types are declared differently in the two gtec APIs.
* Hence we get a conflict if both headers are included by the
* same compilation unit.
*
* With this class, the gmobilab API is not exposed just by
* including ovasCDriverGtecMobiLabPlus.h to the Acquisition Server.
*
*/
#pragma once
#if defined TARGET_HAS_ThirdPartyGMobiLabPlusAPI
#include <gMOBIlabplus.h>
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \author Jussi T. Lindgren (Inria)
*
* The class collects all members that used to be in
* CDriverGtecMobiLabPlus header that depend on the gmobilab API.
*
*/
class CDriverGTecGMobiLabPlusPrivate
{
public:
//useful data to communicate with the gTec module
_BUFFER_ST m_oBuffer;
HANDLE m_device;
_AIN m_analogIn;
#if defined(TARGET_OS_Windows)
OVERLAPPED m_oOverlap;
#endif
// These functions are defined in the gmobilab library
#if defined(TARGET_OS_Windows)
typedef HANDLE (__stdcall *OV_GT_OpenDevice)(LPSTR lpPort);
typedef BOOL (__stdcall *OV_GT_CloseDevice)(HANDLE hDevice);
typedef BOOL (__stdcall *OV_GT_SetTestmode)(HANDLE hDevice, BOOL Testmode);
typedef BOOL (__stdcall *OV_GT_StartAcquisition)(HANDLE hDevice);
typedef BOOL (__stdcall *OV_GT_GetData)(HANDLE hDevice, _BUFFER_ST* buffer, LPOVERLAPPED lpOvl);
typedef BOOL (__stdcall *OV_GT_InitChannels)(HANDLE hDevice, _AIN analogCh, _DIO digitalCh);
typedef bool (__stdcall *OV_GT_StopAcquisition)(HANDLE hDevice);
typedef bool (__stdcall *OV_GT_GetLastError)(UINT* LastError);
typedef bool (__stdcall *OV_GT_TranslateErrorCode)(_ERRSTR* ErrorString, UINT ErrorCode);
#else
typedef HANDLE(*OV_GT_OpenDevice)(const char* lpPort);
typedef bool(*OV_GT_CloseDevice)(HANDLE hDevice);
typedef bool(*OV_GT_SetTestmode)(HANDLE hDevice, bool Testmode);
typedef bool(*OV_GT_StartAcquisition)(HANDLE hDevice);
typedef bool(*OV_GT_GetData)(HANDLE hDevice, _BUFFER_ST *buffer);
typedef bool(*OV_GT_InitChannels)(HANDLE hDevice, _AIN analogCh, _DIO digitalCh);
typedef bool(*OV_GT_StopAcquisition)(HANDLE hDevice);
typedef bool(*OV_GT_GetLastError)(uint32_t* LastError);
typedef bool(*OV_GT_TranslateErrorCode)(_ERRSTR *ErrorString, uint32_t ErrorCode);
#endif
// Function pointers to the dll
OV_GT_OpenDevice m_fOpenDevice;
OV_GT_CloseDevice m_fCloseDevice;
OV_GT_SetTestmode m_fSetTestmode;
OV_GT_StartAcquisition m_fStartAcquisition;
OV_GT_GetData m_fGetData;
OV_GT_InitChannels m_fInitChannels;
OV_GT_StopAcquisition m_fStopAcquisition;
OV_GT_GetLastError m_fGetLastError;
OV_GT_TranslateErrorCode m_fTranslateErrorCode;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGMobiLabPlusAPI
@@ -0,0 +1,969 @@
#if defined(TARGET_HAS_ThirdPartyGNEEDaccessAPI)
#include "ovasCConfigurationgNautilusInterface.h"
namespace OpenViBE {
namespace AcquisitionServer {
/*_________________________________________________
Insert callback to specific widget here
Example with a button that launch a calibration of the device:
*/
//Callback connected to a dedicated gtk button (button_select_channels_bipolar_car_noise):
static void ChannelSettingsCB(GtkButton* button, void* data) { static_cast<CConfigurationgNautilusInterface*>(data)->buttonChannelSettingsPressedCB(); }
//Callback connected to a dedicated gtk button (button_select_sensitivity_filters):
static void SensitivityFiltersCB(GtkButton* button, void* data) { static_cast<CConfigurationgNautilusInterface*>(data)->buttonSensitivityFiltersPressedCB(); }
//Callback connected to a dedicated gtk button (button_sensitivity_filters_apply):
static void SensitivityFiltersApplyCB(GtkButton* button, void* data)
{
static_cast<CConfigurationgNautilusInterface*>(data)->buttonSensitivityFiltersApplyPressedCB();
}
//Callback connected to a dedicated gtk button (button_channel_apply):
static void ChannelSettingsApplyCB(GtkButton* button, void* data)
{
static_cast<CConfigurationgNautilusInterface*>(data)->buttonChannelSettingsApplyPressedCB();
}
//Callback actually called:
void CConfigurationgNautilusInterface::buttonChannelSettingsPressedCB()
{
// Connect to the hardware, ask for calibration, verify the return code, etc.
GtkWidget* dialog = GTK_WIDGET(gtk_builder_get_object(m_builder,"dialog_select_channels_bipolar_car_noise"));
gint resp = gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_hide(dialog);
}
//Callback actually called:
void CConfigurationgNautilusInterface::buttonSensitivityFiltersPressedCB()
{
// get bandpass and notch filters for currenty selected sampling rate and set to filter list in corresponding dialog
GtkWidget* dialog = GTK_WIDGET(gtk_builder_get_object(m_builder,"dialog_sensitivity_filters"));
gint resp = gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_hide(dialog);
}
//Callback actually called:
void CConfigurationgNautilusInterface::buttonSensitivityFiltersApplyPressedCB()
{
// get handle to sensitivity and filters dialog and close it
GtkWidget* dialog = GTK_WIDGET(gtk_builder_get_object(m_builder,"dialog_sensitivity_filters"));
gtk_widget_hide(dialog);
}
//Callback actually called:
void CConfigurationgNautilusInterface::buttonChannelSettingsApplyPressedCB()
{
// get handle to channel settings dialog and close it
GtkWidget* dialog = GTK_WIDGET(gtk_builder_get_object(m_builder,"dialog_select_channels_bipolar_car_noise"));
gtk_widget_hide(dialog);
// get number of channels selected and set range as number of channels starting at number of channels
uint16_t nChannels = 0;
char tmp[30];
for (uint32_t i = 0; i < GDS_GNAUTILUS_CHANNELS_MAX; ++i)
{
// set electrode names as channel names in channel selection dialog
sprintf_s(&tmp[0], 30, "checkbutton_channel_%d", (i + 1));
GtkButton* checkButton = GTK_BUTTON(gtk_builder_get_object(m_builder,tmp));
if (checkButton)
{
if ((gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkButton))) && (gtk_widget_get_visible(GTK_WIDGET(checkButton)))) { nChannels += 1; }
}
}
GtkSpinButton* button = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder,"spinbutton_number_of_channels"));
gtk_spin_button_set_range(button, nChannels, nChannels);
gtk_spin_button_set_value(button, nChannels);
}
// catch combobox sampling rate changed signal and call function which handles event
static void sample_rate_changed_cb(GtkComboBox* comboBox, void* data) { static_cast<CConfigurationgNautilusInterface*>(data)->comboboxSampleRateChangedCB(); }
// Callback actually called
void CConfigurationgNautilusInterface::comboboxSampleRateChangedCB()
{
// get hardware filters according to sampling frequency currently selected
const bool res = getFiltersForNewSamplingRate();
if (!res) { } // error logged in getFiltersForNewSamplingRate
}
// catch noise reduction checkbox toggled signal and call function which handles event
static void noise_reduction_changed_cb(GtkCheckButton* pCheckbutton, void* data)
{
static_cast<CConfigurationgNautilusInterface*>(data)->checkbuttonNoiseReductionChangedCB();
}
// Callback actually called
void CConfigurationgNautilusInterface::checkbuttonNoiseReductionChangedCB()
{
// activate/deactivate noise reduction checkboxes in dialog_select_channels_bipolar_car_noise
GtkCheckButton* buttonNoise = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,"checkbutton_noise_reduction"));
const gboolean buttonValue = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttonNoise));
char tmp[45];
for (uint16_t i = 0; i < GDS_GNAUTILUS_CHANNELS_MAX; ++i)
{
sprintf_s(&tmp[0], 45, "checkbutton_noise_channel_%d", (i + 1));
GtkCheckButton* buttonNoiseChannel = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,tmp));
if (buttonNoiseChannel)
{
if (gtk_widget_get_visible(GTK_WIDGET(buttonNoiseChannel))) { gtk_widget_set_sensitive(GTK_WIDGET(buttonNoiseChannel), buttonValue); }
}
}
}
// catch car checkbox toggled signal and call function which handles event
static void car_changed_cb(GtkCheckButton* /*button*/, void* data) { static_cast<CConfigurationgNautilusInterface*>(data)->checkbuttonCARChangedCB(); }
// Callback actually called
void CConfigurationgNautilusInterface::checkbuttonCARChangedCB()
{
// activate/deactivate car checkboxes in dialog_select_channels_bipolar_car_noise
GtkCheckButton* buttonCAR = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,"checkbutton_car"));
const gboolean buttonValue = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttonCAR));
char tmp[45];
for (uint16_t i = 0; i < GDS_GNAUTILUS_CHANNELS_MAX; ++i)
{
sprintf_s(&tmp[0], 45, "checkbutton_car_channel_%d", (i + 1));
GtkCheckButton* buttonCarChannel = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,tmp));
if (buttonCarChannel)
{
if (gtk_widget_get_visible(GTK_WIDGET(buttonCarChannel))) { gtk_widget_set_sensitive(GTK_WIDGET(buttonCarChannel), buttonValue); }
}
}
}
// get hardware related settings from GDS
bool CConfigurationgNautilusInterface::getHardwareSettings()
{
GtkTreeIter iter;
uint32_t i;
// get network channel and set in dialog and set one as active in dialog
size_t supportedNwChannelsCount;
// get number of supported network channels to allocate memory to hold supported network channels
m_gdsResult = GDS_GNAUTILUS_GetSupportedNetworkChannels(m_deviceHandle, m_deviceNames, nullptr, &supportedNwChannelsCount);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
uint32_t* supportedNwChannels = new uint32_t[supportedNwChannelsCount];
m_gdsResult = GDS_GNAUTILUS_GetSupportedNetworkChannels(m_deviceHandle, m_deviceNames, supportedNwChannels, &supportedNwChannelsCount);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// get network channel currently used between base station and headstage
uint32_t val;
m_gdsResult = GDS_GNAUTILUS_GetNetworkChannel(m_deviceHandle, m_deviceNames, &val);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// put network channels to combobox
GtkComboBox* comboBoxNetworkChannel = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_network_channel"));
GtkTreeModel* listStoreNetworkChannel = gtk_combo_box_get_model(comboBoxNetworkChannel);
gtk_list_store_clear(GTK_LIST_STORE(listStoreNetworkChannel));
std::stringstream networkChannel;
if (m_comboBoxNetworkChannels.size() > 0) m_comboBoxNetworkChannels.clear();
// fill network channel combobox with available network channels
for (i = 0; i < supportedNwChannelsCount; ++i)
{
networkChannel << supportedNwChannels[i];
gtk_list_store_append(GTK_LIST_STORE(listStoreNetworkChannel), &iter);
gtk_list_store_set(GTK_LIST_STORE(listStoreNetworkChannel), &iter, 0, networkChannel.str().c_str(), -1);
networkChannel.clear();
networkChannel.str("");
m_comboBoxNetworkChannels.push_back(supportedNwChannels[i]);
}
const auto it = find(m_comboBoxNetworkChannels.begin(), m_comboBoxNetworkChannels.end(), val);
const size_t nwChIndex = distance(m_comboBoxNetworkChannels.begin(), it);
gtk_combo_box_set_active(comboBoxNetworkChannel, nwChIndex);
delete [] supportedNwChannels;
// get supported sample rates
size_t nSampling;
// get number of supported sample rates
m_gdsResult = GDS_GNAUTILUS_GetSupportedSamplingRates(m_deviceHandle, m_deviceNames, nullptr, &nSampling);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
uint32_t* sampling = new uint32_t[nSampling];
// get supported sample rates
m_gdsResult = GDS_GNAUTILUS_GetSupportedSamplingRates(m_deviceHandle, m_deviceNames, sampling, &nSampling);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// set sample rates as content of combo box in corresponding dialog
GtkComboBox* comboBoxSamplings = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_sampling_frequency"));
GtkTreeModel* listStoreSamplings = gtk_combo_box_get_model(comboBoxSamplings);
gtk_list_store_clear(GTK_LIST_STORE(listStoreSamplings));
for (i = 0; i < nSampling; ++i)
{
std::string str = std::to_string(sampling[i]);
gtk_list_store_append(GTK_LIST_STORE(listStoreSamplings), &iter);
gtk_list_store_set(GTK_LIST_STORE(listStoreSamplings), &iter, 0, str.c_str(), -1);
}
gtk_combo_box_set_active(comboBoxSamplings, 0);
const bool functionReturn = getFiltersForNewSamplingRate();
if (!functionReturn) { return false; }
size_t nSensitivities;
// get number of sensitivities
m_gdsResult = GDS_GNAUTILUS_GetSupportedSensitivities(m_deviceHandle, m_deviceNames, nullptr, &nSensitivities);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// get sensitivities
double* sensitivities = new double[nSensitivities];
m_gdsResult = GDS_GNAUTILUS_GetSupportedSensitivities(m_deviceHandle, m_deviceNames, sensitivities, &nSensitivities);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// set items in dialog item combobox_select_sensitivity
GtkComboBox* comboBoxSensitivities = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_select_sensitivity"));
GtkTreeModel* listStoreSensitivities = gtk_combo_box_get_model(comboBoxSensitivities);
gtk_list_store_clear(GTK_LIST_STORE(listStoreSensitivities));
for (i = 0; i < nSensitivities; ++i)
{
std::string str = std::to_string(sensitivities[i] / 1000);
gtk_list_store_append(GTK_LIST_STORE(listStoreSensitivities), &iter);
gtk_list_store_set(GTK_LIST_STORE(listStoreSensitivities), &iter, 0, str.c_str(), -1);
m_comboBoxSensitivityValues.push_back(sensitivities[i]);
}
gtk_combo_box_set_active(comboBoxSensitivities, 0);
delete [] sensitivities;
size_t nInputSources;
// first get number of supported input sources (call with third parameter set to NULL)
m_gdsResult = GDS_GNAUTILUS_GetSupportedInputSources(m_deviceHandle, m_deviceNames, nullptr, &nInputSources);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// allocate memory to hold input sources
GDS_GNAUTILUS_INPUT_SIGNAL* inputSources = new GDS_GNAUTILUS_INPUT_SIGNAL[nInputSources];
// now get input sources
m_gdsResult = GDS_GNAUTILUS_GetSupportedInputSources(m_deviceHandle, m_deviceNames, inputSources, &nInputSources);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// set values to combobox_input_source (there are only three allowed at the moment, see code below)
GtkComboBox* comboBoxInputSources = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_input_source"));
GtkTreeModel* listStoreInputSources = gtk_combo_box_get_model(comboBoxInputSources);
gtk_list_store_clear(GTK_LIST_STORE(listStoreInputSources));
for (i = 0; i < nInputSources; ++i)
{
if (inputSources[i] == 0)
{
// electrode input
m_comboBoxInputSources.push_back(inputSources[i]);
gtk_list_store_append(GTK_LIST_STORE(listStoreInputSources), &iter);
gtk_list_store_set(GTK_LIST_STORE(listStoreInputSources), &iter, 0, "Electrode", -1);
}
else if (inputSources[i] == 1)
{
// shortcut
m_comboBoxInputSources.push_back(inputSources[i]);
gtk_list_store_append(GTK_LIST_STORE(listStoreInputSources), &iter);
gtk_list_store_set(GTK_LIST_STORE(listStoreInputSources), &iter, 0, "Shortcut", -1);
}
else if (inputSources[i] == 5)
{
// test signal
m_comboBoxInputSources.push_back(inputSources[i]);
gtk_list_store_append(GTK_LIST_STORE(listStoreInputSources), &iter);
gtk_list_store_set(GTK_LIST_STORE(listStoreInputSources), &iter, 0, "Test Signal", -1);
}
}
gtk_combo_box_set_active(comboBoxInputSources, 0);
return true;
}
// get channel names for hardware currently connected (cannot be changed as electrode grid is fixed)
bool CConfigurationgNautilusInterface::getChannelNames()
{
uint32_t nMountedModules;
size_t nElectrodeNames;
char tmp[30];
std::stringstream bipolarEntry;
// get number of mounted modules and electrode names currently available
nMountedModules = 0;
nElectrodeNames = 0;
m_gdsResult = GDS_GNAUTILUS_GetChannelNames(m_deviceHandle, m_deviceNames, &nMountedModules,NULL, &nElectrodeNames);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// set array of electrode names according to names currently available
char (*electrodeNames)[GDS_GNAUTILUS_ELECTRODE_NAME_LENGTH_MAX] = new char[nElectrodeNames][GDS_GNAUTILUS_ELECTRODE_NAME_LENGTH_MAX];
// get electrode names
m_gdsResult = GDS_GNAUTILUS_GetChannelNames(m_deviceHandle, m_deviceNames, &nMountedModules, electrodeNames, &nElectrodeNames);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
GtkComboBox* comboBoxBipolarChannels = GTK_COMBO_BOX(gtk_builder_get_object(m_builder,"combobox_channel_1"));
GtkTreeModel* listStoreBipolarChannels = gtk_combo_box_get_model(comboBoxBipolarChannels);
gtk_list_store_clear(GTK_LIST_STORE(listStoreBipolarChannels));
bipolarEntry << "none";
GtkTreeIter it;
gtk_list_store_append(GTK_LIST_STORE(listStoreBipolarChannels), &it);
gtk_list_store_set(GTK_LIST_STORE(listStoreBipolarChannels), &it, 0, bipolarEntry.str().c_str(), -1);
for (uint16_t i = 0; i < GDS_GNAUTILUS_CHANNELS_MAX; ++i)
{
// set electrode names as channel names in channel selection dialog
sprintf_s(&tmp[0], 30, "checkbutton_channel_%d", (i + 1));
GtkButton* buttonChannel = GTK_BUTTON(gtk_builder_get_object(m_builder,tmp));
if ((buttonChannel) && (i < nElectrodeNames))
{
gtk_button_set_label(buttonChannel, &electrodeNames[i][0]);
gtk_list_store_append(GTK_LIST_STORE(listStoreBipolarChannels), &it);
gtk_list_store_set(GTK_LIST_STORE(listStoreBipolarChannels), &it, 0, &electrodeNames[i][0], -1);
}
else if ((buttonChannel) && (i >= nElectrodeNames))
{
gtk_widget_set_sensitive(GTK_WIDGET(buttonChannel), false);
gtk_button_set_label(buttonChannel, "");
}
}
for (uint16_t i = 0; i < GDS_GNAUTILUS_CHANNELS_MAX; ++i)
{
sprintf_s(&tmp[0], 30, "combobox_channel_%d", (i + 1));
comboBoxBipolarChannels = GTK_COMBO_BOX(gtk_builder_get_object(m_builder,tmp));
if (comboBoxBipolarChannels)
{
gtk_combo_box_set_model(comboBoxBipolarChannels, listStoreBipolarChannels);
gtk_combo_box_set_active(comboBoxBipolarChannels, 0);
}
}
// adapt number of recorded channels in main configuration dialog according to number of actual selected channels
GtkSpinButton* buttonNChannels = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder,"spinbutton_number_of_channels"));
gtk_spin_button_set_range(buttonNChannels, nElectrodeNames, nElectrodeNames);
gtk_spin_button_set_value(buttonNChannels, nElectrodeNames);
// set electrode names as channel names
m_channelNames.clear();
for (size_t i = 0; i < nElectrodeNames; ++i) { m_channelNames[i] = electrodeNames[i]; }
delete [] electrodeNames;
return true;
}
// get channels currenly available for g.Nautilus used
bool CConfigurationgNautilusInterface::getAvailableChannels()
{
char tmp[30];
// get channels currently available on connected device
BOOL availableChannels[GDS_GNAUTILUS_CHANNELS_MAX];
m_gdsResult = GDS_GNAUTILUS_GetAvailableChannels(m_deviceHandle, m_deviceNames, &availableChannels);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
m_availableChannels.resize(GDS_GNAUTILUS_CHANNELS_MAX);
for (uint16_t i = 0; i < GDS_GNAUTILUS_CHANNELS_MAX; ++i)
{
sprintf_s(&tmp[0], 30, "checkbutton_channel_%d", (i + 1));
GtkCheckButton* buttonChannel = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,tmp));
sprintf_s(&tmp[0], 30, "label_channel_%d", (i + 1));
GtkLabel* labelChannel = GTK_LABEL(gtk_builder_get_object(m_builder,tmp));
sprintf_s(&tmp[0], 30, "combobox_channel_%d", (i + 1));
GtkComboBox* comboBoxChannel = GTK_COMBO_BOX(gtk_builder_get_object(m_builder,tmp));
sprintf_s(&tmp[0], 30, "checkbutton_car_channel_%d", (i + 1));
GtkCheckButton* buttonCARChannel = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,tmp));
sprintf_s(&tmp[0], 30, "checkbutton_noise_channel_%d", (i + 1));
GtkCheckButton* buttonNoiseChannel = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,tmp));
if (availableChannels[i] == 1)
{
if (buttonChannel)
{
gtk_widget_set_visible(GTK_WIDGET(buttonChannel), true);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(buttonChannel), true);
gtk_widget_set_visible(GTK_WIDGET(labelChannel), true);
gtk_widget_set_visible(GTK_WIDGET(comboBoxChannel), true);
gtk_widget_set_visible(GTK_WIDGET(buttonCARChannel), true);
gtk_widget_set_visible(GTK_WIDGET(buttonNoiseChannel), true);
}
m_availableChannels[i] = true;
}
else
{
if (buttonChannel)
{
gtk_widget_set_visible(GTK_WIDGET(buttonChannel), false);
gtk_widget_set_visible(GTK_WIDGET(labelChannel), false);
gtk_widget_set_visible(GTK_WIDGET(comboBoxChannel), false);
gtk_widget_set_visible(GTK_WIDGET(buttonCARChannel), false);
gtk_widget_set_visible(GTK_WIDGET(buttonNoiseChannel), false);
}
m_availableChannels[i] = false;
}
}
return true;
}
// if sampling rate changed filters in filter settings dialog have to be updated according to new sampling rate
bool CConfigurationgNautilusInterface::getFiltersForNewSamplingRate()
{
GtkTreeIter iter;
// get current sample rate
GtkComboBox* comboBoxSampling = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_sampling_frequency"));
const char* sSampling = gtk_combo_box_get_active_text(comboBoxSampling);
double sampling = atof(sSampling);
size_t nBandPassFilters, nNotchFilters;
std::stringstream filterDesc;
filterDesc << "no bandpass filter";
// get number of bandpass filters and allocate filter array correspondingly
m_gdsResult = GDS_GNAUTILUS_GetBandpassFilters(m_deviceHandle, m_deviceNames, nullptr, &nBandPassFilters);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
GDS_FILTER_INFO* bandPassFilters = new GDS_FILTER_INFO[nBandPassFilters];
// get bandpass filters
m_gdsResult = GDS_GNAUTILUS_GetBandpassFilters(m_deviceHandle, m_deviceNames, bandPassFilters, &nBandPassFilters);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage;
return false;
}
// get number of notch filters and allocate filter array correspondingly
m_gdsResult = GDS_GNAUTILUS_GetNotchFilters(m_deviceHandle, m_deviceNames, nullptr, &nNotchFilters);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
GDS_FILTER_INFO* notchFilters = new GDS_FILTER_INFO[nNotchFilters];
// get notch filters
m_gdsResult = GDS_GNAUTILUS_GetNotchFilters(m_deviceHandle, m_deviceNames, notchFilters, &nNotchFilters);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// set filters as combobox entries depending on the current sample rate selected
GtkComboBox* comboBoxBandPass = GTK_COMBO_BOX(gtk_builder_get_object(m_builder,"combobox_select_bandpass_filter"));
GtkTreeModel* listStoreBandPass = gtk_combo_box_get_model(comboBoxBandPass);
gtk_list_store_clear(GTK_LIST_STORE(listStoreBandPass));
GtkComboBox* comboBoxNotch = GTK_COMBO_BOX(gtk_builder_get_object(m_builder,"combobox_select_notch_filter"));
GtkTreeModel* listStoreNotch = gtk_combo_box_get_model(comboBoxNotch);
gtk_list_store_clear(GTK_LIST_STORE(listStoreNotch));
if (m_comboBoxBandpassFilterIdx.size() > 0) m_comboBoxBandpassFilterIdx.clear();
if (m_comboBoxNotchFilterIdx.size() > 0) m_comboBoxNotchFilterIdx.clear();
if (m_comboBoxSensitivityValues.size() > 0) m_comboBoxSensitivityValues.clear();
// fill bandpass filter combobox with available filters
gtk_list_store_append(GTK_LIST_STORE(listStoreBandPass), &iter);
gtk_list_store_set(GTK_LIST_STORE(listStoreBandPass), &iter, 0, filterDesc.str().c_str(), -1);
filterDesc.clear();
filterDesc.str("");
m_comboBoxBandpassFilterIdx.push_back(-1);
for (size_t i = 0; i < nBandPassFilters; ++i)
{
if (sampling == bandPassFilters[i].SamplingRate)
{
if (bandPassFilters[i].TypeId == 1) filterDesc << "Butterworth - ";
if (bandPassFilters[i].TypeId == 2) filterDesc << "Chebyshev - ";
filterDesc << bandPassFilters[i].Order << " - [";
filterDesc << bandPassFilters[i].LowerCutoffFrequency << "; ";
filterDesc << bandPassFilters[i].UpperCutoffFrequency << "] - ";
filterDesc << bandPassFilters[i].SamplingRate;
gtk_list_store_append(GTK_LIST_STORE(listStoreBandPass), &iter);
gtk_list_store_set(GTK_LIST_STORE(listStoreBandPass), &iter, 0, filterDesc.str().c_str(), -1);
m_comboBoxBandpassFilterIdx.push_back(i);
}
filterDesc.clear();
filterDesc.str("");
}
gtk_combo_box_set_active(comboBoxBandPass, m_bandpassFilterIdx + 1); // +1 because -1 is for "no filter".
// fill notch filter combobox with available fliters
filterDesc << "no notch filter";
gtk_list_store_append(GTK_LIST_STORE(listStoreNotch), &iter);
gtk_list_store_set(GTK_LIST_STORE(listStoreNotch), &iter, 0, filterDesc.str().c_str(), -1);
filterDesc.clear();
filterDesc.str("");
m_comboBoxNotchFilterIdx.push_back(-1);
for (size_t i = 0; i < nNotchFilters; ++i)
{
if (sampling == notchFilters[i].SamplingRate)
{
if (notchFilters[i].TypeId == 1) filterDesc << "Butterworth - ";
if (notchFilters[i].TypeId == 2) filterDesc << "Chebyshev - ";
filterDesc << notchFilters[i].Order << " - [";
filterDesc << notchFilters[i].LowerCutoffFrequency << "; ";
filterDesc << notchFilters[i].UpperCutoffFrequency << "] - ";
filterDesc << notchFilters[i].SamplingRate;
gtk_list_store_append(GTK_LIST_STORE(listStoreNotch), &iter);
gtk_list_store_set(GTK_LIST_STORE(listStoreNotch), &iter, 0, filterDesc.str().c_str(), -1);
m_comboBoxNotchFilterIdx.push_back(i);
}
filterDesc.clear();
filterDesc.str("");
}
gtk_combo_box_set_active(comboBoxNotch, m_notchFilterIdx + 1); // +1 because -1 is for "no filter".
delete [] bandPassFilters;
delete [] notchFilters;
return true;
}
// If you added more reference attribute, initialize them here
CConfigurationgNautilusInterface::CConfigurationgNautilusInterface(IDriverContext& ctx, const char* gtkBuilderFilename, std::string& deviceSerial,
int& inputSource, uint32_t& networkChannel, int& bandpassFilterIdx, int& notchFilterIdx,
double& sensitivity, bool& digitalInputEnabled, bool& noiseReductionEnabled,
bool& carEnabled, bool& accelerationDataEnabled, bool& counterEnabled,
bool& linkQualityEnabled, bool& batteryLevelEnabled, bool& validationIndicatorEnabled,
std::vector<uint16_t>& selectedChannels, std::vector<int>& bipolarChannels,
std::vector<bool>& cars, std::vector<bool>& noiseReduction)
: CConfigurationBuilder(gtkBuilderFilename), m_driverCtx(ctx), m_deviceSerial(deviceSerial), m_inputSource(inputSource), m_networkChannel(networkChannel),
m_bandpassFilterIdx(bandpassFilterIdx), m_notchFilterIdx(notchFilterIdx), m_sensitivity(sensitivity), m_digitalInputEnabled(digitalInputEnabled),
m_noiseReductionEnabled(noiseReductionEnabled), m_carEnabled(carEnabled), m_accelerationDataEnabled(accelerationDataEnabled),
m_counterEnabled(counterEnabled), m_linkQualityEnabled(linkQualityEnabled), m_batteryLevelEnabled(batteryLevelEnabled),
m_validationIndicatorEnabled(validationIndicatorEnabled), m_selectedChannels(selectedChannels), m_bipolarChannels(bipolarChannels), m_cars(cars),
m_noiseReduction(noiseReduction) { m_connectionOpen = false; }
bool CConfigurationgNautilusInterface::preConfigure()
{
if (! CConfigurationBuilder::preConfigure()) { return false; }
// Connect here all callbacks
// Example:
// g_signal_connect(gtk_builder_get_object(m_builder, "button_calibrate"), "pressed", G_CALLBACK(button_calibrate_pressed_cb), this);
// Insert here the pre-configure code.
// For example, you may want to check if a device is currently connected
// and if more than one are connected. Then you can list in a dedicated combo-box
// the device currently connected so the user can choose which one he wants to acquire from.
bool functionReturn;
// open connection to device if not done yet before reading filters, channel names etc.
if (!m_connectionOpen)
{
// open device handle and get connected device
functionReturn = openDevice();
if (!functionReturn) { return false; } // error logged in openDevice;
}
// get settings for connected hardware
functionReturn = getHardwareSettings();
if (!functionReturn) { return false; }
// error logged in getHardwareSettings
// get available channels
functionReturn = getAvailableChannels();
if (!functionReturn) { return false; } // error logged in getAvailableChannels
functionReturn = getChannelNames();
if (!functionReturn) { return false; } // error logged in getChannelNames
// activate checkboxes in g.Nautilus Configuration dialog
GtkCheckButton* buttonEventChannel = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_event_channel"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(buttonEventChannel), m_digitalInputEnabled);
GtkCheckButton* buttonNoiseReduction = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_noise_reduction"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(buttonNoiseReduction), m_noiseReductionEnabled);
GtkCheckButton* buttonCAR = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_car"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(buttonCAR), m_carEnabled);
// activate/deactivate channel, noise reduction and CAR checkboxes in dialog_select_channels_bipolar_car_noise
// as well as bipolar combo boxes according to available channels
const gboolean noiseReduction = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttonNoiseReduction));
const gboolean car = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttonCAR));
char tmp[45];
for (uint16_t i = 0; i < GDS_GNAUTILUS_CHANNELS_MAX; ++i)
{
sprintf_s(&tmp[0], 45, "checkbutton_channel_%d", (i + 1));
GtkCheckButton* button = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,tmp));
if ((button) && (m_availableChannels[i])) gtk_widget_set_sensitive(GTK_WIDGET(button), true);
else if (button) gtk_widget_set_sensitive(GTK_WIDGET(button), false);
sprintf_s(&tmp[0], 45, "checkbutton_noise_channel_%d", (i + 1));
button = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,tmp));
if ((button) && (m_availableChannels[i])) gtk_widget_set_sensitive(GTK_WIDGET(button), noiseReduction);
else if (button) gtk_widget_set_sensitive(GTK_WIDGET(button), false);
sprintf_s(&tmp[0], 45, "checkbutton_car_channel_%d", (i + 1));
button = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,tmp));
if ((button) && (m_availableChannels[i])) gtk_widget_set_sensitive(GTK_WIDGET(button), car);
else if (button) gtk_widget_set_sensitive(GTK_WIDGET(button), false);
sprintf_s(&tmp[0], 45, "combobox_channel_%d", (i + 1));
GtkComboBox* comboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_builder,tmp));
if ((comboBox) && (m_availableChannels[i])) gtk_widget_set_sensitive(GTK_WIDGET(comboBox), true);
else if (comboBox) gtk_widget_set_sensitive(GTK_WIDGET(comboBox), false);
}
GtkCheckButton* accelerationData = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_acceleration_data"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(accelerationData), m_accelerationDataEnabled);
GtkCheckButton* counter = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_counter"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(counter), m_counterEnabled);
GtkCheckButton* linkQuality = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_link_quality"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(linkQuality), m_linkQualityEnabled);
GtkCheckButton* batteryLevel = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_battery_level"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(batteryLevel), m_batteryLevelEnabled);
GtkCheckButton* validationIndicator = GTK_CHECK_BUTTON(
gtk_builder_get_object(m_builder, "checkbutton_validation_indicator"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(validationIndicator), m_validationIndicatorEnabled);
g_signal_connect(gtk_builder_get_object(m_builder,"button_select_channels_bipolar_car_noise"), "pressed", G_CALLBACK(ChannelSettingsCB), this);
g_signal_connect(gtk_builder_get_object(m_builder,"button_select_sensitivity_filters"), "pressed", G_CALLBACK(SensitivityFiltersCB), this);
g_signal_connect(gtk_builder_get_object(m_builder,"button_select_sensitivity_filters_apply"), "pressed", G_CALLBACK(SensitivityFiltersApplyCB), this);
g_signal_connect(gtk_builder_get_object(m_builder,"button_channel_apply"), "pressed", G_CALLBACK(ChannelSettingsApplyCB), this);
// set device serial in device serial text entry in g.Nautilus Configuration dialog
GtkEntry* deviceSerial = GTK_ENTRY(gtk_builder_get_object(m_builder, "entry_device_serial"));
gtk_entry_set_text(deviceSerial, m_deviceSerial.c_str());
gtk_entry_set_editable(deviceSerial, false);
// deactivate buttons if no device is detected
if (gtk_entry_get_text_length(deviceSerial) < 13)
{
GtkWidget* button = GTK_WIDGET(gtk_builder_get_object(m_builder,"button_select_channels_bipolar_car_noise"));
gtk_widget_set_sensitive(button, false);
button = GTK_WIDGET(gtk_builder_get_object(m_builder,"button_select_sensitivity_filters"));
gtk_widget_set_sensitive(button, false);
}
// catch event when sample rate is changed to adjust available filters
g_signal_connect(gtk_builder_get_object(m_builder, "combobox_sampling_frequency"), "changed", G_CALLBACK(sample_rate_changed_cb), this);
// catch events when car and noise reduction checkboxes are toggled in main config dialog to enable/disable corresponding checkboxes in related dialog
g_signal_connect(gtk_builder_get_object(m_builder, "checkbutton_noise_reduction"), "toggled", G_CALLBACK(noise_reduction_changed_cb), this);
g_signal_connect(gtk_builder_get_object(m_builder, "checkbutton_car"), "toggled", G_CALLBACK(car_changed_cb), this);
return true;
}
bool CConfigurationgNautilusInterface::postConfigure()
{
if (m_applyConfig)
{
// If the user pressed the "apply" button, you need to save the changes made in the configuration.
// For example, you can save the connection ID of the selected device:
// m_connectionID = <value-from-gtk-widget>
GtkCheckButton* checkButton;
uint16_t i;
gboolean buttonValue = false;
gdouble nChannels = 0;
// get serial number from configuration dialog
GtkEntry* entrySerial = GTK_ENTRY(gtk_builder_get_object(m_builder, "entry_device_serial"));
m_deviceSerial = gtk_entry_get_text(entrySerial);
// get selected input source from dialog
GtkComboBox* inputSources = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_input_source"));
size_t idx = gtk_combo_box_get_active(inputSources);
m_inputSource = m_comboBoxInputSources[idx];
// get selected channels
char tmp[45];
m_selectedChannels.clear();
for (i = 0; i < GDS_GNAUTILUS_CHANNELS_MAX; ++i)
{
sprintf_s(&tmp[0], 45, "checkbutton_channel_%d", (i + 1));
checkButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,tmp));
if (checkButton)
{
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkButton)))
{
m_selectedChannels.push_back(i + 1);
nChannels += 1;
}
else
{
m_selectedChannels.push_back(0);
m_channelNames.erase(i);
}
}
}
// get bipolar channels
m_bipolarChannels.clear();
for (i = 0; i < GDS_GNAUTILUS_CHANNELS_MAX; ++i)
{
sprintf_s(&tmp[0], 45, "combobox_channel_%d", (i + 1));
GtkComboBox* bipolarChannels = GTK_COMBO_BOX(gtk_builder_get_object(m_builder,tmp));
if (bipolarChannels)
{
// bipolar is 0 based for the channels, -1 indicates no bipolar derivation
m_bipolarChannels.push_back(gtk_combo_box_get_active(bipolarChannels) - 1);
}
}
// set bandpass and notch filter indices to correponding variables
GtkComboBox* bandpassFilters = GTK_COMBO_BOX(gtk_builder_get_object(m_builder,"combobox_select_bandpass_filter"));
idx = gtk_combo_box_get_active(bandpassFilters);
m_bandpassFilterIdx = m_comboBoxBandpassFilterIdx.at(idx);
GtkComboBox* notchFilters = GTK_COMBO_BOX(gtk_builder_get_object(m_builder,"combobox_select_notch_filter"));
idx = gtk_combo_box_get_active(notchFilters);
m_notchFilterIdx = m_comboBoxNotchFilterIdx.at(idx);
// sensitivity
GtkComboBox* sensitivities = GTK_COMBO_BOX(gtk_builder_get_object(m_builder,"combobox_select_sensitivity"));
idx = gtk_combo_box_get_active(sensitivities);
m_sensitivity = m_comboBoxSensitivityValues.at(idx);
// digital inputs
checkButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,"checkbutton_event_channel"));
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkButton)) == 1) { m_digitalInputEnabled = true; }
else { m_digitalInputEnabled = false; }
// noise reduction
checkButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,"checkbutton_noise_reduction"));
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkButton)) == 1) { m_noiseReductionEnabled = true; }
else { m_noiseReductionEnabled = false; }
// if noise reduction is active, check for which channels noise reduction is enabled
m_noiseReduction.clear();
for (i = 0; i < GDS_GNAUTILUS_CHANNELS_MAX; ++i)
{
sprintf_s(&tmp[0], 45, "checkbutton_noise_channel_%d", (i + 1));
checkButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,tmp));
if (checkButton)
{
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkButton)) == 1) { m_noiseReduction.push_back(true); }
else { m_noiseReduction.push_back(false); }
}
}
// CAR
checkButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,"checkbutton_car"));
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkButton)) == 1) m_carEnabled = true;
else m_carEnabled = false;
// if CAR is active, check for which channels CAR is enabled
m_cars.clear();
for (i = 0; i < GDS_GNAUTILUS_CHANNELS_MAX; ++i)
{
sprintf_s(&tmp[0], 45, "checkbutton_car_channel_%d", (i + 1));
checkButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,tmp));
if (checkButton)
{
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkButton)) == 1) m_cars.push_back(true);
else m_cars.push_back(false);
}
}
// acceleration data
checkButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,"checkbutton_acceleration_data"));
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkButton)) == 1) { m_accelerationDataEnabled = true; }
else { m_accelerationDataEnabled = false; }
// counter
checkButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,"checkbutton_counter"));
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkButton)) == 1) { m_counterEnabled = true; }
else { m_counterEnabled = false; }
// link quality
checkButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,"checkbutton_link_quality"));
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkButton)) == 1) { m_linkQualityEnabled = true; }
else { m_linkQualityEnabled = false; }
// battery level
checkButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,"checkbutton_battery_level"));
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkButton)) == 1) { m_batteryLevelEnabled = true; }
else { m_batteryLevelEnabled = false; }
// validation indicator
checkButton = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder,"checkbutton_validation_indicator"));
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(checkButton)) == 1) { m_validationIndicatorEnabled = true; }
else { m_validationIndicatorEnabled = false; }
// network channel
GtkComboBox* comboBoxNetworkChannel = GTK_COMBO_BOX(gtk_builder_get_object(m_builder,"combobox_network_channel"));
const char* networkChannel = gtk_combo_box_get_active_text(comboBoxNetworkChannel);
m_networkChannel = uint32_t(atoi(networkChannel));
// set number of channels in main configuration dialog (spinbutton still remains disabled, user cannot change value there)
GtkSpinButton* buttonChannels = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_number_of_channels"));
gtk_spin_button_set_value(buttonChannels, nChannels);
gtk_spin_button_set_range(buttonChannels, nChannels, nChannels);
}
if (m_connectionOpen)
{
// close connection handle
const bool functionReturn = closeDevice();
if (!functionReturn)
{
// error logged in closeDevice
return false;
}
}
if (! CConfigurationBuilder::postConfigure()
) { return false; } // normal header is filled (Subject ID, Age, Gender, channels, sampling frequency), ressources are realesed
return true;
}
bool CConfigurationgNautilusInterface::openDevice()
{
GDS_ENDPOINT hostEp, localEp;
GDS_DEVICE_CONNECTION_INFO* connectedDevicesInfo;
size_t nDevices;
std::string deviceSerial;
BOOL openExclusively = true;
BOOL isCreator;
m_deviceNames = new char[1][DEVICE_NAME_LENGTH_MAX];
uint8_t byteIP[4];
byteIP[0] = 1;
byteIP[1] = 0;
byteIP[2] = 0;
byteIP[3] = 127;
char tmpIP[16];
_snprintf_s(tmpIP, IP_ADDRESS_LENGTH_MAX, "%d.%d.%d.%d", byteIP[3], byteIP[2], byteIP[1], byteIP[0]);
for (size_t i = 0; i < IP_ADDRESS_LENGTH_MAX; ++i)
{
hostEp.IpAddress[i] = tmpIP[i];
localEp.IpAddress[i] = tmpIP[i];
}
hostEp.Port = 50223;
localEp.Port = 50224;
// get connected device
m_gdsResult = GDS_GetConnectedDevices(hostEp, localEp, &connectedDevicesInfo, &nDevices);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
for (size_t i = 0; i < nDevices; ++i)
{
// if devices are in use they cannot be used for a new acquisition
if ((connectedDevicesInfo[i].InUse) && (i < nDevices)) continue;
// only one device can be used for data acquisition, as g.Nautilus cannot be synchronized
if ((connectedDevicesInfo[i].InUse) && (connectedDevicesInfo[i].ConnectedDevicesLength > 1)) continue;
GDS_DEVICE_INFO* deviceInfo = connectedDevicesInfo[i].ConnectedDevices;
if (deviceInfo[0].DeviceType == GDS_DEVICE_TYPE_GNAUTILUS)
{
deviceSerial = deviceInfo[0].Name;
nDevices = 1;
break;
}
}
if (nDevices == 0)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "No g.Nautilus connected\n";
return false;
}
strncpy_s(m_deviceNames[0], deviceSerial.c_str(), DEVICE_NAME_LENGTH_MAX);
m_deviceSerial = deviceSerial;
// connect to device
m_gdsResult = GDS_Connect(hostEp, localEp, m_deviceNames, nDevices, openExclusively, &m_deviceHandle, &isCreator);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// free connected devices list allocated by GDS_GetConnectedDevices
m_gdsResult = GDS_FreeConnectedDevicesList(&connectedDevicesInfo, nDevices);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
m_connectionOpen = true;
return true;
}
bool CConfigurationgNautilusInterface::closeDevice()
{
// disconnect device
m_gdsResult = GDS_Disconnect(&m_deviceHandle);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
m_connectionOpen = false;
delete [] m_deviceNames;
return true;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGNEEDaccessAPI
@@ -0,0 +1,102 @@
#pragma once
#if defined(TARGET_HAS_ThirdPartyGNEEDaccessAPI)
#include "../ovasCConfigurationBuilder.h"
#include "ovasIDriver.h"
#include <gtk/gtk.h>
#include <GDSClientAPI.h>
#include <GDSClientAPI_gNautilus.h>
#include <sstream>
#include <algorithm>
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CConfigurationgNautilusInterface
* \author g.tec medical engineering GmbH (g.tec medical engineering GmbH)
* \date Wed Aug 12 16:37:18 2015
* \brief The CConfigurationgNautilusInterface handles the configuration dialog specific to the g.NEEDaccess device.
*
* TODO: details
*
* \sa CDrivergNautilusInterface
*/
class CConfigurationgNautilusInterface final : public CConfigurationBuilder
{
public:
// you may have to add to your constructor some reference parameters
// for example, a connection ID:
//CConfigurationgNautilusInterface(IDriverContext& ctx, const char* gtkBuilderFilename, uint32_t& rConnectionId);
CConfigurationgNautilusInterface(IDriverContext& ctx, const char* gtkBuilderFilename, std::string& deviceSerial, int& inputSource, uint32_t& networkChannel,
int& bandpassFilterIdx, int& notchFilterIdx, double& sensitivity, bool& digitalInputEnabled, bool& noiseReductionEnabled,
bool& carEnabled, bool& accelerationDataEnabled, bool& counterEnabled, bool& linkQualityEnabled, bool& batteryLevelEnabled,
bool& validationIndicatorEnabled, std::vector<uint16_t>& selectedChannels, std::vector<int>& bipolarChannels,
std::vector<bool>& cars, std::vector<bool>& noiseReduction);
bool preConfigure() override;
bool postConfigure() override;
//button callback functions
void buttonChannelSettingsPressedCB();
void buttonChannelSettingsApplyPressedCB();
void buttonSensitivityFiltersPressedCB();
void buttonSensitivityFiltersApplyPressedCB();
bool getHardwareSettings();
bool getChannelNames();
bool getAvailableChannels();
bool getFiltersForNewSamplingRate();
void comboboxSampleRateChangedCB();
void checkbuttonNoiseReductionChangedCB();
void checkbuttonCARChangedCB();
protected:
IDriverContext& m_driverCtx;
std::string& m_deviceSerial;
int& m_inputSource;
uint32_t& m_networkChannel;
int& m_bandpassFilterIdx;
int& m_notchFilterIdx;
double& m_sensitivity;
bool& m_digitalInputEnabled;
bool& m_noiseReductionEnabled;
bool& m_carEnabled;
bool& m_accelerationDataEnabled;
bool& m_counterEnabled;
bool& m_linkQualityEnabled;
bool& m_batteryLevelEnabled;
bool& m_validationIndicatorEnabled;
std::vector<uint16_t>& m_selectedChannels;
std::vector<int>& m_bipolarChannels;
std::vector<bool>& m_cars;
std::vector<bool>& m_noiseReduction;
std::vector<int> m_comboBoxBandpassFilterIdx;
std::vector<int> m_comboBoxNotchFilterIdx;
std::vector<double> m_comboBoxSensitivityValues;
std::vector<int> m_comboBoxInputSources;
std::vector<size_t> m_comboBoxNetworkChannels;
private:
/*
* Insert here all specific attributes, such as a connection ID.
* use references to directly modify the corresponding attribute of the driver
* Example:
*/
// uint32_t& m_connectionID;
bool openDevice();
bool closeDevice();
GDS_HANDLE m_deviceHandle;
GDS_RESULT m_gdsResult;
std::vector<bool> m_availableChannels;
char (*m_deviceNames)[DEVICE_NAME_LENGTH_MAX];
bool m_connectionOpen = false;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGNEEDaccessAPI
@@ -0,0 +1,462 @@
#if defined(TARGET_HAS_ThirdPartyGNEEDaccessAPI)
#include "ovasCDrivergNautilusInterface.h"
#include "ovasCConfigurationgNautilusInterface.h"
#include <toolkit/ovtk_all.h>
namespace OpenViBE {
namespace AcquisitionServer {
// global functions, event handles and variables
void OnDataReadyEventHandler(GDS_HANDLE handle, void* data);
HANDLE dataReadyEventHandle;
CDrivergNautilusInterface::CDrivergNautilusInterface(IDriverContext& ctx)
: IDriver(ctx), m_settings("AcquisitionServer_Driver_gNautilusInterface", m_driverCtx.getConfigurationManager()), m_Device(NULL)
{
m_header.setSamplingFrequency(250);
m_header.setChannelCount(32);
// The following class allows saving and loading driver settings from the acquisition server .conf file
m_settings.add("Header", &m_header);
// To save your custom driver settings, register each variable to the SettingsHelper
//m_settings.add("SettingName", &variable);
m_settings.add("DeviceIndex", &m_deviceIdx);
m_settings.add("InputSource", &m_inputSource);
m_settings.add("NetworkChannel", &m_networkChannel);
m_settings.add("Sensitivity", &m_sensitivity);
m_settings.add("NotchFilterIndex", &m_notchFilterIdx);
m_settings.add("BandPassFilterIndex", &m_bandPassFilterIdx);
m_settings.add("DigitalInputEnabled", &m_digitalInputEnabled);
m_settings.add("NoiseReductionEnabled", &m_noiseReductionEnabled);
m_settings.add("CAREnabled", &m_carEnabled);
m_settings.add("AccelerationEnabled", &m_accelerationDataEnabled);
m_settings.add("CounterEnabled", &m_counterEnabled);
m_settings.add("LinqQualityEnabled", &m_linkQualityEnabled);
m_settings.add("BatteryLevelEnabled", &m_batteryLevelEnabled);
m_settings.add("ValidationIndicatorEnabled", &m_validationIndicatorEnabled);
m_settings.load();
}
//___________________________________________________________________//
// //
bool CDrivergNautilusInterface::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
{
if (m_driverCtx.isConnected() || !m_header.isChannelCountSet() || !m_header.isSamplingFrequencySet()) { return false; }
if (nSamplePerSentBlock > 250)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Sample count per sent block cannot be higher than 250 samples for g.Nautilus\n";
return false;
}
// initialize basic GDS functions before the first GDS function itself is called
GDS_Initialize();
// get number of channels actually acquired
m_nAcquiredChannel = 0;
for (size_t i = 0; i < GDS_GNAUTILUS_CHANNELS_MAX; ++i)
{
if (find(m_selectedChannels.begin(), m_selectedChannels.end(), (i + 1)) != m_selectedChannels.end())
{
m_header.setChannelUnits(m_nAcquiredChannel, OVTK_UNIT_Volts, OVTK_FACTOR_Micro);
m_nAcquiredChannel += 1;
}
}
m_header.setChannelCount(m_nAcquiredChannel);
// g.Nautilus provides some extra channel, add them to the channels count
// and provide corresponding names
uint32_t nAcquiredChannel = m_nAcquiredChannel;
if (m_accelerationDataEnabled)
{
m_header.setChannelCount(nAcquiredChannel + 3);
m_header.setChannelName(nAcquiredChannel, "CH_Accel_x");
m_header.setChannelName(nAcquiredChannel + 1, "CH_Accel_y");
m_header.setChannelName(nAcquiredChannel + 2, "CH_Accel_z");
m_header.setChannelUnits(nAcquiredChannel, OVTK_UNIT_Meter_Per_Second_Squared, OVTK_FACTOR_Base);
m_header.setChannelUnits(nAcquiredChannel + 1, OVTK_UNIT_Meter_Per_Second_Squared, OVTK_FACTOR_Base);
m_header.setChannelUnits(nAcquiredChannel + 2, OVTK_UNIT_Meter_Per_Second_Squared, OVTK_FACTOR_Base);
nAcquiredChannel = m_header.getChannelCount();
}
if (m_counterEnabled)
{
m_header.setChannelCount(nAcquiredChannel + 1);
m_header.setChannelName(nAcquiredChannel, "CH_Counter");
m_header.setChannelUnits(nAcquiredChannel,OVTK_UNIT_Dimensionless,OVTK_FACTOR_Base);
nAcquiredChannel = m_header.getChannelCount();
}
if (m_linkQualityEnabled)
{
m_header.setChannelCount(nAcquiredChannel + 1);
m_header.setChannelName(nAcquiredChannel, "CH_LQ");
m_header.setChannelUnits(nAcquiredChannel,OVTK_UNIT_10_2_Percent,OVTK_FACTOR_Base);
nAcquiredChannel = m_header.getChannelCount();
}
if (m_batteryLevelEnabled)
{
m_header.setChannelCount(nAcquiredChannel + 1);
m_header.setChannelName(nAcquiredChannel, "CH_Battery");
m_header.setChannelUnits(nAcquiredChannel,OVTK_UNIT_10_2_Percent,OVTK_FACTOR_Base);
nAcquiredChannel = m_header.getChannelCount();
}
if (m_digitalInputEnabled)
{
m_header.setChannelCount(nAcquiredChannel + 1);
m_header.setChannelName(nAcquiredChannel, "CH_Event");
m_header.setChannelUnits(nAcquiredChannel,OVTK_UNIT_Dimensionless,OVTK_FACTOR_Base);
nAcquiredChannel = m_header.getChannelCount();
}
if (m_validationIndicatorEnabled)
{
m_header.setChannelCount(nAcquiredChannel + 1);
m_header.setChannelName(nAcquiredChannel, "CH_Valid");
m_header.setChannelUnits(nAcquiredChannel,OVTK_UNIT_Dimensionless,OVTK_FACTOR_Base);
nAcquiredChannel = m_header.getChannelCount();
}
// initialize connection and open connection handle and get serial of connected g.Nautilus
GDS_DEVICE_CONNECTION_INFO* devicesInfo;
size_t nDevices = 0;
GDS_ENDPOINT hostEp;
GDS_ENDPOINT localEp;
uint8_t byteIP[4];
char tempIP[16];
byteIP[0] = 1;
byteIP[1] = 0;
byteIP[2] = 0;
byteIP[3] = 127;
_snprintf_s(tempIP, IP_ADDRESS_LENGTH_MAX, "%d.%d.%d.%d", byteIP[3], byteIP[2], byteIP[1], byteIP[0]);
for (size_t i = 0; i < IP_ADDRESS_LENGTH_MAX; ++i)
{
hostEp.IpAddress[i] = tempIP[i];
localEp.IpAddress[i] = tempIP[i];
}
hostEp.Port = 50223;
localEp.Port = 50224;
bool found = false;
m_gdsResult = GDS_GetConnectedDevices(hostEp, localEp, &devicesInfo, &nDevices);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
for (size_t i = 0; i < nDevices; ++i)
{
// if devices are in use they cannot be used for a new acquisition
if ((devicesInfo[i].InUse) && (i < nDevices)) { continue; }
// only one device can be used for data acquisition, as g.Nautilus cannot be synchronized
if ((devicesInfo[i].InUse) && (devicesInfo[i].ConnectedDevicesLength > 1)) { continue; }
GDS_DEVICE_INFO* info = devicesInfo[i].ConnectedDevices;
if (info[0].DeviceType == GDS_DEVICE_TYPE_GNAUTILUS)
{
// check if device used for configuration is still available
if (!strcmp(info[0].Name, m_deviceSerial.c_str()))
{
m_nDevice = 1;
found = true;
break;
}
}
}
if (found == false)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "No g.Nautilus device found\n";
return false;
}
char (*names)[DEVICE_NAME_LENGTH_MAX] = new char[1][DEVICE_NAME_LENGTH_MAX];
bool openExclusively = true;
BOOL isCreator;
strncpy_s(names[0], m_deviceSerial.c_str(), DEVICE_NAME_LENGTH_MAX);
// connect to device
m_gdsResult = GDS_Connect(hostEp, localEp, names, m_nDevice, openExclusively, &m_Device, &isCreator);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Connected to device : " << m_deviceSerial << "\n";
m_deviceCfg.SamplingRate = m_header.getSamplingFrequency();
m_deviceCfg.NumberOfScans = nSamplePerSentBlock;
m_deviceCfg.NetworkChannel = m_networkChannel;
m_deviceCfg.AccelerationData = m_accelerationDataEnabled;
m_deviceCfg.BatteryLevel = m_batteryLevelEnabled;
m_deviceCfg.CAR = m_carEnabled;
m_deviceCfg.Counter = m_counterEnabled;
m_deviceCfg.DigitalIOs = m_digitalInputEnabled;
m_deviceCfg.LinkQualityInformation = m_linkQualityEnabled;
m_deviceCfg.NoiseReduction = m_noiseReductionEnabled;
m_deviceCfg.Slave = 0;
m_deviceCfg.ValidationIndicator = m_validationIndicatorEnabled;
m_deviceCfg.InputSignal = GDS_GNAUTILUS_INPUT_SIGNAL(m_inputSource);
for (size_t i = 0; i < GDS_GNAUTILUS_CHANNELS_MAX; ++i)
{
if (find(m_selectedChannels.begin(), m_selectedChannels.end(), (i + 1)) != m_selectedChannels.end()) { m_deviceCfg.Channels[i].Enabled = true; }
else { m_deviceCfg.Channels[i].Enabled = false; }
m_deviceCfg.Channels[i].Sensitivity = m_sensitivity;
if (i < m_cars.size()) { m_deviceCfg.Channels[i].UsedForCar = m_cars[i]; }
else { m_deviceCfg.Channels[i].UsedForCar = false; }
if (i < m_noiseReduction.size()) { m_deviceCfg.Channels[i].UsedForNoiseReduction = m_noiseReduction[i]; }
else { m_deviceCfg.Channels[i].UsedForNoiseReduction = false; }
m_deviceCfg.Channels[i].BandpassFilterIndex = m_bandPassFilterIdx;
m_deviceCfg.Channels[i].NotchFilterIndex = m_notchFilterIdx;
if (i < m_bipolarChannels.size()) { m_deviceCfg.Channels[i].BipolarChannel = m_bipolarChannels[i]; }
else { m_deviceCfg.Channels[i].BipolarChannel = false; }
}
// Free memory allocated for list of connected devices by GDS_GetConnectedDevices
m_gdsResult = GDS_FreeConnectedDevicesList(&devicesInfo, nDevices);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// Builds up a buffer to store acquired samples. This buffer
// will be sent to the acquisition server later...
m_sample = new float[nAcquiredChannel * m_deviceCfg.NumberOfScans];
if (!m_sample)
{
delete [] m_sample;
m_sample = nullptr;
return false;
}
// set up data buffer for gds getdata function
m_Buffer = new float[nAcquiredChannel * m_deviceCfg.NumberOfScans];
if (!m_Buffer)
{
delete [] m_Buffer;
m_Buffer = nullptr;
return false;
}
m_bufferSize = nAcquiredChannel * m_deviceCfg.NumberOfScans;
// Saves parameters
m_callback = &callback;
m_nSamplePerSentBlock = nSamplePerSentBlock;
return true;
}
bool CDrivergNautilusInterface::start()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// if no device was found return
if (m_nDevice != 1) { return false; }
GDS_CONFIGURATION_BASE* config = new GDS_CONFIGURATION_BASE[m_nDevice];
config[0].Configuration = &m_deviceCfg;
config[0].DeviceInfo.DeviceType = GDS_DEVICE_TYPE_GNAUTILUS;
strcpy_s(config[0].DeviceInfo.Name, m_deviceSerial.c_str());
size_t nScan = 0;
size_t channelsPerDevice = 0;
size_t bufferSizeInSamples = 0;
// set device configuration
m_gdsResult = GDS_SetConfiguration(m_Device, config, m_nDevice);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// initialize data ready event and set data ready event handle
dataReadyEventHandle = nullptr;
dataReadyEventHandle = CreateEvent(nullptr, false, false, nullptr);
m_gdsResult = GDS_SetDataReadyCallback(m_Device, (GDS_Callback)OnDataReadyEventHandler, m_deviceCfg.NumberOfScans,NULL);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// set number of scans getdata function will return
m_availableScans = m_deviceCfg.NumberOfScans;
// ...
// request hardware to start
// sending data
// ...
// start acquisition
m_gdsResult = GDS_StartAcquisition(m_Device);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// start data stream from server
m_gdsResult = GDS_StartStreaming(m_Device);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
m_gdsResult = GDS_GetDataInfo(m_Device, &nScan, nullptr, &channelsPerDevice, &bufferSizeInSamples);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
return true;
}
bool CDrivergNautilusInterface::loop()
{
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return true; }
const CStimulationSet stimSet;
if (m_driverCtx.isStarted())
{
const DWORD ret = WaitForSingleObject(dataReadyEventHandle, 5000);
if (ret == WAIT_TIMEOUT)
{
// if data ready event is not triggered in 5000ms add a timeout handler
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "No data received in 5 seconds\n";
return false;
}
// when data is ready call get data function with amount of scans to return
m_gdsResult = GDS_GetData(m_Device, &m_availableScans, m_Buffer, m_bufferSize);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// put data from receiving buffer to application buffer
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
{
for (size_t j = 0; j < m_nSamplePerSentBlock; ++j) { m_sample[i * m_nSamplePerSentBlock + j] = m_Buffer[j * (m_header.getChannelCount()) + i]; }
}
}
// ...
// receive samples from hardware
// put them the correct way in the sample array
// whether the buffer is full, send it to the acquisition server
//...
m_callback->setSamples(m_sample);
// When your sample buffer is fully loaded,
// it is advised to ask the acquisition server
// to correct any drift in the acquisition automatically.
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
// ...
// receive events from hardware
// and put them the correct way in a CStimulationSet object
//...
m_callback->setStimulationSet(stimSet);
return true;
}
bool CDrivergNautilusInterface::stop()
{
if (!m_driverCtx.isConnected()) return false;
if (!m_driverCtx.isStarted()) return false;
// ...
// request the hardware to stop
// sending data
// ...
// stop streaming
m_gdsResult = GDS_StopStreaming(m_Device);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
// stop data acquisiton
m_gdsResult = GDS_StopAcquisition(m_Device);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
return true;
}
bool CDrivergNautilusInterface::uninitialize()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// ...
// uninitialize hardware here
// ...
m_gdsResult = GDS_Disconnect(&m_Device);
if (m_gdsResult.ErrorCode != GDS_ERROR_SUCCESS)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << m_gdsResult.ErrorMessage << "\n";
return false;
}
delete [] m_sample;
m_sample = nullptr;
m_callback = nullptr;
// uninitialize basic GDS functions after last GDS function is called
GDS_Uninitialize();
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Disconnected from device : " << m_deviceSerial << "\n";
return true;
}
//___________________________________________________________________//
// //
bool CDrivergNautilusInterface::isConfigurable()
{
return true; // change to false if your device is not configurable
}
bool CDrivergNautilusInterface::configure()
{
// Change this line if you need to specify some references to your driver attribute that need configuration, e.g. the connection ID.
CConfigurationgNautilusInterface config(m_driverCtx, Directories::getDataDir() + "/applications/acquisition-server/interface-gNautilusInterface.ui",
m_deviceSerial, m_inputSource, m_networkChannel, m_bandPassFilterIdx, m_notchFilterIdx, m_sensitivity,
m_digitalInputEnabled, m_noiseReductionEnabled, m_carEnabled, m_accelerationDataEnabled, m_counterEnabled,
m_linkQualityEnabled, m_batteryLevelEnabled, m_validationIndicatorEnabled, m_selectedChannels, m_bipolarChannels,
m_cars, m_noiseReduction);
if (!config.configure(m_header)) { return false; }
m_settings.save();
return true;
}
void OnDataReadyEventHandler(GDS_HANDLE handle, void* data)
{
// signals data is ready on GDS acquisition buffer
if (!SetEvent(dataReadyEventHandle))
{
// insert error handling if necessary
}
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGNEEDaccessAPI
@@ -0,0 +1,98 @@
#pragma once
#if defined(TARGET_HAS_ThirdPartyGNEEDaccessAPI)
#include "ovasIDriver.h"
#include "../ovasCHeader.h"
#include <openvibe/ov_all.h>
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
#include <GDSClientAPI.h>
#include <GDSClientAPI_gNautilus.h>
#include <vector>
#include <string>
#include <algorithm>
#include <Windows.h>
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CDrivergNautilusInterface
* \author g.tec medical engineering GmbH (g.tec medical engineering GmbH)
* \date Wed Aug 12 16:37:18 2015
* \brief The CDrivergNautilusInterface allows the acquisition server to acquire data from a g.NEEDaccess device.
*
* TODO: details
*
* \sa CConfigurationgNautilusInterface
*/
class CDrivergNautilusInterface final : public IDriver
{
public:
explicit CDrivergNautilusInterface(IDriverContext& ctx);
~CDrivergNautilusInterface() override { }
const char* getName() override { return "g.tec g.Nautilus using g.NEEDaccess"; }
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
bool uninitialize() override;
bool start() override;
bool stop() override;
bool loop() override;
bool isConfigurable() override;
bool configure() override;
const IHeader* getHeader() override { return &m_header; }
bool isFlagSet(const EDriverFlag flag) const override { return flag == EDriverFlag::IsUnstable; }
protected:
SettingsHelper m_settings;
IDriverCallback* m_callback = nullptr;
// Replace this generic Header with any specific header you might have written
CHeader m_header;
uint32_t m_nSamplePerSentBlock = 4;
float* m_sample = nullptr;
uint32_t m_deviceIdx = uint32_t(-1);
uint32_t m_actualDeviceIdx = 0;
uint32_t m_bufferSize = 0;
size_t m_availableScans = 0;
float* m_Buffer = nullptr;
int m_notchFilterIdx = -1;
int m_bandPassFilterIdx = -1;
double m_sensitivity = 0;
int m_inputSource = 0;
uint32_t m_networkChannel = 11;
bool m_digitalInputEnabled = true;
bool m_noiseReductionEnabled = false;
bool m_carEnabled = false;
bool m_accelerationDataEnabled = true;
bool m_counterEnabled = true;
bool m_linkQualityEnabled = true;
bool m_batteryLevelEnabled = true;
bool m_validationIndicatorEnabled = true;
std::vector<uint16_t> m_selectedChannels;
std::vector<int> m_bipolarChannels;
std::vector<bool> m_noiseReduction;
std::vector<bool> m_cars;
uint32_t m_nAcquiredChannel = 32;
GDS_HANDLE m_Device;
GDS_RESULT m_gdsResult;
GDS_GNAUTILUS_CONFIGURATION m_deviceCfg;
std::string m_deviceSerial;
uint32_t m_nDevice = 0;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGNEEDaccessAPI
@@ -0,0 +1,812 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkAdjustment" id="adjustment1">
<property name="value">4</property>
<property name="lower">1</property>
<property name="upper">16</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment3">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkListStore" id="model1">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">32</col>
</row>
<row>
<col id="0" translatable="yes">64</col>
</row>
<row>
<col id="0" translatable="yes">128</col>
</row>
<row>
<col id="0" translatable="yes">256</col>
</row>
<row>
<col id="0" translatable="yes">512</col>
</row>
<row>
<col id="0" translatable="yes">600</col>
</row>
<row>
<col id="0" translatable="yes">1200</col>
</row>
<row>
<col id="0" translatable="yes">2400</col>
</row>
<row>
<col id="0" translatable="yes">4800</col>
</row>
<row>
<col id="0" translatable="yes">9600</col>
</row>
<row>
<col id="0" translatable="yes">19200</col>
</row>
<row>
<col id="0" translatable="yes">38400</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model2">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">unspecified</col>
</row>
<row>
<col id="0" translatable="yes">female</col>
</row>
<row>
<col id="0" translatable="yes">male</col>
</row>
<row>
<col id="0" translatable="yes">unknown</col>
</row>
</data>
</object>
<object class="GtkDialog" id="openvibe-acquisition-server-settings">
<property name="border_width">5</property>
<property name="title" translatable="yes">Device configuration</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel" id="label_title">
<property name="visible">True</property>
<property name="label" translatable="yes">g.Tec gUSBamp</property>
<property name="justify">center</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox">
<property name="visible">True</property>
<property name="border_width">8</property>
<property name="spacing">8</property>
<child>
<object class="GtkHBox" id="hbox">
<property name="visible">True</property>
<property name="spacing">8</property>
<child>
<object class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="n_rows">8</property>
<property name="n_columns">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkSpinButton" id="spinbutton_number_of_channels">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="adjustment">adjustment1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_number_of_channels">
<property name="visible">True</property>
<property name="label" translatable="yes">Number of channels :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_device">
<property name="visible">True</property>
<property name="label" translatable="yes">Device :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_sampling_frequency">
<property name="visible">True</property>
<property name="model">model1</property>
<child>
<object class="GtkCellRendererText" id="renderer1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_sampling_frequency">
<property name="visible">True</property>
<property name="label" translatable="yes">Sampling frequency :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_gender">
<property name="visible">True</property>
<property name="model">model2</property>
<child>
<object class="GtkCellRendererText" id="renderer2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_device">
<property name="visible">True</property>
<property name="model">model3</property>
<child>
<object class="GtkCellRendererText" id="renderer3"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_identifier">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment2</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_age">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment3</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_identifier">
<property name="visible">True</property>
<property name="label" translatable="yes">Identifier :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label_age">
<property name="visible">True</property>
<property name="label" translatable="yes">Age :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_gender">
<property name="visible">True</property>
<property name="label" translatable="yes">Gender :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_EventChannel">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_event_channel">
<property name="visible">True</property>
<property name="label" translatable="yes">Event channel :</property>
</object>
<packing>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Check impedance :</property>
</object>
<packing>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_impedance">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">7</property>
<property name="bottom_attach">8</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_calibrate">
<property name="label" translatable="yes">Auto calibrate in 4 seconds...</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator2">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button-common-gnd-ref">
<property name="label" translatable="yes">Connect to Common Gnd and Ref</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button-filters">
<property name="label" translatable="yes">Set Amplifier Filters</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator3">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_change_channel_names">
<property name="label" translatable="yes">Change channel names...</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">6</property>
</packing>
</child>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">4</property>
</packing>
</child>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_apply">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-10">button_apply</action-widget>
<action-widget response="-6">button_cancel</action-widget>
</action-widgets>
</object>
<object class="GtkListStore" id="model3">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkDialog" id="dialog-common-gnd-ref">
<property name="border_width">5</property>
<property name="title" translatable="yes">Common Ground and Reference</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox2">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="n_rows">3</property>
<property name="n_columns">2</property>
<property name="column_spacing">5</property>
<property name="row_spacing">5</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;big&gt;Connect to Common Ground&lt;/big&gt;</property>
<property name="use_markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-gnd-blockA">
<property name="label" translatable="yes">Block A (yellow)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-gnd-blockB">
<property name="label" translatable="yes">Block B (red)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-gnd-blockC">
<property name="label" translatable="yes">Block C (blue)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-gnd-blockD">
<property name="label" translatable="yes">Block D (green)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator4">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkTable" id="table3">
<property name="visible">True</property>
<property name="n_rows">3</property>
<property name="n_columns">2</property>
<property name="column_spacing">5</property>
<property name="row_spacing">5</property>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;big&gt;Connect to Common Reference&lt;/big&gt;</property>
<property name="use_markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-ref-blockA">
<property name="label" translatable="yes">Block A (yellow)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-ref-blockB">
<property name="label" translatable="yes">Block B (red)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-ref-blockC">
<property name="label" translatable="yes">Block C (blue)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton-ref-blockD">
<property name="label" translatable="yes">Block D (green)</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area2">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkDialog" id="dialog-filters">
<property name="border_width">7</property>
<property name="title" translatable="yes">Amplifier Filters</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox3">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child>
<object class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;big&gt;Notch filter&lt;/big&gt;
Type - Order - [Low Pass; High Pass] - Frequency</property>
<property name="use_markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox-notch">
<property name="visible">True</property>
<property name="model">liststore1</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator5">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;big&gt;Band Pass filter&lt;/big&gt;
Type - Order - [Low Cut; High Cut] - Frequency</property>
<property name="use_markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox-band-pass">
<property name="visible">True</property>
<property name="model">liststore2</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="position">4</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area3">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkListStore" id="liststore1">
<columns>
<!-- column-name filter -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkListStore" id="liststore2">
<columns>
<!-- column-name filter -->
<column type="gchararray"/>
</columns>
</object>
</interface>
@@ -0,0 +1,282 @@
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI
#include "ovasCConfigurationGTecGUSBampLegacy.h"
#include <windows.h>
#include <gUSBamp.h>
#include <iostream>
#include <sstream>
namespace OpenViBE {
namespace AcquisitionServer {
static void CalibratePressedCB(GtkButton* /*button*/, void* data) { static_cast<CConfigurationGTecGUSBampLegacy*>(data)->buttonCalibratePressedCB(); }
static gboolean idle_calibrate_cb(void* data)
{
static_cast<CConfigurationGTecGUSBampLegacy*>(data)->idleCalibrateCB();
return FALSE;
}
static void button_common_gnd_ref_pressed_cb(GtkButton* /*button*/, void* data)
{
static_cast<CConfigurationGTecGUSBampLegacy*>(data)->buttonCommonGndRefPressedCB();
}
static void FiltersPressedCB(GtkButton* /*button*/, void* data) { static_cast<CConfigurationGTecGUSBampLegacy*>(data)->buttonFiltersPressedCB(); }
CConfigurationGTecGUSBampLegacy::CConfigurationGTecGUSBampLegacy(const char* gtkBuilderFilename, uint32_t& usbIdx, uint8_t& commonGndAndRefBitmap,
int& notchFilterIdx, int& bandPassFilterIdx, bool& triggerInput)
: CConfigurationBuilder(gtkBuilderFilename), m_usbIdx(usbIdx), m_commonGndAndRefBitmap(commonGndAndRefBitmap), m_notchFilterIdx(notchFilterIdx),
m_bandPassFilterIdx(bandPassFilterIdx), m_triggerInput(triggerInput) {}
bool CConfigurationGTecGUSBampLegacy::preConfigure()
{
if (!CConfigurationBuilder::preConfigure()) { return false; }
GtkCheckButton* hardwareTagging = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_EventChannel"));
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(hardwareTagging), m_triggerInput);
GtkComboBox* comboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_device"));
char buffer[1024];
int count = 0;
bool selected = false;
// autodetection of the connected device
for (uint32_t i = 1; i < 11; ++i)
{
HANDLE handle = GT_OpenDevice(i);
if (handle)
{
GT_CloseDevice(&handle);
sprintf(buffer, "USB port %i", i);
gtk_combo_box_append_text(comboBox, buffer);
if (m_usbIdx == i)
{
gtk_combo_box_set_active(comboBox, count);
selected = true;
}
count++;
}
}
g_signal_connect(gtk_builder_get_object(m_builder, "button_calibrate"), "pressed", G_CALLBACK(CalibratePressedCB), this);
if (!selected && count != 0) { gtk_combo_box_set_active(comboBox, 0); }
g_signal_connect(gtk_builder_get_object(m_builder, "button-common-gnd-ref"), "pressed", G_CALLBACK(button_common_gnd_ref_pressed_cb), this);
g_signal_connect(gtk_builder_get_object(m_builder, "button-filters"), "pressed", G_CALLBACK(FiltersPressedCB), this);
GtkWidget* dialog = GTK_WIDGET(gtk_builder_get_object(m_builder, "dialog-common-gnd-ref"));
gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_APPLY, GTK_RESPONSE_APPLY);
gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
dialog = GTK_WIDGET(gtk_builder_get_object(m_builder, "dialog-filters"));
gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_APPLY, GTK_RESPONSE_APPLY);
gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
GtkToggleButton* checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockA"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & 1));
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockB"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & (1 << 1)));
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockC"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & (1 << 2)));
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockD"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & (1 << 3)));
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockA"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & (1 << 4)));
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockB"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & (1 << 5)));
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockC"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & (1 << 6)));
checkBox = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockD"));
gtk_toggle_button_set_active(checkBox, (m_commonGndAndRefBitmap & (1 << 7)));
// To check for available filters in the amplifier, we must connect to it.
if (count != 0)
{
HANDLE handle = GT_OpenDevice(count);
int nBandPassFilters, nNotchFilters;
if (!GT_GetNumberOfFilter(&nBandPassFilters)) { std::cout << "err GT_GetNumberOfFilter\n"; }
if (!GT_GetNumberOfNotch(&nNotchFilters)) { std::cout << "err GT_GetNumberOfNotch\n"; }
FILT* bpFilterSpec = new FILT[nBandPassFilters];
FILT* notchFilterSpec = new FILT[nNotchFilters];
if (!GT_GetFilterSpec(bpFilterSpec)) { std::cout << "err GT_GetFilterSpec\n"; }
if (!GT_GetNotchSpec(notchFilterSpec)) { std::cout << "err GT_GetNotchSpec\n"; }
GtkComboBox* comboBoxBandPass = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox-band-pass"));
GtkTreeModel* bandPassListStore = gtk_combo_box_get_model(comboBoxBandPass);
std::stringstream desc;
desc << "no band pass filter.";
GtkTreeIter lIter;
gtk_list_store_append(GTK_LIST_STORE(bandPassListStore), &lIter);
gtk_list_store_set(GTK_LIST_STORE(bandPassListStore), &lIter, 0, desc.str().c_str(), -1);
desc.clear();
desc.str("");
for (int i = 0; i < nBandPassFilters; ++i)
{
if (bpFilterSpec[i].type == 1) { desc << "Butterworth - "; }
if (bpFilterSpec[i].type == 2) { desc << "Chebyshev - "; }
desc << bpFilterSpec[i].order << " - [" << bpFilterSpec[i].fu << "; " << bpFilterSpec[i].fo << "] - " << bpFilterSpec[i].fs;
GtkTreeIter iter;
gtk_list_store_append(GTK_LIST_STORE(bandPassListStore), &iter);
gtk_list_store_set(GTK_LIST_STORE(bandPassListStore), &iter, 0, desc.str().c_str(), -1);
desc.clear();
desc.str("");
}
gtk_combo_box_set_active(comboBoxBandPass, m_bandPassFilterIdx + 1); // +1 because -1 is for "no filter".
GtkComboBox* comboBoxNotch = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox-notch"));
GtkTreeModel* notchListStore = gtk_combo_box_get_model(comboBoxNotch);
desc << "no notch filter.";
gtk_list_store_append(GTK_LIST_STORE(notchListStore), &lIter);
gtk_list_store_set(GTK_LIST_STORE(notchListStore), &lIter, 0, desc.str().c_str(), -1);
desc.clear();
desc.str("");
for (int i = 0; i < nNotchFilters; ++i)
{
if (notchFilterSpec[i].type == 1) { desc << "Butterworth - "; }
if (notchFilterSpec[i].type == 2) { desc << "Chebyshev - "; }
desc << notchFilterSpec[i].order << " - [" << notchFilterSpec[i].fu << "; " << notchFilterSpec[i].fo << "] - " << notchFilterSpec[i].fs;
GtkTreeIter iter;
gtk_list_store_append(GTK_LIST_STORE(notchListStore), &iter);
gtk_list_store_set(GTK_LIST_STORE(notchListStore), &iter, 0, desc.str().c_str(), -1);
desc.clear();
desc.str("");
}
gtk_combo_box_set_active(comboBoxNotch, m_notchFilterIdx + 1);
delete bpFilterSpec;
delete notchFilterSpec;
GT_CloseDevice(&handle);
}
else
{
// deactivate the buttons
GtkWidget* button = GTK_WIDGET(gtk_builder_get_object(m_builder, "button-filters"));
gtk_widget_set_sensitive(button, false);
button = GTK_WIDGET(gtk_builder_get_object(m_builder, "button-common-gnd-ref"));
gtk_widget_set_sensitive(button, false);
}
return true;
}
bool CConfigurationGTecGUSBampLegacy::postConfigure()
{
GtkComboBox* comboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_device"));
if (m_applyConfig)
{
int iUsbIdx = 0;
const char* usbIdx = gtk_combo_box_get_active_text(comboBox);
if (usbIdx) { if (sscanf(usbIdx, "USB port %i", &iUsbIdx) == 1) { m_usbIdx = uint32_t(iUsbIdx); } }
GtkToggleButton* button = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockA"));
m_commonGndAndRefBitmap = (gtk_toggle_button_get_active(button) ? 1 : 0);
button = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockB"));
m_commonGndAndRefBitmap = m_commonGndAndRefBitmap + (gtk_toggle_button_get_active(button) ? (1 << 1) : 0);
button = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockC"));
m_commonGndAndRefBitmap = m_commonGndAndRefBitmap + (gtk_toggle_button_get_active(button) ? (1 << 2) : 0);
button = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-gnd-blockD"));
m_commonGndAndRefBitmap = m_commonGndAndRefBitmap + (gtk_toggle_button_get_active(button) ? (1 << 3) : 0);
button = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockA"));
m_commonGndAndRefBitmap = m_commonGndAndRefBitmap + (gtk_toggle_button_get_active(button) ? (1 << 4) : 0);
button = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockB"));
m_commonGndAndRefBitmap = m_commonGndAndRefBitmap + (gtk_toggle_button_get_active(button) ? (1 << 5) : 0);
button = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockC"));
m_commonGndAndRefBitmap = m_commonGndAndRefBitmap + (gtk_toggle_button_get_active(button) ? (1 << 6) : 0);
button = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton-ref-blockD"));
m_commonGndAndRefBitmap = m_commonGndAndRefBitmap + (gtk_toggle_button_get_active(button) ? (1 << 7) : 0);
GtkComboBox* notch = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox-notch"));
m_notchFilterIdx = gtk_combo_box_get_active(notch) - 1;
GtkComboBox* bandPass = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox-band-pass"));
m_bandPassFilterIdx = gtk_combo_box_get_active(bandPass) - 1;
GtkCheckButton* hardwareTagging = GTK_CHECK_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_EventChannel"));
m_triggerInput = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(hardwareTagging)) ? true : false);
}
if (!CConfigurationBuilder::postConfigure()) { return false; }
return true;
}
void CConfigurationGTecGUSBampLegacy::buttonCalibratePressedCB()
{
g_idle_add(idle_calibrate_cb, this);
m_calibrateDialog = gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, "Calibrating...");
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(m_calibrateDialog), "Please wait a few seconds...");
gtk_dialog_run(GTK_DIALOG(m_calibrateDialog));
gtk_widget_destroy(m_calibrateDialog);
if (m_calibrationDone)
{
GtkWidget* dialog = gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "Calibration finished !");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
}
else
{
GtkWidget* dialog = gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "Calibration failed !");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
}
}
void CConfigurationGTecGUSBampLegacy::idleCalibrateCB()
{
GtkComboBox* comboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_device"));
m_calibrationDone = false;
int iUsbIdx = 0;
const char* usbIdx = gtk_combo_box_get_active_text(comboBox);
if (usbIdx)
{
if (sscanf(usbIdx, "USB port %i", &iUsbIdx) == 1)
{
HANDLE handle = GT_OpenDevice(iUsbIdx);
if (handle)
{
m_calibrationDone = true;
SCALE calibration;
if (!GT_Calibrate(handle, &calibration)) { std::cout << "err GT_Calibrate\n", m_calibrationDone = false; }
if (!GT_SetScale(handle, &calibration))
{
std::cout << "err GT_SetScale\n";
m_calibrationDone = false;
}
GT_CloseDevice(&handle);
}
}
}
gtk_dialog_response(GTK_DIALOG(m_calibrateDialog), 0);
}
void CConfigurationGTecGUSBampLegacy::buttonCommonGndRefPressedCB()
{
GtkWidget* dialog = GTK_WIDGET(gtk_builder_get_object(m_builder, "dialog-common-gnd-ref"));
gint resp = gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_hide(dialog);
}
void CConfigurationGTecGUSBampLegacy::buttonFiltersPressedCB()
{
GtkWidget* dialog = GTK_WIDGET(gtk_builder_get_object(m_builder, "dialog-filters"));
gint resp = gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_hide(dialog);
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGUSBampCAPI
@@ -0,0 +1,41 @@
#pragma once
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI
#include "../ovasCConfigurationBuilder.h"
#include <gtk/gtk.h>
namespace OpenViBE {
namespace AcquisitionServer {
class CConfigurationGTecGUSBampLegacy final : public CConfigurationBuilder
{
public:
CConfigurationGTecGUSBampLegacy(const char* gtkBuilderFilename, uint32_t& usbIdx, uint8_t& commonGndAndRefBitmap, int& notchFilterIdx,
int& bandPassFilterIdx, bool& triggerInput);
bool preConfigure() override;
bool postConfigure() override;
void buttonCalibratePressedCB();
void idleCalibrateCB();
void buttonCommonGndRefPressedCB();
void buttonFiltersPressedCB();
protected:
uint32_t& m_usbIdx;
uint8_t& m_commonGndAndRefBitmap;
int& m_notchFilterIdx;
int& m_bandPassFilterIdx;
bool& m_triggerInput;
private:
GtkWidget* m_calibrateDialog = nullptr;
bool m_calibrationDone = false;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGUSBampCAPI
@@ -0,0 +1,351 @@
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI
#include "ovasCDriverGTecGUSBampLegacy.h"
#include "ovasCConfigurationGTecGUSBampLegacy.h"
#include <toolkit/ovtk_all.h>
#include <system/ovCTime.h>
#include <cmath>
#include <windows.h>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <gUSBamp.h>
#define GTEC_NUM_CHANNELS 16
namespace OpenViBE {
namespace AcquisitionServer {
/*
This driver always reads 17 channels: 16 + 1
16 are EEG channels
1 is the last channel that provides triggers from the parallel port of the GTEC
Although 17 channels are read only "m_nAcquiredChannel" + 1 (if m_pTriggerInputEnabled==true) are displayed.
If m_nAcquiredChannel=6 and m_pTriggerInputEnabled=true then the output in OpenVibe is 7 channels. If m_pTriggerInputEnabled=false then 6.
"m_nAcquiredChannel" is a user modifiable variable with default value 16
*/
CDriverGTecGUSBampLegacy::CDriverGTecGUSBampLegacy(IDriverContext& ctx)
: IDriver(ctx)
, m_settings("AcquisitionServer_Driver_GTecGUSBampLegacy", m_driverCtx.getConfigurationManager())
, m_deviceIdx(uint32_t(-1))
, m_notchFilterIdx(-1)
, m_bandPassFilterIdx(-1)
, m_nAcquiredChannel(GTEC_NUM_CHANNELS)
{
m_header.setSamplingFrequency(512);
m_header.setChannelCount(GTEC_NUM_CHANNELS);
m_settings.add("Header", &m_header);
m_settings.add("DeviceIndex", &m_deviceIdx);
m_settings.add("CommonGndAndRefBitmap", &m_commonGndAndRefBitmap);
m_settings.add("NotchFilterIndex", &m_notchFilterIdx);
m_settings.add("BandPassFilterIndex", &m_bandPassFilterIdx);
m_settings.add("TriggerInputEnabled", &m_bTriggerInputEnabled);
m_settings.load();
}
//___________________________________________________________________//
// //
bool CDriverGTecGUSBampLegacy::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
{
if (m_driverCtx.isConnected() || !m_header.isChannelCountSet() || !m_header.isSamplingFrequencySet()) { return false; }
if (m_bTriggerInputEnabled)
{
m_header.setChannelCount(m_nAcquiredChannel + 1);
m_header.setChannelName(m_nAcquiredChannel, "CH_Event");
}
else { m_header.setChannelCount(m_nAcquiredChannel); }
// If device has not been selected, try to find a device
uint32_t i = 0;
m_actualDeviceIdx = m_deviceIdx;
while (i < 11 && m_actualDeviceIdx == uint32_t(-1))
{
HANDLE handle = GT_OpenDevice(i);
if (handle)
{
GT_CloseDevice(&handle);
m_actualDeviceIdx = i;
}
i++;
}
if (m_actualDeviceIdx == uint32_t(-1)) { return false; }
m_pEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (!m_pEvent) { return false; }
//allocate buffers
m_bufferSize = (GTEC_NUM_CHANNELS + 1) * nSamplePerSentBlock * sizeof(float) + HEADER_SIZE;//+1 channel for trigger
m_Buffer = new uint8_t[m_bufferSize];
if (m_bTriggerInputEnabled) { m_sample = new float[(m_nAcquiredChannel + 1) * nSamplePerSentBlock]; }
else { m_sample = new float[m_nAcquiredChannel * nSamplePerSentBlock]; }
if (!m_Buffer || !m_sample)
{
delete [] m_Buffer;
delete [] m_sample;
CloseHandle(m_pEvent);
return false;
}
memset(m_Buffer, 0, m_bufferSize);
m_sampleTranspose = reinterpret_cast<float*>(m_Buffer + HEADER_SIZE);
m_pOverlapped = new OVERLAPPED;
if (!m_pOverlapped)
{
delete [] m_Buffer;
delete [] m_sample;
CloseHandle(m_pEvent);
return false;
}
#define m_pOverlapped ((OVERLAPPED*)m_pOverlapped)
memset(m_pOverlapped, 0, sizeof(OVERLAPPED));
m_pOverlapped->hEvent = m_pEvent;
m_Device = GT_OpenDevice(m_actualDeviceIdx);
if (!m_Device)
{
delete m_pOverlapped;
delete [] m_Buffer;
delete [] m_sample;
CloseHandle(m_pEvent);
return false;
}
m_actualImpedanceIdx = 0;
m_nSamplePerSentBlock = nSamplePerSentBlock;
m_callback = &callback;
return true;
}
bool CDriverGTecGUSBampLegacy::start()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
UCHAR channels[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
// The amplifier is divided in 4 blocks, A to D
// each one has its own Ref/gnd connections,
// user can specify whether or not to connect the block to the common ground and reference of the amplifier.
GND gnd;
gnd.GND1 = (m_commonGndAndRefBitmap & 1);
gnd.GND2 = (m_commonGndAndRefBitmap & (1 << 1));
gnd.GND3 = (m_commonGndAndRefBitmap & (1 << 2));
gnd.GND4 = (m_commonGndAndRefBitmap & (1 << 3));
REF ref;
ref.ref1 = (m_commonGndAndRefBitmap & (1 << 4));
ref.ref2 = (m_commonGndAndRefBitmap & (1 << 5));
ref.ref3 = (m_commonGndAndRefBitmap & (1 << 6));
ref.ref4 = (m_commonGndAndRefBitmap & (1 << 7));
if (!GT_SetMode(m_Device, M_NORMAL)) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unexpected error while calling GT_SetMode\n"; }
if (!GT_SetBufferSize(m_Device, m_nSamplePerSentBlock))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unexpected error while calling GT_SetBufferSize\n";
}
if (!GT_SetChannels(m_Device, channels, sizeof(channels) / sizeof(UCHAR)))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unexpected error while calling GT_SetChannels\n";
}
if (!GT_SetSlave(m_Device, FALSE)) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unexpected error while calling GT_SetSlave\n"; }
if (!GT_EnableTriggerLine(m_Device, TRUE))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unexpected error while calling GT_EnableTriggerLine - the extra input trigger channel is disabled\n";
}
// GT_EnableSC
// GT_SetBipolar
for (uint32_t i = 0; i < m_nAcquiredChannel; ++i)
{
if (!GT_SetBandPass(m_Device, i + 1, m_bandPassFilterIdx))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unexpected error while calling GT_SetBandPass for channel " << i << "\n";
}
if (!GT_SetNotch(m_Device, i + 1, m_notchFilterIdx))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unexpected error while calling GT_SetNotch for channel " << i << "\n";
}
}
if (!GT_SetSampleRate(m_Device, m_header.getSamplingFrequency()))
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unexpected error while calling GT_SetSampleRate\n";
}
if (!GT_SetReference(m_Device, ref)) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unexpected error while calling GT_SetReference\n"; }
if (!GT_SetGround(m_Device, gnd)) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unexpected error while calling GT_SetGround\n"; }
m_lastStimulation = STIMULATION_0;
m_totalHardwareStimulations = 0;
m_totalDriverChunksLost = 0;
GT_Start(m_Device);
return true;
}
bool CDriverGTecGUSBampLegacy::loop()
{
CStimulationSet stimSet;
if (!m_driverCtx.isConnected()) { return false; }
if (m_driverCtx.isStarted())
{
if (GT_GetData(m_Device, m_Buffer, m_bufferSize, m_pOverlapped))
{
if (WaitForSingleObject(m_pOverlapped->hEvent, 1000) == WAIT_OBJECT_0)
{
DWORD nByte = 0;
GetOverlappedResult(m_Device, m_pOverlapped, &nByte, FALSE);
if (nByte == m_bufferSize)
{
for (uint32_t i = 0; i < m_nAcquiredChannel; ++i)
{
for (uint32_t j = 0; j < m_nSamplePerSentBlock; ++j)
{
m_sample[i * m_nSamplePerSentBlock + j] = m_sampleTranspose[j * (GTEC_NUM_CHANNELS + 1) + i];
}
}
if (m_bTriggerInputEnabled)
{
for (uint32_t iSample = 0; iSample < m_nSamplePerSentBlock; ++iSample)
{
const uint32_t stimCode = uint32_t(
m_sampleTranspose[iSample * (GTEC_NUM_CHANNELS + 1) + GTEC_NUM_CHANNELS]);
m_sample[m_nAcquiredChannel * m_nSamplePerSentBlock + iSample] = float(stimCode);
//this means that the user sends 0 after each stimulatuion and in the beginning
if ((stimCode != STIMULATION_0) && (stimCode != m_lastStimulation)
)
{
uint64_t identifier;
switch (stimCode)
{
case STIMULATION_64: identifier = OVTK_StimulationId_Label_01;
break;
case STIMULATION_128: identifier = OVTK_StimulationId_Label_02;
break;
case STIMULATION_192: identifier = OVTK_StimulationId_Label_03;
break;
default: identifier = OVTK_StimulationId_Label_07;
}
const uint64_t time = CTime(m_header.getSamplingFrequency(), uint64_t(iSample)).time();
stimSet.appendStimulation(identifier, time, 0);
m_totalHardwareStimulations++;
}
m_lastStimulation = stimCode;
}
}
m_callback->setSamples(m_sample);
m_callback->setStimulationSet(stimSet);
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
}
else
{
m_totalDriverChunksLost++;
//m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "nByte and m_bufferSize differs : " << nByte << "/" << m_bufferSize << "(header size is " << HEADER_SIZE << ")\n";
/*m_driverCtx.getLogManager() << Kernel::LogLevel_Warning
<< "Returned data is different than expected. Total chunks lost: " << m_totalDriverChunksLost
<< ", Total samples lost: " << m_nSamplePerSentBlock * m_totalDriverChunksLost
<< "\n";*/
}
}
else
{
// TIMEOUT
//m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "timeout 1\n";
}
}
else
{
//m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "tError on GT_GetData.\n";
}
}
else
{
if (m_driverCtx.isImpedanceCheckRequested())
{
double impedance = 0;
GT_GetImpedance(m_Device, m_actualImpedanceIdx + 1, &impedance);
if (impedance < 0) { impedance *= -1; }
m_driverCtx.updateImpedance(m_actualImpedanceIdx, impedance);
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Channel " << m_actualImpedanceIdx << " - "
<< m_header.getChannelName(m_actualImpedanceIdx) << " : " << impedance << "\n";
m_actualImpedanceIdx++;
m_actualImpedanceIdx %= m_header.getChannelCount();
m_driverCtx.updateImpedance(m_actualImpedanceIdx, -1);
}
else { System::Time::sleep(20); }
}
return true;
}
bool CDriverGTecGUSBampLegacy::stop()
{
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
//stop device
GT_Stop(m_Device);
GT_ResetTransfer(m_Device);
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Total number of hardware stimulations acquired: " << m_totalHardwareStimulations << "\n";
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Total chunks lost: " << m_totalDriverChunksLost << "\n";
return true;
}
bool CDriverGTecGUSBampLegacy::uninitialize()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
GT_CloseDevice(&m_Device);
CloseHandle(m_pEvent);
delete [] m_Buffer;
delete [] m_sample;
delete m_pOverlapped;
m_Device = nullptr;
m_pEvent = nullptr;
m_Buffer = nullptr;
m_sample = nullptr;
m_callback = nullptr;
return true;
}
bool CDriverGTecGUSBampLegacy::configure()
{
CConfigurationGTecGUSBampLegacy config(Directories::getDataDir() + "/applications/acquisition-server/interface-GTec-GUSBampLegacy.ui",
m_deviceIdx, m_commonGndAndRefBitmap, m_notchFilterIdx, m_bandPassFilterIdx,
m_bTriggerInputEnabled);
if (!config.configure(m_header)) { return false; }
this->m_nAcquiredChannel = m_header.getChannelCount();
m_settings.save();
return true;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGUSBampCAPI
@@ -0,0 +1,88 @@
#pragma once
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI
#include "ovasIDriver.h"
#include "../ovasCHeader.h"
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
#include <gtk/gtk.h>
#include <vector>
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CDriverGTecGUSBampLegacy
* \author Yann Renard (Inria)
* \date unknown
* \brief GTEC driver
*
*/
class CDriverGTecGUSBampLegacy final : public IDriver
{
public:
explicit CDriverGTecGUSBampLegacy(IDriverContext& ctx);
void release() { delete this; }
const char* getName() override { return "g.tec gUSBamp Legacy"; }
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
bool uninitialize() override;
bool start() override;
bool stop() override;
bool loop() override;
bool isConfigurable() override { return true; }
bool configure() override;
const IHeader* getHeader() override { return &m_header; }
bool isFlagSet(const EDriverFlag flag) const override { return flag == EDriverFlag::IsDeprecated; }
protected:
SettingsHelper m_settings;
IDriverCallback* m_callback = nullptr;
CHeader m_header;
uint32_t m_nSamplePerSentBlock = 0;
uint32_t m_deviceIdx = 0;
uint32_t m_actualDeviceIdx = 0;
uint32_t m_bufferSize = 0;
uint8_t* m_Buffer = nullptr;
float* m_sampleTranspose = nullptr;
float* m_sample = nullptr;
void* m_Device = nullptr;
void* m_pEvent = nullptr;
void* m_pOverlapped = nullptr;
uint32_t m_actualImpedanceIdx = 0;
uint8_t m_commonGndAndRefBitmap = 0;
int m_notchFilterIdx = 0;
int m_bandPassFilterIdx = 0;
bool m_bTriggerInputEnabled = false;
uint32_t m_lastStimulation = 0;
// EVENT CHANNEL : contribution Anton Andreev (Gipsa-lab) - 0.14.0
typedef enum
{
STIMULATION_0 = 0,
STIMULATION_64 = 64,
STIMULATION_128 = 128,
STIMULATION_192 = 192
} gtec_triggers_t;
uint32_t m_totalHardwareStimulations = 0; //since start button clicked
uint32_t m_totalDriverChunksLost = 0; //since start button clicked
uint32_t m_nAcquiredChannel = 0; //number of channels 1..16 specified bu user
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyGUSBampCAPI
@@ -0,0 +1,403 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkAdjustment" id="adjustment1">
<property name="lower">24</property>
<property name="upper">27</property>
<property name="value">24</property>
<property name="step_increment">3</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="upper">100</property>
<property name="value">18</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment3">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment4">
<property name="upper">255</property>
<property name="value">0</property>
<property name="lower">0</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkListStore" id="model1">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">unspecified</col>
</row>
<row>
<col id="0" translatable="yes">female</col>
</row>
<row>
<col id="0" translatable="yes">male</col>
</row>
<row>
<col id="0" translatable="yes">unknown</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model2">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">500</col>
</row>
<row>
<col id="0" translatable="yes">250</col>
</row>
</data>
</object>
<object class="GtkDialog" id="openvibe-acquisition-server-settings">
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Device configuration</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="spacing">8</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_apply">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_title">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">mBrainTrain configuration (24 channels)</property>
<property name="use_markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">8</property>
<property name="spacing">8</property>
<child>
<object class="GtkHBox" id="hbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">8</property>
<child>
<object class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">6</property>
<property name="n_columns">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkSpinButton" id="spinbutton_number_of_channels">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="sensitive">False</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_number_of_channels">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Number of channels</property>
<property name="use_markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_port_number">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Port number :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_port_number">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment2</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_gender">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">model1</property>
<child>
<object class="GtkCellRendererText" id="renderer1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_age">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment2</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_identifier">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment3</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_gender">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Gender :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_age">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Age :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_identifier">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Identifier :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
</child>
<child>
<object class="GtkComboBox" id="combobox_sampling_frequency">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">model2</property>
<child>
<object class="GtkCellRendererText" id="renderer3"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_sampling_frequency">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Sampling frequency :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_change_channel_names">
<property name="label" translatable="yes">Change channel names</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-10">button_apply</action-widget>
<action-widget response="-6">button_cancel</action-widget>
</action-widgets>
</object>
</interface>
@@ -0,0 +1,77 @@
#include "ovasCConfigurationMBTSmarting.h"
#include <string.h>
#include <iostream>
namespace OpenViBE {
namespace AcquisitionServer {
/*_________________________________________________
Insert callback to specific widget here
Example with a button that launch a calibration of the device:
//Callback connected to a dedicated gtk button:
static void button_calibrate_pressed_cb(GtkButton* button, void* data)
{
CConfigurationMBTSmarting* config=static_cast<CConfigurationMBTSmarting*>(data);
config->buttonCalibratePressedCB();
}
//Callback actually called:
void CConfigurationGTecGUSBamp::buttonCalibratePressedCB()
{
// Connect to the hardware, ask for calibration, verify the return code, etc.
}
_________________________________________________*/
// If you added more reference attribute, initialize them here
CConfigurationMBTSmarting::CConfigurationMBTSmarting(IDriverContext& ctx, const char* gtkBuilderFilename, uint32_t& rConnectionId)
: CConfigurationBuilder(gtkBuilderFilename), m_driverCtx(ctx), m_connectionID(rConnectionId)
{
//m_listStore = gtk_list_store_new(1, G_TYPE_STRING);
}
CConfigurationMBTSmarting::~CConfigurationMBTSmarting()
{
//g_object_unref(m_listStore);
}
bool CConfigurationMBTSmarting::preConfigure()
{
if (! CConfigurationBuilder::preConfigure()) { return false; }
// Apply previous port number
GtkSpinButton* buttonPortNumber = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_port_number"));
gtk_spin_button_set_value(buttonPortNumber, m_connectionID);
// Connect here all callbacks
// Example:
// g_signal_connect(gtk_builder_get_object(m_builder, "button_calibrate"), "pressed", G_CALLBACK(button_calibrate_pressed_cb), this);
// Insert here the pre-configure code.
// For example, you may want to check if a device is currently connected
// and if more than one are connected. Then you can list in a dedicated combo-box
// the device currently connected so the user can choose which one he wants to acquire from.
return true;
}
bool CConfigurationMBTSmarting::postConfigure()
{
if (m_applyConfig)
{
// If the user pressed the "apply" button, you need to save the changes made in the configuration.
// For example, you can save the connection ID of the selected device:
// m_connectionID = <value-from-gtk-widget>
GtkSpinButton* buttonPortNumber = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_port_number"));
gtk_spin_button_update(buttonPortNumber);
m_connectionID = gtk_spin_button_get_value_as_int(buttonPortNumber);
}
// normal header is filled (Subject ID, Age, Gender, channels, sampling frequency), ressources are realesed
return CConfigurationBuilder::postConfigure();
}
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,46 @@
#pragma once
#include "../ovasCConfigurationBuilder.h"
#include "ovasIDriver.h"
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CConfigurationMBTSmarting
* \author mBrainTrain dev team (mBrainTrain)
* \date Tue Oct 14 16:09:43 2014
* \brief The CConfigurationMBTSmarting handles the configuration dialog specific to the MBTSmarting device.
*
* TODO: details
*
* \sa CDriverMBTSmarting
*/
class CConfigurationMBTSmarting final : public CConfigurationBuilder
{
public:
// you may have to add to your constructor some reference parameters
// for example, a connection ID:
CConfigurationMBTSmarting(IDriverContext& ctx, const char* gtkBuilderFilename, uint32_t& rConnectionId);
//CConfigurationMBTSmarting(IDriverContext& ctx, const char* gtkBuilderFilename);
bool preConfigure() override;
bool postConfigure() override;
~CConfigurationMBTSmarting() override;
protected:
IDriverContext& m_driverCtx;
private:
/*
* Insert here all specific attributes, such as a connection ID.
* use references to directly modify the corresponding attribute of the driver
* Example:
*/
uint32_t& m_connectionID;
//::GtkListStore* m_listStore;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,240 @@
#include "ovasCDriverMBTSmarting.h"
#include "ovasCConfigurationMBTSmarting.h"
#include <toolkit/ovtk_all.h>
#include <string>
#include <sstream>
namespace OpenViBE {
namespace AcquisitionServer {
//___________________________________________________________________//
// //
CDriverMBTSmarting::CDriverMBTSmarting(IDriverContext& ctx)
: IDriver(ctx)
, m_settings("AcquisitionServer_Driver_MBTSmarting", m_driverCtx.getConfigurationManager())
{
m_header.setSamplingFrequency(500);
m_header.setChannelCount(27);
// The following class allows saving and loading driver settings from the acquisition server .conf file
m_settings.add("Header", &m_header);
// To save your custom driver settings, register each variable to the SettingsHelper
m_settings.add("ConnectionID", &m_connectionID);
m_settings.load();
}
//___________________________________________________________________//
// //
bool CDriverMBTSmarting::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
{
if (m_driverCtx.isConnected() || !m_header.isChannelCountSet() || !m_header.isSamplingFrequencySet()) { return false; }
// Builds up a buffer to store
// acquired samples. This buffer
// will be sent to the acquisition
// server later...
m_sample = new float[m_header.getChannelCount()];
if (!m_sample)
{
delete [] m_sample;
m_sample = nullptr;
return false;
}
// ...
// initialize hardware and get
// available header information
// from it
// Using for example the connection ID provided by the configuration (m_connectionID)
// ...
m_pSmartingAmp.reset(new SmartingAmp);
std::stringstream port_ss;
#ifdef TARGET_OS_Windows
port_ss << "COM" << m_connectionID;
#elif defined TARGET_OS_Linux
port_ss << "/dev/rfcomm" << m_connectionID;
#endif
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Attempting to Connect to Device at : " << port_ss.str() << "\n";
std::string port(port_ss.str());
const bool connected = m_pSmartingAmp->connect(port);
if (connected)
{
// set sampling frequency
switch (m_header.getSamplingFrequency())
{
case 250: m_pSmartingAmp->send_command(FREQUENCY_250);
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Setting the sampling frequency at " << 250 << "\n";
break;
case 500: m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Setting the sampling frequency at " << 500 << "\n";
m_pSmartingAmp->send_command(FREQUENCY_500);
break;
default: m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Only sampling frequencies 250 and 500 are supported\n";
return false;
}
// Declare channel units
for (uint32_t c = 0; c < 24; ++c)
{
m_header.setChannelUnits(c, OVTK_UNIT_Volts, OVTK_FACTOR_Micro); // signal channels
}
m_header.setChannelUnits(24, OVTK_UNIT_Degree_Per_Second, OVTK_FACTOR_Base); // gyroscope outputs
m_header.setChannelUnits(25, OVTK_UNIT_Degree_Per_Second, OVTK_FACTOR_Base);
m_header.setChannelUnits(26, OVTK_UNIT_Degree_Per_Second, OVTK_FACTOR_Base);
m_header.setChannelName(24, "Gyro 1");
m_header.setChannelName(25, "Gyro 2");
m_header.setChannelName(26, "Gyro 3");
// Saves parameters
m_callback = &callback;
m_nSamplePerSentBlock = nSamplePerSentBlock;
return true;
}
return false;
}
bool CDriverMBTSmarting::start()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// ...
// request hardware to start
// sending data
// ...
m_pSmartingAmp->send_command(ON);
m_byteArray.clear();
sample_number = 1;
latency = 1;
return true;
}
bool CDriverMBTSmarting::loop()
{
if (!m_driverCtx.isConnected()) { return false; }
if (!m_driverCtx.isStarted()) { return true; }
const CStimulationSet stimSet;
// ...
// receive samples from hardware
// put them the correct way in the sample array
// whether the buffer is full, send it to the acquisition server
//...
unsigned char* receiveBuffer = new unsigned char[MAX_PACKAGE_SIZE];
const int readed = m_pSmartingAmp->read(receiveBuffer, MAX_PACKAGE_SIZE);
for (int i = 0; i < readed; ++i)
{
if (!m_byteArray.empty())
{
m_byteArray.push_back(receiveBuffer[i]);
if (m_byteArray.size() == 83)
{
if (m_byteArray[82] == '<')
{
if (sample_number % 5000 == 0)
{
sample_number = 1;
if (m_driverCtx.getDriftSampleCount() < 2)
{
m_sample = m_pSmartingAmp->convert_data(m_byteArray);
m_callback->setSamples(m_sample, 1);
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
}
}
else
{
sample_number++;
m_sample = m_pSmartingAmp->convert_data(m_byteArray);
m_callback->setSamples(m_sample, 1);
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
}
}
m_byteArray.clear();
}
}
if (m_byteArray.empty() && receiveBuffer[i] == '>') { m_byteArray.push_back(receiveBuffer[i]); }
}
if (latency == 300)
{
latency = 0;
m_driverCtx.setInnerLatencySampleCount(-m_driverCtx.getDriftSampleCount());
}
else { if (latency != 0) { latency++; } }
// ...
// receive events from hardware
// and put them the correct way in a CStimulationSet object
//...
m_callback->setStimulationSet(stimSet);
return true;
}
bool CDriverMBTSmarting::stop()
{
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
// ...
// request the hardware to stop
// sending data
// ...
m_driverCtx.setInnerLatencySampleCount(0);
m_pSmartingAmp->off();
return true;
}
bool CDriverMBTSmarting::uninitialize()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// ...
// uninitialize hardware here
// ...
m_pSmartingAmp->disconnect();
delete [] m_sample;
m_sample = nullptr;
m_callback = nullptr;
return true;
}
//___________________________________________________________________//
// //
bool CDriverMBTSmarting::configure()
{
// Change this line if you need to specify some references to your driver attribute that need configuration, e.g. the connection ID.
CConfigurationMBTSmarting config(m_driverCtx, Directories::getDataDir() + "/applications/acquisition-server/interface-MBTSmarting.ui",
m_connectionID);
if (!config.configure(m_header)) { return false; }
m_settings.save();
return true;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,73 @@
#pragma once
#include "ovasIDriver.h"
#include "../ovasCHeader.h"
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
#include "ovasCSmartingAmp.h"
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CDriverMBTSmarting
* \author mBrainTrain dev team (mBrainTrain)
* \date Tue Oct 14 16:09:43 2014
* \brief The CDriverMBTSmarting allows the acquisition server to acquire data from a MBTSmarting device.
*
* TODO: details
*
* \sa CConfigurationMBTSmarting
*/
class CDriverMBTSmarting final : public IDriver
{
public:
explicit CDriverMBTSmarting(IDriverContext& ctx);
~CDriverMBTSmarting() override {}
const char* getName() override { return "mBrainTrain Smarting"; }
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
bool uninitialize() override;
bool start() override;
bool stop() override;
bool loop() override;
bool isConfigurable() override { return true; } // change to false if your device is not configurable
bool configure() override;
const IHeader* getHeader() override { return &m_header; }
#ifdef TARGET_OS_Windows
bool isFlagSet(const EDriverFlag /*flag*/) const override { return false; }
#elif defined TARGET_OS_Linux
bool isFlagSet(const EDriverFlag /*flag*/) const override { return true; }
#endif
protected:
SettingsHelper m_settings;
IDriverCallback* m_callback = nullptr;
// Replace this generic Header with any specific header you might have written
CHeader m_header;
uint32_t m_nSamplePerSentBlock = 0;
float* m_sample = nullptr;
private:
/*
* Insert here all specific attributes, such as USB port number or device ID.
* Example :
*/
uint32_t m_connectionID = 1;
std::shared_ptr<SmartingAmp> m_pSmartingAmp;
std::vector<unsigned char> m_byteArray;
int sample_number = 0;
int latency = 0;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,431 @@
#include "ovasCSmartingAmp.h"
#include <system/ovCTime.h>
#include <boost/bind.hpp>
#include <thread>
#include <functional>
#include <iostream>
#define CHANNEL_SCALAR_FACTOR 2.235174445530706e-2
#define GYRO_SCALAR_FACTOR 0.00875
SmartingAmp::SmartingAmp()
{
m_port.reset(new boost::asio::serial_port(m_io));
m_timer.reset(new boost::asio::deadline_timer(m_io));
}
void SmartingAmp::disconnect()
{
if (!m_port->is_open()) { return; }
m_port->close();
}
void SmartingAmp::write(const unsigned char* data, const size_t& size) const
{
boost::system::error_code code;
m_port->write_some(boost::asio::buffer(data, size), code);
}
int SmartingAmp::read(unsigned char* data, const size_t size) const
{
boost::system::error_code code;
return m_port->read_some(boost::asio::buffer(data, size), code);
}
void SmartingAmp::acquire()
{
if (m_port.get() == nullptr || !m_port->is_open()) { return; }
m_port->async_read_some(boost::asio::buffer(m_receiveBuffer, MAX_PACKAGE_SIZE),
boost::bind(&SmartingAmp::onReceive, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void SmartingAmp::onReceive(const boost::system::error_code& ec, const size_t bytesTransferred)
{
std::lock_guard<std::mutex> m(m_on_receive_lock);
if (m_port.get() == nullptr || !m_port->is_open()) { return; }
if (ec)
{
//acquire();
return;
}
for (uint32_t i = 0; i < bytesTransferred; ++i)
{
if (!m_byteArray.empty())
{
m_byteArray.push_back(m_receiveBuffer[i]);
if (m_byteArray.size() == 83)
{
if (m_byteArray[82] == '<')
{
float* sample = convert_data(m_byteArray);
m_byteArray.clear();
m_receivedSamples++;
m_samples_lock.lock();
m_samplesBuffer.push(sample);
m_samples_lock.unlock();
}
else
{
m_failedSamples++;
m_byteArray.clear();
}
}
}
if (m_byteArray.empty() && m_receiveBuffer[i] == '>') { m_byteArray.push_back(m_receiveBuffer[i]); }
}
acquire();
}
std::pair<unsigned char*, int> SmartingAmp::make_command(const Command command)
{
unsigned char* cmd;
std::pair<unsigned char*, size_t> out;
switch (command)
{
case ON: cmd = new unsigned char[4];
cmd[0] = '>';
cmd[1] = 'O';
cmd[2] = 'N';
cmd[3] = '<';
out = std::make_pair(cmd, 4);
break;
case OFF: cmd = new unsigned char[5];
cmd[0] = '>';
cmd[1] = 'O';
cmd[2] = 'F';
cmd[3] = 'F';
cmd[4] = '<';
out = std::make_pair(cmd, 5);
break;
case TEST: cmd = new unsigned char[6];
cmd[0] = '>';
cmd[1] = 'T';
cmd[2] = 'E';
cmd[3] = 'S';
cmd[4] = 'T';
cmd[5] = '<';
out = std::make_pair(cmd, 6);
break;
case NORMAL: cmd = new unsigned char[8];
cmd[0] = '>';
cmd[1] = 'N';
cmd[2] = 'O';
cmd[3] = 'R';
cmd[4] = 'M';
cmd[5] = 'A';
cmd[6] = 'L';
cmd[7] = '<';
out = std::make_pair(cmd, 8);
break;
case SELECT_CHANNELS: cmd = new unsigned char[8];
cmd[0] = '>';
cmd[1] = 'S';
cmd[2] = 'C';
cmd[3] = ';';
cmd[4] = 0xFF;
cmd[5] = 0xFF;
cmd[6] = 0xFF;
cmd[7] = '<';
out = std::make_pair(cmd, 8);
break;
case REFON: cmd = new unsigned char[7];
cmd[0] = '>';
cmd[1] = 'R';
cmd[2] = 'E';
cmd[3] = 'F';
cmd[4] = 'O';
cmd[5] = 'N';
cmd[6] = '<';
out = std::make_pair(cmd, 7);
break;
case REFOFF: cmd = new unsigned char[8];
cmd[0] = '>';
cmd[1] = 'R';
cmd[2] = 'E';
cmd[3] = 'F';
cmd[4] = 'O';
cmd[5] = 'F';
cmd[6] = 'F';
cmd[7] = '<';
out = std::make_pair(cmd, 8);
break;
case IMPON: cmd = new unsigned char[7];
cmd[0] = '>';
cmd[1] = 'I';
cmd[2] = 'M';
cmd[3] = 'P';
cmd[4] = 'O';
cmd[5] = 'N';
cmd[6] = '<';
out = std::make_pair(cmd, 7);
break;
case IMPOFF: cmd = new unsigned char[8];
cmd[0] = '>';
cmd[1] = 'I';
cmd[2] = 'M';
cmd[3] = 'P';
cmd[4] = 'O';
cmd[5] = 'F';
cmd[6] = 'F';
cmd[7] = '<';
out = std::make_pair(cmd, 8);
break;
case FREQUENCY_250: cmd = new unsigned char[5];
cmd[0] = '>';
cmd[1] = '2';
cmd[2] = '5';
cmd[3] = '0';
cmd[4] = '<';
out = std::make_pair(cmd, 5);
break;
case FREQUENCY_500: cmd = new unsigned char[5];
cmd[0] = '>';
cmd[1] = '5';
cmd[2] = '0';
cmd[3] = '0';
cmd[4] = '<';
out = std::make_pair(cmd, 5);
break;
case NOISE: cmd = new unsigned char[7];
cmd[0] = '>';
cmd[1] = 'N';
cmd[2] = 'O';
cmd[3] = 'I';
cmd[4] = 'S';
cmd[5] = 'E';
cmd[6] = '<';
out = std::make_pair(cmd, 7);
break;
}
return out;
}
CommandStatus SmartingAmp::send_command(const Command cmd)
{
const std::pair<unsigned char*, size_t> newCmd = make_command(cmd);
write(newCmd.first, newCmd.second);
unsigned char* response = new unsigned char[OK_RESPONSE_SIZE];
const int readed = read(response, OK_RESPONSE_SIZE);
return command_status(response, readed);
}
bool SmartingAmp::connect(std::string& portName)
{
boost::system::error_code errorCode;
m_port->open(portName, errorCode);
if (errorCode)
{
std::cout << "error : port->open() failed...com_port_name = " << portName << ", e=" << errorCode.message() << std::endl;
disconnect();
// m_port->open(portName, error_code);
return false;
}
try
{
m_port->set_option(boost::asio::serial_port_base::baud_rate(921600));
m_port->set_option(boost::asio::serial_port_base::character_size(8));
m_port->set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one));
m_port->set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none));
m_port->set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::hardware));
}
catch (const std::exception&)
{
disconnect();
return false;
}
// NOTE: After connection is established wait 3s and then you can start using the amp
System::Time::sleep(3000);
// NOTE: Leaving MAX_PORT_SIZE bytes to read for the first time because we are not sure
// how many bytes are in serial port buffer
read_with_timeout(MAX_PORT_SIZE, 3000);
if (m_bytes_readed > 0) { if (m_commandReceiveBuffer[m_bytes_readed - 1] == 'g') { if (send_command(SELECT_CHANNELS) == Success) { return true; } } }
disconnect();
return false;
}
CommandStatus SmartingAmp::command_status(const unsigned char* response, const int readed)
{
if (readed < 4) { return Failure; }
char* msg = new char[5];
msg[0] = response[readed - 4];
msg[1] = response[readed - 3];
msg[2] = response[readed - 2];
msg[3] = response[readed - 1];
msg[4] = '\0';
if (!strcmp(msg, ">OK<")) { return Success; }
// TODO: Implement timeout expired method
return Failure;
}
bool SmartingAmp::start()
{
send_command(ON);
m_samplesBuffer = std::queue<float*>();
m_failedSamples = 0;
m_receivedSamples = 0;
// start reading
acquire();
auto f = [this]() { this->m_io.run(); };
// auto f = boost::bind(&boost::asio::io_service::run, &m_io);
acquire_t.reset(new std::thread(f));
return true;
}
float* SmartingAmp::get_sample()
{
if (!m_samplesBuffer.empty())
{
m_samples_lock.lock();
float* sample = m_samplesBuffer.front();
m_samplesBuffer.pop();
m_samples_lock.unlock();
return sample;
}
return nullptr;
}
bool SmartingAmp::stop()
{
// NOTE: Stop would rarely return SUCCESS
off();
try
{
// cancel all async operations
m_io.stop();
m_io.reset();
// TODO: Implement proper stopping of the acquiring thread
}
catch (std::exception&)
{
// to do
}
// NOTE: Currently, I am very unhappy with flushing
// It slows down everything and after 6 play/stops open vibe is blocked
// flush();
//std::cout << "Failed " << m_failedSamples << std::endl;
//std::cout << "Received " << m_receivedSamples << std::endl;
return false;
}
float* SmartingAmp::convert_data(std::vector<unsigned char> in)
{
float* converted = new float[30];
// channel data
for (int i = 0; i < 24; ++i) { converted[i] = get_channel_value(in[3 * i + 1], in[3 * i + 2], in[3 * i + 3]); }
// gyroX
converted[24] = get_gyro_value(in[74], in[75]);
// gyroY
converted[25] = get_gyro_value(in[76], in[77]);
// gyroZ
converted[26] = get_gyro_value(in[78], in[79]);
// counter
converted[27] = float(in[73]);
// battery
converted[28] = float(in[80] & 0x7F);//add baterry data, removing highest bit, because it's used for impedance measurement
converted[29] = in[81]; //add checksum data
return converted;
}
float SmartingAmp::get_channel_value(const unsigned char first, const unsigned char second, const unsigned char third)
{
const int firstByte = int(first) & 0xff;
const int secondByte = int(second) & 0xff;
const int thirdByte = int(third) & 0xff;
int channelValue = (firstByte << 16) + (secondByte << 8) + thirdByte;
// Checking if value is positive or negative
if (channelValue > 0x007FFFFF) { channelValue = channelValue - 0x01000000; }
return float(channelValue) * float(CHANNEL_SCALAR_FACTOR);
}
float SmartingAmp::get_gyro_value(const unsigned char first, const unsigned char second)
{
const int firstByte = int(first) & 0xff;
const int secondByte = int(second) & 0xff;
int out = (firstByte << 8) + secondByte;
if (out > 0x00007FFF) { out = out - 0x00008000; }
else { out = out + 0x00008000; }
return float(out) * float(GYRO_SCALAR_FACTOR);
}
void SmartingAmp::read_with_timeout(const int size, const size_t timeout)
{
// Asynchronously read.
if (m_port.get() == nullptr || !m_port->is_open()) { return; }
m_bytes_readed = 0;
m_port->async_read_some(boost::asio::buffer(m_commandReceiveBuffer, size),
boost::bind(&SmartingAmp::read_complete, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
// Setup a deadline time to implement our timeout.
m_timer->expires_from_now(boost::posix_time::milliseconds(timeout));
m_timer->async_wait(boost::bind(&SmartingAmp::timeout_expired, this, boost::asio::placeholders::error));
// This will block until a character is read
// or until it is cancelled.
m_io.run();
m_io.stop();
// After a timeout & cancel it seems we need
// to do a reset for subsequent reads to work.
m_port->get_io_service().reset();
}
// Called when the timer's deadline expires.
void SmartingAmp::timeout_expired(const boost::system::error_code& /*error*/) { m_io.stop(); }
// Called when an async read completes or has been cancelled
void SmartingAmp::read_complete(const boost::system::error_code& error, const size_t bytesTransferred)
{
// IMPORTANT NOTE: when timer expires, and port is canceled, read complete is
// called for the last time. Its extremly important to cancel the timer even though
// it has already expired. So, here it goes
if (error) { return; }
m_bytes_readed = bytesTransferred;
m_timer->cancel();
}
void SmartingAmp::off()
{
const std::pair<unsigned char*, size_t> transformedCmd = make_command(OFF);
write(transformedCmd.first, transformedCmd.second);
}
@@ -0,0 +1,88 @@
#ifndef SMARTINGAMP_H
#define SMARTINGAMP_H
#ifndef _WIN32_WINNT // Minimum platform is Windows 7
#define _WIN32_WINNT 0x0601
#endif
#include <stdexcept>
#include <queue>
#include <string>
#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
#include <boost/system/error_code.hpp>
#include <boost/utility.hpp>
#include <memory> // shared_ptr
#include <mutex>
#include <thread>
#define OK_RESPONSE_SIZE 4
#define MAX_PACKAGE_SIZE 4096
#define MAX_PORT_SIZE 1000000
#define BOOST_ASIO_ENABLE_HANDLER_TRACKING
enum Command { SELECT_CHANNELS, ON, OFF, TEST, NORMAL, REFON, REFOFF, IMPON, IMPOFF, FREQUENCY_250, FREQUENCY_500, NOISE };
enum CommandStatus { Success, Failure, TimeoutExpired };
/**
* \class SmartingAmp
* \author mBrainTrain dev team (mBrainTrain)
* \brief Communicates with a MBTSmarting device.
*/
class SmartingAmp : boost::noncopyable
{
public:
SmartingAmp();
~SmartingAmp() {}
bool connect(std::string& portName);
void disconnect();
bool start();
bool stop();
/* NOTE: off doesn't wait for response */
void off();
CommandStatus send_command(Command cmd);
CommandStatus command_status(const unsigned char* response, int readed);
void write(const unsigned char* data, const size_t& size) const;
int read(unsigned char* data, size_t size) const;
void acquire();
void onReceive(const boost::system::error_code& ec, size_t bytesTransferred);
float* get_sample();
float* convert_data(std::vector<unsigned char> in);
void read_with_timeout(int size, size_t timeout);
void read_complete(const boost::system::error_code& error, size_t bytesTransferred);
void timeout_expired(const boost::system::error_code& error);
float get_channel_value(unsigned char first, unsigned char second, unsigned char third);
float get_gyro_value(unsigned char first, unsigned char second);
private:
std::pair<unsigned char*, int> make_command(Command command);
boost::asio::io_service m_io;
std::shared_ptr<boost::asio::serial_port> m_port;
unsigned char m_receiveBuffer[MAX_PACKAGE_SIZE];
unsigned char m_commandReceiveBuffer[MAX_PORT_SIZE];
std::queue<float*> m_samplesBuffer;
std::vector<unsigned char> m_byteArray;
std::mutex m_on_receive_lock;
std::mutex m_read_complete_lock;
std::mutex m_samples_lock;
std::mutex m_bytes_readed_lock;
std::mutex m_timer_expired_lock;
int m_failedSamples = 0;
int m_receivedSamples = 0;
std::shared_ptr<std::thread> acquire_t;
std::shared_ptr<boost::asio::deadline_timer> m_timer;
// read with timeout
int m_bytes_readed = 0;
};
#endif
@@ -0,0 +1,395 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkDialog" id="openvibe-acquisition-server-settings">
<property name="border_width">5</property>
<property name="title" translatable="yes">Device configuration</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox">
<property name="visible">True</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel" id="label_title">
<property name="visible">True</property>
<property name="label" translatable="yes">Mitsar EEG202</property>
<property name="justify">center</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox">
<property name="visible">True</property>
<property name="border_width">8</property>
<property name="spacing">8</property>
<child>
<object class="GtkHBox" id="hbox">
<property name="visible">True</property>
<property name="spacing">8</property>
<child>
<object class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="n_rows">6</property>
<property name="n_columns">2</property>
<child>
<object class="GtkComboBox" id="combobox_gender">
<property name="visible">True</property>
<property name="model">modelGender</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_age">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment2</property>
<property name="climb_rate">1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_identifier">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment1</property>
<property name="climb_rate">1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_age">
<property name="visible">True</property>
<property name="label" translatable="yes">Age :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_identifier">
<property name="visible">True</property>
<property name="label" translatable="yes">Identifier :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes">Gender :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="label" translatable="yes">Reference :</property>
<property name="justify">right</property>
</object>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_ref">
<property name="visible">True</property>
<property name="model">modelRef</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_number_of_channels">
<property name="visible">True</property>
<property name="label" translatable="yes">Number of channels :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_sampling_frequency">
<property name="visible">True</property>
<property name="label" translatable="yes">Sampling frequency :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_number_of_channels">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="invisible_char">&#x25CF;</property>
<property name="adjustment">adjustment3</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_sampling_frequency">
<property name="visible">True</property>
<property name="model">modelSamplingFreq</property>
<property name="button_sensitivity">off</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_change_channel_names">
<property name="label" translatable="yes">Change channel names</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_EventAndBioChannels">
<property name="label" translatable="yes">Event and Bio channels</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label-info">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;span size="smaller" foreground="#602020"&gt;All channels are acquired
The signal is sampled at 500 Hz&lt;/span&gt;</property>
<property name="use_markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">4</property>
</packing>
</child>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_apply">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-10">button_apply</action-widget>
<action-widget response="-6">button_cancel</action-widget>
</action-widgets>
</object>
<object class="GtkListStore" id="modelGender">
<columns>
<!-- column-name gchararray1 -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">unspecified</col>
</row>
<row>
<col id="0" translatable="yes">female</col>
</row>
<row>
<col id="0" translatable="yes">male</col>
</row>
<row>
<col id="0" translatable="yes">unknown</col>
</row>
</data>
</object>
<object class="GtkListStore" id="modelChan">
<columns>
<!-- column-name gchararray1 -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">31 EEG + 1 Event + 1 Bio</col>
</row>
<row>
<col id="0" translatable="yes">31 EEG + 1 Event + 1 Bio + 3 Synchro</col>
</row>
</data>
</object>
<object class="GtkListStore" id="modelRef">
<columns>
<!-- column-name gchararray1 -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Common (A1 et A2)</col>
</row>
<row>
<col id="0" translatable="yes">Left A1 - Right A2</col>
</row>
</data>
</object>
<object class="GtkAdjustment" id="adjustment1">
<property name="value">1025</property>
<property name="upper">65535</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkListStore" id="modelSamplingFreq">
<columns>
<!-- column-name gchararray1 -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">500</col>
</row>
</data>
</object>
<object class="GtkAdjustment" id="adjustment3">
<property name="lower">33</property>
<property name="upper">33</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
</interface>

Some files were not shown because too many files have changed in this diff Show More