init
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
+2529
File diff suppressed because it is too large
Load Diff
+120
@@ -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
|
||||
+545
@@ -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
|
||||
+65
@@ -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
|
||||
+351
@@ -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
|
||||
+78
@@ -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
|
||||
Reference in New Issue
Block a user