Hier eigene README einfügen | |||||
1. meta Inhalt in OpenViBe meta Ordner kopieren (git repository: https://gitlab.inria.fr/openvibe/meta.git) | |||||
2. libunicorn.so aus Unicorn Suite Hybrid Black git repository nach /usr/lib kopieren (git repository: https://github.com/unicorn-bi/Unicorn-Suite-Hybrid-Black.git) | |||||
3. libunicorn.so NACH bauen von OpenViBe nach meta/dist/Release/lib kopieren |
GET_PROPERTY(OV_PRINTED GLOBAL PROPERTY OV_TRIED_ThirdPartyGtecUnicornCAPI) | |||||
IF(WIN32) | |||||
FIND_PATH(PATH_UNICORN Unicorn.dll PATHS ${LIST_DEPENDENCIES_PATH} PATH_SUFFIXES sdk_gtec_unicorn NO_DEFAULT_PATH) | |||||
IF(PATH_UNICORN) | |||||
OV_PRINT(OV_PRINTED " Found Gtec Unicorn device API...") | |||||
INCLUDE_DIRECTORIES(${PATH_UNICORN}/) | |||||
TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${PATH_UNICORN}/unicorn.lib) | |||||
INSTALL(PROGRAMS "${PATH_UNICORN}/Unicorn.dll" DESTINATION ${DIST_BINDIR}) | |||||
ADD_DEFINITIONS(-DTARGET_HAS_ThirdPartyGtecUnicron) | |||||
ELSE(PATH_UNICORN) | |||||
OV_PRINT(OV_PRINTED " FAILED to find Gtec Unicorn device API (optional driver)") | |||||
ENDIF(PATH_UNICORN) | |||||
ENDIF(WIN32) | |||||
IF (UNIX) | |||||
SET(CMAKE_FIND_LIBRARY_SUFFIXES ".so") | |||||
FIND_LIBRARY(Unicorn_Linux_LIBRARY NAMES "unicorn" PATHS "/usr/lib" "/usr/local/lib") | |||||
IF(Unicorn_Linux_LIBRARY) | |||||
OV_PRINT(OV_PRINTED " Found Unicorn Linux C API...") | |||||
OV_PRINT(OV_PRINTED " [ OK ] Third party lib ${Unicorn_Linux_LIBRARY}") | |||||
ADD_DEFINITIONS(-DTARGET_HAS_ThirdPartyUnicornLinux) | |||||
TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${Unicorn_Linux_LIBRARY} ) | |||||
SET(OV_ThirdPartyUnicornLinux "YES") | |||||
ELSE() | |||||
OV_PRINT(OV_PRINTED " FAILED to find libunicorn Linux Unicorn API... (optional)") | |||||
ENDIF() | |||||
ENDIF(UNIX) | |||||
SET_PROPERTY(GLOBAL PROPERTY OV_TRIED_ThirdPartyGtecUnicornCAPI "Yes") | |||||
INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/extras/contrib/common") | |||||
SET(ADDITIONAL_PATH "${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-extensions/external-stimulations/") | |||||
INCLUDE_DIRECTORIES(${ADDITIONAL_PATH}) | |||||
FILE(GLOB_RECURSE ADDITIONAL_SRC_FILES ${ADDITIONAL_PATH}/*.cpp ${ADDITIONAL_PATH}/*.h) | |||||
SET(SRC_FILES "${SRC_FILES};${ADDITIONAL_SRC_FILES}") | |||||
SET(ADDITIONAL_PATH "${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-extensions/tcp-tagging/") | |||||
INCLUDE_DIRECTORIES(${ADDITIONAL_PATH}) | |||||
FILE(GLOB ADDITIONAL_SRC_FILES ${ADDITIONAL_PATH}/*.cpp ${ADDITIONAL_PATH}/*.h) | |||||
SET(SRC_FILES "${SRC_FILES};${ADDITIONAL_SRC_FILES}") | |||||
FUNCTION(OV_ADD_CONTRIB_DRIVER DRIVER_PATH) | |||||
SET(ADDITIONAL_PATH ${DRIVER_PATH}) | |||||
INCLUDE_DIRECTORIES(${ADDITIONAL_PATH}/src) | |||||
FILE(GLOB_RECURSE ADDITIONAL_SRC_FILES ${ADDITIONAL_PATH}/src/*.cpp ${ADDITIONAL_PATH}/src/*.h) | |||||
SET(SRC_FILES "${SRC_FILES};${ADDITIONAL_SRC_FILES}" PARENT_SCOPE) | |||||
#MESSAGE(STATUS "DO I EXIST: ${ADDITIONAL_PATH}/share/") | |||||
IF(EXISTS "${ADDITIONAL_PATH}/share/") | |||||
#MESSAGE(STATUS "I EXIST: ${ADDITIONAL_PATH}/share/") | |||||
INSTALL(DIRECTORY "${ADDITIONAL_PATH}/share/" DESTINATION "${DIST_DATADIR}/openvibe/applications/acquisition-server/") | |||||
ENDIF(EXISTS "${ADDITIONAL_PATH}/share/") | |||||
#MESSAGE(STATUS "DO I EXIST: ${ADDITIONAL_PATH}/bin/") | |||||
IF(EXISTS "${ADDITIONAL_PATH}/bin/") | |||||
#MESSAGE(STATUS "I EXIST: ${ADDITIONAL_PATH}/bin/") | |||||
INSTALL(DIRECTORY "${ADDITIONAL_PATH}/bin/" DESTINATION "${DIST_BINDIR}") | |||||
ENDIF(EXISTS "${ADDITIONAL_PATH}/bin/") | |||||
# Add the dir to be parsed for documentation later. | |||||
GET_PROPERTY(OV_TMP GLOBAL PROPERTY OV_PROP_CURRENT_PROJECTS) | |||||
SET(OV_TMP "${OV_TMP};${ADDITIONAL_PATH}") | |||||
SET_PROPERTY(GLOBAL PROPERTY OV_PROP_CURRENT_PROJECTS ${OV_TMP}) | |||||
ENDFUNCTION(OV_ADD_CONTRIB_DRIVER) | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/brainmaster-discovery") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/brainproducts-brainvisionrecorder") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/cognionics") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/ctfvsm-meg") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/encephalan") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/gtec-gipsa/common") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/gtec-gipsa/gusbamp") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/gtec-gipsa/unicorn") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/th-nuremberg/unicorn") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/gtec-bcilab") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/gtec-gmobilabplus") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/gtec-gusbamp") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/gtec-gnautilus") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/mbt-smarting") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/mitsarEEG202A") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/openal-mono16bit-audiocapture") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/openeeg-modulareeg") | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/openbci") | |||||
IF(WIN32 AND "${PLATFORM_TARGET}" STREQUAL "x64") | |||||
MESSAGE(STATUS " SKIPPED fieldtrip on x64") | |||||
ELSE() | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/field-trip-protocol") | |||||
ENDIF() | |||||
OV_ADD_CONTRIB_DRIVER("${CMAKE_SOURCE_DIR}/extras/contrib/plugins/server-drivers/eemagine-eego") | |||||
# The block is used to compile wrapper.cc into Acquisition Server which is not in OV git. | |||||
# nb. we need to add the wrapper.cc file before the cmake add_executable() directive, and at that | |||||
# point FindThirdPartyEemagineEEGO has not yet been run on some builds (e.g. win command line build), | |||||
# nor can we do the adding at that point; it'd be too late. On the other hand, the find script | |||||
# cannot be called before the executable has been added. | |||||
if (WIN32) | |||||
FIND_PATH(PATH_EEGOAPI amplifier.h PATHS ${LIST_DEPENDENCIES_PATH} PATH_SUFFIXES sdk_eemagine_eego/eemagine/sdk/) | |||||
else() | |||||
FIND_PATH(PATH_EEGOAPI amplifier.h PATHS /usr/include PATH_SUFFIXES eemagine/sdk/) | |||||
endif(WIN32) | |||||
IF(PATH_EEGOAPI) | |||||
SET(SRC_FILES "${SRC_FILES};${PATH_EEGOAPI}/wrapper.cc") | |||||
ENDIF(PATH_EEGOAPI) | |||||
IF(OV_COMPILE_TESTS) | |||||
ADD_SUBDIRECTORY("../../../contrib/plugins/server-extensions/tcp-tagging/test" "./test") | |||||
ENDIF(OV_COMPILE_TESTS) |
#pragma once | |||||
/* | |||||
#include "openeeg-modulareeg/src/ovasCDriverOpenEEGModularEEG.h" | |||||
#include "field-trip-protocol/src/ovasCDriverFieldtrip.h" | |||||
#include "brainproducts-brainvisionrecorder/src/ovasCDriverBrainProductsBrainVisionRecorder.h" | |||||
*/ | |||||
#include "ovasCPluginExternalStimulations.h" | |||||
#include "ovasCPluginTCPTagging.h" | |||||
#include "ovasCDriverBrainmasterDiscovery.h" | |||||
#include "ovasCDriverBrainProductsBrainVisionRecorder.h" | |||||
#include "ovasCDriverCognionics.h" | |||||
#include "ovasCDriverCtfVsmMeg.h" | |||||
#include "ovasCDriverEncephalan.h" | |||||
#include "CDriverGTecGUSBamp.hpp" | |||||
#include "ovasCDriverGTecGUSBampLegacy.h" | |||||
#include "ovasCDriverGTecGUSBampLinux.h" | |||||
#include "CDriverGTecUnicorn.hpp" | |||||
#include "CDriverUnicornLinux.hpp" | |||||
#include "ovasCDriverGTecGMobiLabPlus.h" | |||||
#include "ovasCDrivergNautilusInterface.h" | |||||
#include "ovasCDriverMBTSmarting.h" | |||||
#include "ovasCDriverMitsarEEG202A.h" | |||||
#include "ovasCDriverOpenALAudioCapture.h" | |||||
#include "ovasCDriverOpenEEGModularEEG.h" | |||||
#include "ovasCDriverOpenBCI.h" | |||||
#include "ovasCDriverEEGO.h" | |||||
#if !defined(WIN32) || defined(TARGET_PLATFORM_i386) | |||||
#include "ovasCDriverFieldtrip.h" | |||||
#endif | |||||
namespace OpenViBE { | |||||
namespace Contributions { | |||||
inline void InitiateContributions(AcquisitionServer::CAcquisitionServerGUI* pGUI, AcquisitionServer::CAcquisitionServer* pAS, | |||||
const Kernel::IKernelContext& context, std::vector<AcquisitionServer::IDriver*>* drivers) | |||||
{ | |||||
//No Limitations | |||||
drivers->push_back(new AcquisitionServer::CDriverBrainProductsBrainVisionRecorder(pAS->getDriverContext())); | |||||
drivers->push_back(new AcquisitionServer::CDriverCtfVsmMeg(pAS->getDriverContext())); | |||||
drivers->push_back(new AcquisitionServer::CDriverMBTSmarting(pAS->getDriverContext())); | |||||
drivers->push_back(new AcquisitionServer::CDriverOpenEEGModularEEG(pAS->getDriverContext())); | |||||
drivers->push_back(new AcquisitionServer::CDriverOpenBCI(pAS->getDriverContext())); | |||||
//OS Limitations | |||||
#if defined WIN32 | |||||
drivers->push_back(new AcquisitionServer::CDriverCognionics(pAS->getDriverContext())); | |||||
#endif | |||||
#if defined TARGET_OS_Windows | |||||
drivers->push_back(new AcquisitionServer::CDriverEncephalan(pAS->getDriverContext())); | |||||
#endif | |||||
#if defined TARGET_HAS_PThread | |||||
#if !defined(WIN32) || defined(TARGET_PLATFORM_i386) | |||||
drivers->push_back(new AcquisitionServer::CDriverFieldtrip(pAS->getDriverContext())); | |||||
#endif | |||||
#endif | |||||
//Commercial Limitations | |||||
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI | |||||
drivers->push_back(new AcquisitionServer::CDriverGTecGUSBamp(pAS->getDriverContext())); | |||||
drivers->push_back(new AcquisitionServer::CDriverGTecGUSBampLegacy(pAS->getDriverContext())); | |||||
#endif | |||||
#if defined TARGET_HAS_ThirdPartyGUSBampCAPI_Linux | |||||
drivers->push_back(new AcquisitionServer::CDriverGTecGUSBampLinux(pAS->getDriverContext())); | |||||
#endif | |||||
#if defined TARGET_HAS_ThirdPartyGtecUnicron | |||||
drivers->push_back(new AcquisitionServer::CDriverGTecUnicorn(pAS->getDriverContext())); | |||||
#endif | |||||
#if defined TARGET_HAS_ThirdPartyUnicornLinux | |||||
drivers->push_back(new AcquisitionServer::CDriverUnicornLinux(pAS->getDriverContext())); | |||||
#endif | |||||
#if defined TARGET_HAS_ThirdPartyGMobiLabPlusAPI | |||||
drivers->push_back(new AcquisitionServer::CDriverGTecGMobiLabPlus(pAS->getDriverContext())); | |||||
#endif | |||||
#if defined TARGET_HAS_ThirdPartyGNEEDaccessAPI | |||||
drivers->push_back(new AcquisitionServer::CDrivergNautilusInterface(pAS->getDriverContext())); | |||||
#endif | |||||
#if defined TARGET_HAS_ThirdPartyBrainmasterCodeMakerAPI | |||||
drivers->push_back(new AcquisitionServer::CDriverBrainmasterDiscovery(pAS->getDriverContext())); | |||||
#endif | |||||
#if defined(TARGET_HAS_ThirdPartyMitsar) | |||||
drivers->push_back(new AcquisitionServer::CDriverMitsarEEG202A(pAS->getDriverContext())); | |||||
#endif | |||||
#if defined TARGET_HAS_ThirdPartyOpenAL | |||||
drivers->push_back(new AcquisitionServer::CDriverOpenALAudioCapture(pAS->getDriverContext())); | |||||
#endif | |||||
#if defined(TARGET_HAS_ThirdPartyEEGOAPI) | |||||
drivers->push_back(new AcquisitionServer::CDriverEEGO(pAS->getDriverContext())); | |||||
#endif | |||||
pGUI->registerPlugin(new AcquisitionServer::Plugins::CPluginExternalStimulations(context)); | |||||
pGUI->registerPlugin(new AcquisitionServer::Plugins::CPluginTCPTagging(context)); | |||||
} | |||||
} // namespace Contributions | |||||
} // namespace OpenViBE |
INCLUDE("FindThirdPartyBrainmasterCodeMakerAPI") | |||||
INCLUDE("FindThirdPartyEemagineEEGO") | |||||
INCLUDE("FindThirdPartyGMobiLabPlusAPI") | |||||
INCLUDE("FindThirdPartyGUSBampCAPI") | |||||
INCLUDE("FindThirdPartyMitsar") | |||||
INCLUDE("FindThirdPartyGNEEDaccessAPI") | |||||
INCLUDE("FindThirdPartyGtecUnicornCAPI") |
/** | |||||
* Software License Agreement (AGPL-3 License) | |||||
* | |||||
* \file CDriverUnicornLinux.cpp | |||||
* \author Igor Beloschapkin, TH-Nuremberg, BCI team | |||||
* \date 17/04/2022 | |||||
* | |||||
* 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_ThirdPartyUnicornLinux | |||||
#include "CDriverUnicornLinux.hpp" | |||||
#include <toolkit/ovtk_all.h> | |||||
#include <system/ovCTime.h> | |||||
#include <cmath> | |||||
#include <cstring> | |||||
#include <cstdlib> | |||||
#include <cstdio> | |||||
#include <limits> | |||||
#include <functional> | |||||
#include <mutex> | |||||
#include <thread> | |||||
#include "unicorn.h" | |||||
namespace OpenViBE { | |||||
namespace AcquisitionServer { | |||||
CDriverUnicornLinux::CDriverUnicornLinux(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 CDriverUnicornLinux::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 CDriverUnicornLinux::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 CDriverUnicornLinux::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 CDriverUnicornLinux::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(&CDriverUnicornLinux::acquire, this))); | |||||
return true; | |||||
} | |||||
// This method is called by the AS and it supplies the acquired data to the AS | |||||
bool CDriverUnicornLinux::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 CDriverUnicornLinux::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 CDriverUnicornLinux::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 CDriverUnicornLinux::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 CDriverUnicornLinux::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_ThirdPartyUnicornLinux |
/** | |||||
* Software License Agreement (AGPL-3 License) | |||||
* | |||||
* \file CDriverUnicornLinux.cpp | |||||
* \author Igor Beloschapkin, TH-Nuremberg, BCI team | |||||
* \date 17/04/2022 | |||||
* | |||||
* 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_ThirdPartyUnicornLinux | |||||
#include "ovasIDriver.h" | |||||
#include "../ovasCHeader.h" | |||||
#include "../ovasCSettingsHelper.h" | |||||
#include "../ovasCSettingsHelperOperators.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 CDriverUnicornLinux : public OpenViBE::AcquisitionServer::IDriver | |||||
{ | |||||
public: | |||||
CDriverUnicornLinux(OpenViBE::AcquisitionServer::IDriverContext& rDriverContext); | |||||
void release() { delete this; }; | |||||
const char* getName() override { return "Unicorn Linux"; }; | |||||
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_ThirdPartyUnicornLinux |
//CRingBuffer provided by g.tec | |||||
//website: http://www.gtec.at | |||||
//redistribution of this file has been officially granted by g.tec | |||||
//Linux port by Igor Beloschapkin, Technische Hochschule Nürnberg, 23.04.2022 | |||||
#pragma once | |||||
#include <algorithm> // std::min | |||||
#include <sys/mman.h> | |||||
#include <stdlib.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) | |||||
{ | |||||
msync(_buffer, GetSize(), MS_SYNC); | |||||
munmap(_buffer, GetSize()); | |||||
} | |||||
_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) | |||||
{ | |||||
msync(_buffer, GetSize(), MS_SYNC); | |||||
munmap(_buffer, GetSize()); | |||||
_buffer = nullptr; | |||||
} | |||||
if (capacity > 0) | |||||
{ | |||||
//allocate memory for the buffer | |||||
_buffer = static_cast<T*>(mmap(NULL, capacity * sizeof(T), (PROT_READ | PROT_WRITE), (MAP_SHARED | MAP_ANON), -1, 0)); | |||||
msync(_buffer, capacity * sizeof(T), (MS_SYNC | MS_INVALIDATE)); | |||||
//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 | |||||
memcpy(&_buffer[_end], source, std::min(firstPartCapacity, length) * sizeof(T)); | |||||
//if a second part exists, copy second part | |||||
if (length > firstPartCapacity) | |||||
{ | |||||
memcpy(&_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 | |||||
memcpy(destination, &_buffer[_start], firstPartSize * sizeof(T)); | |||||
//if a second part exists, copy second part | |||||
if (secondPartSize > 0) {memcpy(&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; | |||||
}; |
#ifndef UNICORN_H | |||||
#define UNICORN_H | |||||
#include <stdint.h> | |||||
#include <stdbool.h> | |||||
// The following ifdef block is the standard way of creating macros which make exporting | |||||
// from a DLL simpler. All files within this DLL are compiled with the UNICORN_EXPORTS | |||||
// symbol defined on the command line. This symbol should not be defined on any project | |||||
// that uses this DLL. This way any other project whose source files include this file see | |||||
// UNICORN_API functions as being imported from a DLL, whereas this DLL sees symbols | |||||
// defined with this macro as being exported. | |||||
#if defined _WIN32 || defined __CYGWIN__ | |||||
#if defined _MSC_VER //Windows && MS Visual C | |||||
#ifdef UNICORN_EXPORTS | |||||
#define UNICORN_API __declspec(dllexport) | |||||
#else | |||||
#define UNICORN_API __declspec(dllimport) | |||||
#endif | |||||
#elif defined __GNUC__ //Windows && GNU C compiler | |||||
#ifdef UNICORN_EXPORTS | |||||
#define UNICORN_API __attribute__((dllexport)) | |||||
#else | |||||
#define UNICORN_API __attribute__((dllexport)) | |||||
#endif | |||||
#endif | |||||
#elif defined __linux__ || __APPLE__ | |||||
#if __GNUC__ >= 4 //Linux && GNU C compiler version 4.0 and higher | |||||
#ifdef UNICORN_EXPORTS | |||||
#define UNICORN_API __attribute__((visibility("default"))) | |||||
#else | |||||
#define UNICORN_API __attribute__((visibility("default"))) | |||||
#endif | |||||
#else | |||||
#define UNICORN_API | |||||
#endif | |||||
#endif | |||||
#ifdef __cplusplus | |||||
extern "C" { | |||||
#endif | |||||
// ======================================================================================== | |||||
// Type definitions | |||||
// ======================================================================================== | |||||
#ifndef NULL | |||||
//! The null pointer. | |||||
#define NULL 0 | |||||
#endif | |||||
#ifndef BOOL | |||||
//! The boolean data type, whose values can be \ref TRUE or \ref FALSE. | |||||
typedef int32_t BOOL; | |||||
#endif | |||||
#ifndef FALSE | |||||
//! The \ref FALSE value for the \ref BOOL type. | |||||
#define FALSE 0 | |||||
#endif | |||||
#ifndef TRUE | |||||
//! The \ref TRUE value for the \ref BOOL type. | |||||
#define TRUE 1 | |||||
#endif | |||||
// ======================================================================================== | |||||
// Error Codes | |||||
// ======================================================================================== | |||||
//! The operation completed successfully. No error occurred. | |||||
#define UNICORN_ERROR_SUCCESS 0 | |||||
//! One of the specified parameters does not contain a valid value. | |||||
#define UNICORN_ERROR_INVALID_PARAMETER 1 | |||||
//! The initialization of the Bluetooth adapter failed. | |||||
#define UNICORN_ERROR_BLUETOOTH_INIT_FAILED 2 | |||||
//! The operation could not be performed because the Bluetooth socket failed. | |||||
#define UNICORN_ERROR_BLUETOOTH_SOCKET_FAILED 3 | |||||
//! The device could not be opened. | |||||
#define UNICORN_ERROR_OPEN_DEVICE_FAILED 4 | |||||
//! The configuration is invalid. | |||||
#define UNICORN_ERROR_INVALID_CONFIGURATION 5 | |||||
//! The acquisition buffer is full. | |||||
#define UNICORN_ERROR_BUFFER_OVERFLOW 6 | |||||
//! The acquisition buffer is empty. | |||||
#define UNICORN_ERROR_BUFFER_UNDERFLOW 7 | |||||
//! The operation is not allowed. | |||||
#define UNICORN_ERROR_OPERATION_NOT_ALLOWED 8 | |||||
//! The operation could not complete because of connection problems. | |||||
#define UNICORN_ERROR_CONNECTION_PROBLEM 9 | |||||
//! The device is not supported with this API (\ref UNICORN_SUPPORTED_DEVICE_VERSION) | |||||
#define UNICORN_ERROR_UNSUPPORTED_DEVICE 10 | |||||
//! The specified connection handle is invalid. | |||||
#define UNICORN_ERROR_INVALID_HANDLE 0xFFFFFFFE | |||||
//! An unspecified error occurred. | |||||
#define UNICORN_ERROR_GENERAL_ERROR 0xFFFFFFFF | |||||
// ======================================================================================== | |||||
// Amplifier Properties | |||||
// ======================================================================================== | |||||
//! The maximum length of the serial number. | |||||
#define UNICORN_SERIAL_LENGTH_MAX 14 | |||||
//! The maximum length of the device version. | |||||
#define UNICORN_DEVICE_VERSION_LENGTH_MAX 6 | |||||
//! The maximum length of the firmware version. | |||||
#define UNICORN_FIRMWARE_VERSION_LENGTH_MAX 12 | |||||
//! The sampling rate of the amplifier. | |||||
#define UNICORN_SAMPLING_RATE 250 | |||||
//! The maximum string length. | |||||
#define UNICORN_STRING_LENGTH_MAX 255 | |||||
//! The number of available EEG channels. | |||||
#define UNICORN_EEG_CHANNELS_COUNT 8 | |||||
//! The number of available accelerometer channels. | |||||
#define UNICORN_ACCELEROMETER_CHANNELS_COUNT 3 | |||||
//! The number of available gyroscope channels. | |||||
#define UNICORN_GYROSCOPE_CHANNELS_COUNT 3 | |||||
//! The total number of available channels. | |||||
#define UNICORN_TOTAL_CHANNELS_COUNT 17 | |||||
//! Index of the first EEG \ref UNICORN_AMPLIFIER_CHANNEL in the \ref UNICORN_AMPLIFIER_CONFIGURATION Channels array. | |||||
#define UNICORN_EEG_CONFIG_INDEX 0 | |||||
//! Index of the first Accelerometer \ref UNICORN_AMPLIFIER_CHANNEL in the \ref UNICORN_AMPLIFIER_CONFIGURATION Channels array. | |||||
#define UNICORN_ACCELEROMETER_CONFIG_INDEX 8 | |||||
//! Index of the first gyroscope \ref UNICORN_AMPLIFIER_CHANNEL in the \ref UNICORN_AMPLIFIER_CONFIGURATION Channels array. | |||||
#define UNICORN_GYROSCOPE_CONFIG_INDEX 11 | |||||
//! Index of the battery level \ref UNICORN_AMPLIFIER_CHANNEL in the \ref UNICORN_AMPLIFIER_CONFIGURATION Channels array. | |||||
#define UNICORN_BATTERY_CONFIG_INDEX 14 | |||||
//! Index of the counter \ref UNICORN_AMPLIFIER_CHANNEL in the \ref UNICORN_AMPLIFIER_CONFIGURATION Channels array. | |||||
#define UNICORN_COUNTER_CONFIG_INDEX 15 | |||||
//! Index of the validation indicator \ref UNICORN_AMPLIFIER_CHANNEL in the \ref UNICORN_AMPLIFIER_CONFIGURATION Channels array. | |||||
#define UNICORN_VALIDATION_CONFIG_INDEX 16 | |||||
//! The number of digital output channels. | |||||
#define UNICORN_NUMBER_OF_DIGITAL_OUTPUTS 8 | |||||
// ======================================================================================== | |||||
// Type definitions | |||||
// ======================================================================================== | |||||
//! Type that holds the handle associated with a device. | |||||
typedef uint64_t UNICORN_HANDLE; | |||||
//! Type that holds device serial. | |||||
typedef char UNICORN_DEVICE_SERIAL[UNICORN_SERIAL_LENGTH_MAX]; | |||||
//! Type that holds device version. | |||||
typedef char UNICORN_DEVICE_VERSION[UNICORN_DEVICE_VERSION_LENGTH_MAX]; | |||||
//! Type that holds firmware version. | |||||
typedef char UNICORN_FIRMWARE_VERSION[UNICORN_FIRMWARE_VERSION_LENGTH_MAX]; | |||||
// ======================================================================================== | |||||
// Structures | |||||
// ======================================================================================== | |||||
//! The type containing information about a single channel of the amplifier. | |||||
typedef struct _UNICORN_AMPLIFIER_CHANNEL | |||||
{ | |||||
//! The channel name. | |||||
char name[32]; | |||||
//! The channel unit. | |||||
char unit[32]; | |||||
//! The channel range as float array. First entry min value; Second max value. | |||||
float range[2]; | |||||
//! The channel enabled flag. \ref TRUE to enable channel; \ref FALSE to disable channel. | |||||
BOOL enabled; | |||||
} UNICORN_AMPLIFIER_CHANNEL; | |||||
//! The type containing an amplifier configuration. | |||||
typedef struct _UNICORN_AMPLIFIER_CONFIGURATION | |||||
{ | |||||
//! The array holding a configuration for each available \ref UNICORN_AMPLIFIER_CHANNEL. | |||||
UNICORN_AMPLIFIER_CHANNEL Channels[UNICORN_TOTAL_CHANNELS_COUNT]; | |||||
} UNICORN_AMPLIFIER_CONFIGURATION; | |||||
//! Type that holds additional information about the device. | |||||
typedef struct _UNICORN_DEVICE_INFORMATION | |||||
{ | |||||
//! The number of EEG channels. | |||||
uint16_t numberOfEegChannels; | |||||
//! The serial number of the device. | |||||
UNICORN_DEVICE_SERIAL serial; | |||||
//! The firmware version number. | |||||
UNICORN_FIRMWARE_VERSION firmwareVersion; | |||||
//!The device version number. | |||||
UNICORN_DEVICE_VERSION deviceVersion; | |||||
//! The PCB version number. | |||||
uint8_t pcbVersion[4]; | |||||
//! The enclosure version number. | |||||
uint8_t enclosureVersion[4]; | |||||
} UNICORN_DEVICE_INFORMATION; | |||||
// ======================================================================================== | |||||
// API Methods | |||||
// ======================================================================================== | |||||
//! Returns the current API version. | |||||
/*! | |||||
\return The current API version. | |||||
*/ | |||||
UNICORN_API float UNICORN_GetApiVersion(); | |||||
//! Returns the last error text. | |||||
/*! | |||||
\return The last error text. | |||||
*/ | |||||
UNICORN_API const char *UNICORN_GetLastErrorText(); | |||||
//! Scans for available devices. | |||||
/*! | |||||
Discovers available paired or unpaired devices. Estimates the number of available paired or unpaired devices and returns information about discovered devices. | |||||
\param availableDevices A pointer to the beginning of an array of | |||||
\ref UNICORN_DEVICE_SERIAL, which receives available | |||||
devices when the method returns. If NULL is passed, the | |||||
number of available devices is returned only to determine | |||||
the amount of memory to allocate. | |||||
\param availableDevicesCount A pointer to a variable that receives the number of | |||||
available devices. | |||||
\param rescan A flag determining whether a full device scan should be performed or not. | |||||
A quick-scan returns the result of the last scan an is faster than a rescan. | |||||
A rescan lasts about 10 seconds. | |||||
\ref TRUE to perform a rescan. | |||||
\ref FALSE to perform a quick-scan. | |||||
\return An error code is returned as integer if scanning for available devices fails. | |||||
*/ | |||||
UNICORN_API int UNICORN_GetAvailableDevices(UNICORN_DEVICE_SERIAL* availableDevices, uint32_t* availableDevicesCount, BOOL rescan); | |||||
//! Opens a device. | |||||
/*! | |||||
Connects to a certain Unicorn device and assigns a Unicorn handle if the connection attempt succeeded. | |||||
\param serial The serial number of the device to connect to. | |||||
\param hDevice A pointer to a \ref UNICORN_HANDLE that receives the handle associated with the current session if the device could be opened successfully. | |||||
\return An error code is returned as integer if the device could not be opened. | |||||
*/ | |||||
UNICORN_API int UNICORN_OpenDevice(const char* serial, UNICORN_HANDLE *hDevice); | |||||
//! Closes a device. | |||||
/*! | |||||
Disconnects from a device by a given session handle. | |||||
\param hDevice A pointer to the handle associated with the session. | |||||
\return An error code is returned as integer if the disconnection attempt fails. | |||||
*/ | |||||
UNICORN_API int UNICORN_CloseDevice(UNICORN_HANDLE *hDevice); | |||||
//! Initiates a data acquisition in testsignal or measurement mode. | |||||
/*! | |||||
Starts data acquisition in test signal or measurement mode. | |||||
\param hDevice The \ref UNICORN_HANDLE associated with the session. | |||||
\param testSignalEnabled Enables or disables the test signal mode. \ref TRUE to start the data | |||||
acquisition in test signal mode; \ref FALSE to start the data | |||||
acquisition in measurement mode. | |||||
\return An error code is returned as integer if the data acquisition could not be started. | |||||
*/ | |||||
UNICORN_API int UNICORN_StartAcquisition(UNICORN_HANDLE hDevice, BOOL testSignalEnabled); | |||||
//! Terminates a running data acquisition. | |||||
/*! | |||||
Stops a currently running data acquisition session. | |||||
\param hDevice The \ref UNICORN_HANDLE associated with the session. | |||||
\return An error code is returned as integer if the acquisition could not be terminated. | |||||
*/ | |||||
UNICORN_API int UNICORN_StopAcquisition(UNICORN_HANDLE hDevice); | |||||
//! Sets an amplifier configuration. | |||||
/*! | |||||
Sets an amplifier configuration. | |||||
\param hDevice The \ref UNICORN_HANDLE associated with the session. | |||||
\param configuration A pointer to \ref UNICORN_AMPLIFIER_CONFIGURATION to set. | |||||
\return An error code is returned as integer if configuration is invalid or could not be set. | |||||
*/ | |||||
UNICORN_API int UNICORN_SetConfiguration(UNICORN_HANDLE hDevice, UNICORN_AMPLIFIER_CONFIGURATION *configuration); | |||||
//! Gets the amplifier configuration. | |||||
/*! | |||||
Retrieves the current amplifier configuration from the device as \ref UNICORN_AMPLIFIER_CONFIGURATION. | |||||
\param hDevice The \ref UNICORN_HANDLE associated with the session. | |||||
\param configuration A pointer to a \ref UNICORN_AMPLIFIER_CONFIGURATION which stores the configuration of the amplifier. | |||||
\return An error code is returned as integer if configuration could not be read. | |||||
*/ | |||||
UNICORN_API int UNICORN_GetConfiguration(UNICORN_HANDLE hDevice, UNICORN_AMPLIFIER_CONFIGURATION* configuration); | |||||
//! Reads specific number of scans to a specified destination buffer with known length. | |||||
/*! | |||||
Reads a specific number of scans into the specified destination buffer of known length. Checks | |||||
whether the destination buffer is big enough to hold the requested number of scans. | |||||
\param hDevice The \ref UNICORN_HANDLE associated with the session. | |||||
\param numberOfScans The number of scans to read. The number of scans must be | |||||
greater than zero. A scan consists of one 32-bit floating point | |||||
number for each currently acquired channel. | |||||
\param destinationBuffer A pointer to the destination buffer that receives the acquired data. The destination buffer must | |||||
provide enough memory to hold the requested number of scans multiplied by the number of acquired channels. | |||||
Call \ref UNICORN_GetNumberOfAcquiredChannels to determine the number of acquired channels. | |||||
Call \ref UNICORN_GetChannelIndex to determine the index of a channel within a scan. | |||||
Example: | |||||
The sample of the battery level channel in the n-th scan is: | |||||
n* \ref UNICORN_GetNumberOfAcquiredChannels()+ \ref UNICORN_GetChannelIndex(\93Battery Level\94) | |||||
\param destinationBufferLength Number of floats fitting into destination buffer. | |||||
\return An error code is returned as integer if data could not be read. | |||||
*/ | |||||
UNICORN_API int UNICORN_GetData(UNICORN_HANDLE hDevice, uint32_t numberOfScans, float* destinationBuffer, uint32_t destinationBufferLength); | |||||
//! Determines number of acquired channels. | |||||
/*! | |||||
Uses the currently set \ref UNICORN_AMPLIFIER_CONFIGURATION to get the number of acquired channels. | |||||
\param hDevice The \ref UNICORN_HANDLE associated with the session. | |||||
\param numberOfAcquiredChannels A pointer to a varialbe that receives the number of acquired channels. | |||||
\return An error code is returned as integer if the number of acquired channels could not be determined. | |||||
*/ | |||||
UNICORN_API int UNICORN_GetNumberOfAcquiredChannels(UNICORN_HANDLE hDevice, uint32_t* numberOfAcquiredChannels); | |||||
//! Determines the index of the requested channel in an acquired scan. | |||||
/*! | |||||
Uses the currently set \ref UNICORN_AMPLIFIER_CONFIGURATION to get the index of the requested channel within an acquired scan. | |||||
\param hDevice The \ref UNICORN_HANDLE associated with the session. | |||||
\param name The name of the requested channel. | |||||
\param channelIndex A pointer to a variable that receives the channel index. | |||||
The default names are: | |||||
EEG 1|2|3|4|5|6|7|8 | |||||
Accelerometer X|Y|Z | |||||
Gyroscope X|Y|Z | |||||
Counter | |||||
Battery Level | |||||
Validation Indicator | |||||
\return An error code is returned as integer if the index could not be determined. | |||||
*/ | |||||
UNICORN_API int UNICORN_GetChannelIndex(UNICORN_HANDLE hDevice, const char *name, uint32_t* channelIndex); | |||||
//! Reads the device information. | |||||
/*! | |||||
Reads the device information by a given \ref UNICORN_HANDLE. | |||||
\param hDevice The \ref UNICORN_HANDLE associated with the session. | |||||
\param deviceInformation A pointer to a \ref UNICORN_DEVICE_INFORMATION that receives information about the device. | |||||
\return An error code is returned as integer if the device information could not be read. | |||||
*/ | |||||
UNICORN_API int UNICORN_GetDeviceInformation(UNICORN_HANDLE hDevice, UNICORN_DEVICE_INFORMATION* deviceInformation); | |||||
//! Sets the digital outputs. | |||||
/*! | |||||
Sets the digital outputs to high or low. | |||||
\param hDevice The \ref UNICORN_HANDLE associated with the session. | |||||
\param digitalOutputs A pointer to a variable that receives the states of the digital | |||||
output channels. Each bit represents one digital output channel. | |||||
If a bit is set, the corresponding digital output channel\92s value is | |||||
set to high. If a bit is cleared, the corresponding digital output | |||||
channel\92s value is set to low. | |||||
Examples (the binary representation of each decimal value is | |||||
shown in parentheses): | |||||
0 (0b00000000) -> all digital outputs set to low. | |||||
170 (0b10101010) -> digital outputs 2,4,6,8 are set to high. | |||||
255 (0b11111111) -> all digital outputs set to high. | |||||
\return An error code is returned as integer if the status of the digital output pin could not be set. | |||||
*/ | |||||
UNICORN_API int UNICORN_SetDigitalOutputs(UNICORN_HANDLE hDevice, uint8_t digitalOutputs); | |||||
//! Reads the digital output states. | |||||
/*! | |||||
Reads the digital output states. | |||||
\param hDevice The \ref UNICORN_HANDLE associated with the session. | |||||
\param digitalOutputs The state of the digital output channels to set in bits. Each bit | |||||
represents one digital output channel. Set a bit to set the | |||||
corresponding digital output channel\92s value to high. Clear a bit to | |||||
set the corresponding digital output channel\92s value to low. | |||||
Examples (the binary representation of each decimal value is | |||||
shown in parentheses): | |||||
0 (0b00000000) -> all digital outputs set to low. | |||||
170 (0b10101010) -> digital outputs 2,4,6,8 are set to high. | |||||
255 (0b11111111) -> all digital outputs set to high. | |||||
\return An error code is returned as integer if the status of the digital output pin could not be read. | |||||
*/ | |||||
UNICORN_API int UNICORN_GetDigitalOutputs(UNICORN_HANDLE hDevice, uint8_t* digitalOutputs); | |||||
#ifdef __cplusplus | |||||
} | |||||
#endif | |||||
#endif //UNICORN_H |