123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 |
- /**
- * 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
|