/**
* 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 .
*/
#if defined TARGET_HAS_ThirdPartyUnicornLinux
#include "CDriverUnicornLinux.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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(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 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 lock(m_mutex);
while (m_ringBuffer.GetSize() < static_cast(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 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(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::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(*result);
}
inline std::ostream& operator<<(std::ostream& out, const std::vector& var)
{
for (size_t i = 0; i < var.size(); i++) { out << var[i] << " "; }
return out;
}
inline std::istream& operator>>(std::istream& in, std::vector& 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& 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& var)
{
// "Error not implemented operator >>!";
return in;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // TARGET_HAS_ThirdPartyUnicornLinux