init
This commit is contained in:
+1099
File diff suppressed because it is too large
Load Diff
+165
@@ -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
|
||||
+51
@@ -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
|
||||
+471
@@ -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
|
||||
+99
@@ -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
|
||||
+94
@@ -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
|
||||
+63
@@ -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
|
||||
Reference in New Issue
Block a user