init
This commit is contained in:
+709
@@ -0,0 +1,709 @@
|
||||
/*
|
||||
* mCBioSemiActiveTwoBridge.cpp
|
||||
*
|
||||
* Copyright (c) 2012, Mensia Technologies SA. All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
This code implements the procedure described her:
|
||||
http://www.biosemi.com/faq/make_own_acquisition_software.htm
|
||||
*/
|
||||
|
||||
#ifdef TARGET_HAS_ThirdPartyBioSemiAPI
|
||||
#include "mCBridgeBioSemiActiveTwo.h"
|
||||
|
||||
#include <iostream>
|
||||
//debug
|
||||
#include <fstream>
|
||||
|
||||
#ifdef WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#else
|
||||
// linux, mac
|
||||
#include <unistd.h>
|
||||
#endif // WIN32
|
||||
|
||||
#include <labview_dll.h>
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
#ifdef _DEBUG
|
||||
#define __BioSemiBridgeLogConsole__(msg, ...) printf(msg, __VA_ARGS__)
|
||||
#else
|
||||
#define __BioSemiBridgeLogConsole__(msg, ...)
|
||||
#endif
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
// Ring buffer must be large enough to hold at least a complete sample round
|
||||
// otherwise we wont be able to detect the length between 2 sync bytes and deduce channel count.
|
||||
// test gives 131072 bytes (32768 long) per read
|
||||
// it needs to be a multiple of 512
|
||||
#define BIOSEMI_ACTIVETWO_RINGBUFFERLONGCOUNT (32768*512)
|
||||
#define BIOSEMI_ACTIVETWO_RINGBUFFERBYTES (BIOSEMI_ACTIVETWO_RINGBUFFERLONGCOUNT*sizeof(int)) //67108864 bytes
|
||||
|
||||
// The control buffer is sent through USB_WRITE. It is a 64 bytes buffer.
|
||||
// First byte controls the start/stop filling of ring buffer; if Odd, it starts.
|
||||
// 2nd and 3rd byte are used for outgoing triggers.
|
||||
// byte 4-64 unused.
|
||||
#define BIOSEMI_ACTIVETWO_CONTROLBUFFERBYTES 64
|
||||
|
||||
// SYNC bytes pads the stream that sends a fixed number of bytes per read() independently of the channel count.
|
||||
#define BIOSEMI_ACTIVETWO_SYNCBYTES 0xffffff00
|
||||
|
||||
// the device can be configured by changing the speedmode "button" in front of the amplifier 0-9
|
||||
#define BIOSEMI_ACTIVETWO_SPEEDMODECOUNT 10
|
||||
|
||||
namespace BioSemi {
|
||||
/*
|
||||
AD-box model MK1
|
||||
|
||||
AD-box switch | Sample-rate | Pin channels + EX channels + Sensor channels
|
||||
***************|*****************|*********************************************
|
||||
0 | 2048 (2 kHz) | 256+0+0
|
||||
1 | 4096 (4 kHz) | 128+0+0
|
||||
2 | 8192 (8 kHz) | 64+0+0
|
||||
3 | 16384 (16 kHz) | 32+0+0
|
||||
4 | 2048 (2 kHz) | 232+8+7
|
||||
5 | 4096 (4 kHz) | 104+8+7
|
||||
6 | 8192 (8 kHz) | 40+8+7
|
||||
7 | 16384 (16 kHz) | 8+8+7
|
||||
8 (AIB-mode) | 2048 (2 kHz) | AIB-mode
|
||||
9 | Reserved | Reserved
|
||||
*/
|
||||
static uint32_t activeTwoMarkISpeedModeFrequency[BIOSEMI_ACTIVETWO_SPEEDMODECOUNT] = { 2048, 4096, 8192, 16384, 2048, 4096, 8192, 16384, 2048, 0 };
|
||||
|
||||
/*
|
||||
AD-box model MK2
|
||||
|
||||
AD-box switch | Sample-rate | Pin channels + EX channels + Sensor channels
|
||||
***************|*****************|*********************************************
|
||||
0 | 2048 (2 kHz) | >> Daisy-chain mode:
|
||||
1 | 4096 (4 kHz) | >> In speedmode 0 to 3, the AD-boxes work as up to 4 optical fiber 'daisy chained' boxes,
|
||||
2 | 8192 (8 kHz) | >> each with a maximum of 128+8 channels+sensors @ 2 kHz, speedmode switch = box number. (0=Box1, 1=Box2, 2=Box3, 3=Box4).
|
||||
3 | 16384 (16 kHz) | >> Daisy chain possibilities are not standard included in the base system price
|
||||
4 | 2048 (2 kHz) | 256+8+7
|
||||
5 | 4096 (4 kHz) | 128+8+7
|
||||
6 | 8192 (8 kHz) | 64+8+7
|
||||
7 | 16384 (16 kHz) | 32+8+7
|
||||
8 (AIB-mode) | 2048 (2 kHz) | 256+8+7+32AIB
|
||||
9 (ABR-mode) | 16384 (16 kHz) | 5
|
||||
*/
|
||||
static uint32_t activeTwoMarkIISpeedModeFrequency[BIOSEMI_ACTIVETWO_SPEEDMODECOUNT] = { 2048, 4096, 8192, 16384, 2048, 4096, 8192, 16384, 2048, 16384 };
|
||||
|
||||
static uint32_t* activeTwoSpeedModeFrequency[2] = { activeTwoMarkISpeedModeFrequency, activeTwoMarkIISpeedModeFrequency };
|
||||
|
||||
/*
|
||||
The ActiveTwo sends the following number of 32-bit words per sample:
|
||||
|
||||
Mk1:
|
||||
Speedmode 0 and 4: 258
|
||||
Speedmode 1 and 5: 130
|
||||
Speedmode 2 and 6: 66
|
||||
Speedmode 3 and 7: 34
|
||||
Speedmode 8: 290 (2+256+32)
|
||||
*/
|
||||
static uint32_t activeTwoMarkILongPerSample[BIOSEMI_ACTIVETWO_SPEEDMODECOUNT] = { 258, 130, 66, 34, 258, 130, 66, 34, 290, 0 };
|
||||
|
||||
/*
|
||||
Mk2:
|
||||
Speedmode 0, 1, 2 and 3: 610 (2+4*152)
|
||||
Speedmode 4: 282
|
||||
Speedmode 5: 154
|
||||
Speedmode 6: 90
|
||||
Speedmode 7: 58
|
||||
Speedmode 8: 314 (2+280+32)
|
||||
*/
|
||||
static uint32_t activeTwoMarkIILongPerSample[BIOSEMI_ACTIVETWO_SPEEDMODECOUNT] = { 610, 610, 610, 610, 282, 154, 90, 58, 314, 0 };
|
||||
|
||||
static uint32_t* activeTwoMarkLongPerSample[2] = { activeTwoMarkILongPerSample, activeTwoMarkIILongPerSample };
|
||||
/*
|
||||
The ActiveTwo sends the following number of 32-bit words per sample:
|
||||
|
||||
Mk1:
|
||||
Speedmode 0 and 4: 258
|
||||
Speedmode 1 and 5: 130
|
||||
Speedmode 2 and 6: 66
|
||||
Speedmode 3 and 7: 34
|
||||
Speedmode 8: 290 (2+256+32)
|
||||
*/
|
||||
static uint32_t activeTwoMarkIElectrodeChannelPerSample[BIOSEMI_ACTIVETWO_SPEEDMODECOUNT] = { 256, 128, 256, 34, 232, 104, 40, 8, 290, 0 };
|
||||
|
||||
/*
|
||||
The ActiveTwo sends the following number of 32-bit words per sample:
|
||||
|
||||
Mk2:
|
||||
Speedmode 0 and 4: 258
|
||||
Speedmode 1 and 5: 130
|
||||
Speedmode 2 and 6: 66
|
||||
Speedmode 3 and 7: 34
|
||||
Speedmode 8: 290 (2+256+32)
|
||||
*/
|
||||
static uint32_t activeTwoMarkIIElectrodeChannelPerSample[BIOSEMI_ACTIVETWO_SPEEDMODECOUNT] = { 256, 128, 256, 128, 256, 128, 64, 32, 314, 0 };
|
||||
|
||||
static uint32_t* activeTwoMarkElectrodeChannelPerSample[2] = { activeTwoMarkIElectrodeChannelPerSample, activeTwoMarkIIElectrodeChannelPerSample };
|
||||
|
||||
/*
|
||||
Mk2:
|
||||
Speedmode 0, 1, 2 and 3: 610 (2+4*152)
|
||||
Speedmode 4: 282
|
||||
Speedmode 5: 154
|
||||
Speedmode 6: 90
|
||||
Speedmode 7: 58
|
||||
Speedmode 8: 314 (2+280+32)
|
||||
*/
|
||||
static uint32_t activeTwoMarkIExChannelPerSample[BIOSEMI_ACTIVETWO_SPEEDMODECOUNT] = { 0, 0, 0, 0, 8, 8, 8, 8, 8, 8 };
|
||||
|
||||
/*
|
||||
Mk2:
|
||||
Speedmode 0, 1, 2 and 3: 610 (2+4*152)
|
||||
Speedmode 4: 282
|
||||
Speedmode 5: 154
|
||||
Speedmode 6: 90
|
||||
Speedmode 7: 58
|
||||
Speedmode 8: 314 (2+280+32)
|
||||
*/
|
||||
static uint32_t activeTwoMarkIIExChannelPerSample[BIOSEMI_ACTIVETWO_SPEEDMODECOUNT] = { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 };
|
||||
|
||||
static uint32_t* activeTwoMarkExChannelPerSample[2] = { activeTwoMarkIExChannelPerSample, activeTwoMarkIIExChannelPerSample };
|
||||
} // namespace BioSemi
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
namespace OpenViBE {
|
||||
|
||||
CBridgeBioSemiActiveTwo::CBridgeBioSemiActiveTwo()
|
||||
{
|
||||
m_buffers.resize(BIOSEMI_ACTIVETWO_RINGBUFFERBYTES, 0);
|
||||
m_controlBuffers.resize(BIOSEMI_ACTIVETWO_CONTROLBUFFERBYTES);
|
||||
m_lastError = BioSemiError_NoError;
|
||||
m_useEXChannels = false;
|
||||
}
|
||||
|
||||
CBridgeBioSemiActiveTwo::~CBridgeBioSemiActiveTwo() {}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
bool CBridgeBioSemiActiveTwo::isDeviceConnected()
|
||||
{
|
||||
m_hDevice = OPEN_DRIVER();
|
||||
if (m_hDevice == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("Failed to open driver!\n");
|
||||
m_lastError = BioSemiError_OSOpenFailed;
|
||||
return false;
|
||||
}
|
||||
|
||||
Sleep(100);
|
||||
|
||||
if (!CLOSE_DRIVER(m_hDevice))
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("device driver cannot be closed!!\n");
|
||||
m_lastError = BioSemiError_OSCloseFailed;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CBridgeBioSemiActiveTwo::open()
|
||||
{
|
||||
m_lastRingBufferByteIdx = 0;
|
||||
m_consumptionByteIdx = 0;
|
||||
m_firstRead = true;
|
||||
|
||||
m_nTotalByteRead = 0;
|
||||
|
||||
m_nChannel = 0;
|
||||
m_nInitialChannel = 0;
|
||||
m_bridgeSyncedWithDevice = false;
|
||||
|
||||
m_triggers.clear();
|
||||
m_triggers.resize(16, false);
|
||||
m_epochStarted = false;
|
||||
m_cmsInRange = false;
|
||||
m_batteryLow = false;
|
||||
m_activeTwoMarkII = false;
|
||||
m_initialActiveTwoMarkII = false;
|
||||
m_speedMode = -1;
|
||||
m_initialSpeedmode = 0;
|
||||
|
||||
m_hDevice = OPEN_DRIVER();
|
||||
if (m_hDevice == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("Failed to open driver!\n");
|
||||
m_lastError = BioSemiError_OSOpenFailed;
|
||||
return false;
|
||||
}
|
||||
|
||||
__BioSemiBridgeLogConsole__("Handle created, configuring...\n");
|
||||
|
||||
// TEMP for debug: log file written along with the LabView_DLL
|
||||
//BSIF_SET_LOG(true);
|
||||
|
||||
// default config
|
||||
//BSIF_SET_SYNC(true);
|
||||
//BSIF_SET_DEBUG(false);
|
||||
|
||||
// No stride
|
||||
//BSIF_SET_STRIDE_KB(0);
|
||||
//BSIF_SET_STRIDE_MS(0);
|
||||
|
||||
char bufferInfo[256];
|
||||
GET_DRIVER_INFO(bufferInfo, 256);
|
||||
__BioSemiBridgeLogConsole__("Driver Info: %s", bufferInfo);
|
||||
|
||||
__BioSemiBridgeLogConsole__("connecting ring buffer ...\n");
|
||||
READ_MULTIPLE_SWEEPS(m_hDevice, &m_buffers[0], BIOSEMI_ACTIVETWO_RINGBUFFERBYTES);
|
||||
|
||||
__BioSemiBridgeLogConsole__("device driver now opened.\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBridgeBioSemiActiveTwo::start()
|
||||
{
|
||||
m_controlBuffers.clear();
|
||||
m_controlBuffers.resize(BIOSEMI_ACTIVETWO_CONTROLBUFFERBYTES);
|
||||
m_controlBuffers[0] = char(-1); // Odd number to start the filling of ring buffer
|
||||
|
||||
BOOL status = false;
|
||||
|
||||
status = USB_WRITE(m_hDevice, &m_controlBuffers[0]);
|
||||
if (!status)
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("usb_write for enable handshake failed with [%i]\n", GetLastError());
|
||||
m_lastError = BioSemiError_EnableUSBHandshakeFailed;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Past this point, the ring buffer is constantly filled.
|
||||
return true;
|
||||
}
|
||||
|
||||
int CBridgeBioSemiActiveTwo::read()
|
||||
{
|
||||
int bytesRead;
|
||||
#if TARGET_ARCHITECTURE_x64
|
||||
long long currentRingBufferByteIdx;
|
||||
#else
|
||||
int currentRingBufferByteIdx;
|
||||
#endif
|
||||
const bool status = (READ_POINTER(m_hDevice, ¤tRingBufferByteIdx) != FALSE);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("READ_POINTER failed !!\n");
|
||||
m_lastError = BioSemiError_ReadPointerFailed;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (currentRingBufferByteIdx != m_lastRingBufferByteIdx)
|
||||
{
|
||||
bytesRead = currentRingBufferByteIdx - m_lastRingBufferByteIdx;
|
||||
|
||||
// ring buffer: we can loop indices
|
||||
if (bytesRead < 0)
|
||||
{
|
||||
bytesRead += BIOSEMI_ACTIVETWO_RINGBUFFERBYTES;
|
||||
__BioSemiBridgeLogConsole__("RING BUFFER IS ROLLING\n");
|
||||
}
|
||||
|
||||
//__BioSemiBridgeLogConsole__("READ_POINTER read %i bytes\n",l_iBytesRead);
|
||||
|
||||
m_nTotalByteRead += uint32_t(bytesRead);
|
||||
m_lastRingBufferByteIdx = currentRingBufferByteIdx;
|
||||
|
||||
// determine number of channels according to sync bytes
|
||||
// Two extra channels are leading the ADC data: before: channel 1 = sync (check for FFFFFF00) and channel 2 = Status
|
||||
// (see http://www.biosemi.com/faq/trigger_signals.htm), channels 3-258 are ADCs 1-256.
|
||||
|
||||
uint32_t firstSyncByte = -1;
|
||||
uint32_t nextSyncByte = -1;
|
||||
|
||||
// for loop over LONG values, not bytes
|
||||
for (int i = 0; i < bytesRead && !m_bridgeSyncedWithDevice; ++i)
|
||||
{
|
||||
if (*(reinterpret_cast<int*>(&m_buffers[i])) == BIOSEMI_ACTIVETWO_SYNCBYTES)
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("sync detected at byte %i\n", i);
|
||||
if (firstSyncByte == -1) { firstSyncByte = i; }
|
||||
else
|
||||
{
|
||||
//second sync found, we can deduce the channel count: it's the number of longs in between
|
||||
nextSyncByte = i;
|
||||
m_nChannel = nextSyncByte - firstSyncByte;
|
||||
m_nChannel /= sizeof(int);
|
||||
m_nChannel -= 2;
|
||||
|
||||
|
||||
// we also initialize the status values for getSamplingFrequency
|
||||
const int statusChannelValue = *(reinterpret_cast<int*>(&m_buffers[firstSyncByte + sizeof(int)])); // status integer just after sync integer
|
||||
if (!this->updateStatusFromValue(statusChannelValue))
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("something bad in latest status value !\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Bridge is synced, user can call status getters
|
||||
m_bridgeSyncedWithDevice = true;
|
||||
|
||||
//consuming all sync samples
|
||||
//this->consumeBytes(sizeof(int) * (getSampleCount() - m_nChannel - 1));
|
||||
|
||||
// The Handsake is complete; we can safely consume the corresponding data
|
||||
// channel# + SYNC (Status is consumed in updateStatusFromValue function)
|
||||
this->consumeBytes((m_nChannel + 1) * sizeof(int));
|
||||
|
||||
// fix number of channel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_bridgeSyncedWithDevice)
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("Cannot synchronize on current data, waiting for more.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
//debug
|
||||
/*fstream fs("C:/biosemi-buffer.data",fstream::app);
|
||||
fs<< "START----------------------------------------------";
|
||||
for (int i=0; i<l_iBytesRead; i+=4)
|
||||
fs << hex << *(reinterpret_cast<int*>(&m_buffers[i])) << std::endl;
|
||||
fs.close();
|
||||
fs<< "END----------------------------------------------";*/
|
||||
|
||||
if (m_firstRead)
|
||||
{
|
||||
m_nInitialChannel = m_useEXChannels ? m_nChannel + getEXChannelCount() : m_nChannel;
|
||||
m_initialSpeedmode = m_speedMode;
|
||||
m_initialActiveTwoMarkII = m_activeTwoMarkII;
|
||||
m_firstRead = false;
|
||||
|
||||
// Consume what remains of data
|
||||
// samples# - channel# - SYNC
|
||||
this->consumeBytes(sizeof(int) * (getSampleCount() - m_nChannel - 1));
|
||||
|
||||
m_nChannel = m_nChannel < getElectrodeChannelCount() ? m_nChannel : getElectrodeChannelCount();
|
||||
__BioSemiBridgeLogConsole__(
|
||||
"Bridge sync! Initial configuration is: %u channels | speedmode %u | Mark2 %u | Sample count %u | electrode channel count %u | EX channel count: %u \n",
|
||||
m_nInitialChannel, m_initialSpeedmode, m_initialActiveTwoMarkII, getSampleCount(), getElectrodeChannelCount(), getEXChannelCount());
|
||||
}
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
// nothing read
|
||||
//__BioSemiBridgeLogConsole__("READ_POINTER has nothing for us.\n)";
|
||||
return 0;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CBridgeBioSemiActiveTwo::updateStatusFromValue(int value)
|
||||
{
|
||||
/*
|
||||
Bit 00 (LSB) Trigger Input 1 (High = trigger on)
|
||||
Bit 01 Trigger Input 2 (High = trigger on)
|
||||
Bit 02 Trigger Input 3 (High = trigger on)
|
||||
Bit 03 Trigger Input 4 (High = trigger on)
|
||||
Bit 04 Trigger Input 5 (High = trigger on)
|
||||
Bit 05 Trigger Input 6 (High = trigger on)
|
||||
Bit 06 Trigger Input 7 (High = trigger on)
|
||||
Bit 07 Trigger Input 8 (High = trigger on)
|
||||
Bit 08 Trigger Input 9 (High = trigger on)
|
||||
Bit 09 Trigger Input 10 (High = trigger on)
|
||||
Bit 10 Trigger Input 11 (High = trigger on)
|
||||
Bit 11 Trigger Input 12 (High = trigger on)
|
||||
Bit 12 Trigger Input 13 (High = trigger on)
|
||||
Bit 13 Trigger Input 14 (High = trigger on)
|
||||
Bit 14 Trigger Input 15 (High = trigger on)
|
||||
Bit 15 Trigger Input 16 (High = trigger on)
|
||||
Bit 16 High when new Epoch is started
|
||||
Bit 17 Speed bit 0
|
||||
Bit 18 Speed bit 1
|
||||
Bit 19 Speed bit 2
|
||||
Bit 20 High when CMS is within range ---> error in the documentation, it is the opposite
|
||||
Bit 21 Speed bit 3
|
||||
Bit 22 High when battery is low
|
||||
Bit 23 (MSB) High if ActiveTwo MK2
|
||||
*/
|
||||
//__BioSemiBridgeLogConsole__("status line value is [%#x]\n",value);
|
||||
// the status channel has 2 bytes padding (zeros), so we start at 0x100
|
||||
for (uint32_t t = 0; t < 16; ++t) { m_triggers[t] = (value & (0x100 << t)) != 0; }
|
||||
|
||||
m_epochStarted = (value & (0x100 << 16)) != 0;
|
||||
m_cmsInRange = !(value & (0x100 << 20)) != 0; // bit low = CM in range !
|
||||
m_batteryLow = (value & (0x100 << 22)) != 0;
|
||||
m_activeTwoMarkII = (value & (0x100 << 23)) != 0;
|
||||
m_speedMode = ((value & (0x100 << 17)) != 0) + (((value & (0x100 << 18)) != 0) << 1)
|
||||
+ (((value & (0x100 << 19)) != 0) << 2);
|
||||
// + (((value & (0x100 << 21))!= 0) << 3);
|
||||
|
||||
this->consumeBytes(sizeof(int));
|
||||
|
||||
// fail cases:
|
||||
if (!m_firstRead && m_activeTwoMarkII != m_initialActiveTwoMarkII)
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("Device type changed during acquisition! Stream may have lost synchronization.\n");
|
||||
m_lastError = BioSemiError_DeviceTypeChanged;
|
||||
return false;
|
||||
}
|
||||
if (!m_firstRead && m_speedMode != m_initialSpeedmode)
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("Speedmode changed during acquisition (%u > %u). Stream may have lost synchronization.\n", m_initialSpeedmode, m_speedMode);
|
||||
m_lastError = BioSemiError_SpeedmodeChanged;
|
||||
return false;
|
||||
}
|
||||
if (m_speedMode >= BIOSEMI_ACTIVETWO_SPEEDMODECOUNT)
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("Invalid speedmode [%u].\n", m_speedMode);
|
||||
m_lastError = BioSemiError_InvalidSpeedmode;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t CBridgeBioSemiActiveTwo::getSamplingFrequency() const { return BioSemi::activeTwoSpeedModeFrequency[(m_activeTwoMarkII ? 1 : 0)][m_speedMode]; }
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CBridgeBioSemiActiveTwo::discard()
|
||||
{
|
||||
const uint32_t count = this->getAvailableSampleCount();
|
||||
for (uint32_t i = 0; i < count; ++i)
|
||||
{
|
||||
if (!this->consumeOneSamplePerChannel(nullptr, 0))
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("the sample %u/%u [%u] cannot be discarded ! stopping discard.\n", i, count, m_consumptionByteIdx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t CBridgeBioSemiActiveTwo::getAvailableByteCount() const
|
||||
{
|
||||
if (m_lastRingBufferByteIdx > m_consumptionByteIdx) { return m_lastRingBufferByteIdx - m_consumptionByteIdx; }
|
||||
return m_lastRingBufferByteIdx + (BIOSEMI_ACTIVETWO_RINGBUFFERBYTES - m_consumptionByteIdx);
|
||||
}
|
||||
|
||||
uint32_t CBridgeBioSemiActiveTwo::getAvailableSampleCount()
|
||||
{
|
||||
if (!m_bridgeSyncedWithDevice) return 0;
|
||||
// this will be rounded
|
||||
return (this->getAvailableByteCount() / (sizeof(int) * getSampleCount()));
|
||||
}
|
||||
|
||||
void CBridgeBioSemiActiveTwo::consumeBytes(const uint32_t count)
|
||||
{
|
||||
/*fstream fs("C:/biosemi-consume.txt",fstream::app);
|
||||
fs <<dec<< count<< " : " << m_consumptionByteIdx << std::endl;
|
||||
fs.close();*/
|
||||
|
||||
m_consumptionByteIdx = (m_consumptionByteIdx + count) % BIOSEMI_ACTIVETWO_RINGBUFFERBYTES;
|
||||
}
|
||||
|
||||
bool CBridgeBioSemiActiveTwo::consumeOneSamplePerChannel(float* pSampleBuffer, uint32_t uiBufferValueCount)
|
||||
{
|
||||
// fail case: we can't consume if not yet sync'ed
|
||||
if (!m_bridgeSyncedWithDevice)
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("Bridge is not synced with the device, cannot consume.\n");
|
||||
m_lastError = BioSemiError_NoSync;
|
||||
return false;
|
||||
}
|
||||
|
||||
//fail case: we do nothing if we cannot completly fill the user buffer
|
||||
if (this->getAvailableByteCount() < uiBufferValueCount * sizeof(int))
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("Not enough data to complete user buffer. Waiting for next read & consume run.\n");
|
||||
m_lastError = BioSemiError_NotEnoughDataInBuffer;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* if(*(reinterpret_cast<int*>(&m_buffers[m_consumptionByteIdx])) != BIOSEMI_ACTIVETWO_SYNCBYTES)
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("Lost sync at consume time ! byte index is [%i]\n",m_consumptionByteIdx);
|
||||
m_lastError = BioSemiError_SyncLost;
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
/*
|
||||
the LongPerSample value gives the number of integers that the device sends per sample round.
|
||||
For example with the Mark II and a speedmode = 4, we have 282 Integers per sample.
|
||||
Say we have 8 channels connected, we end up with 282 integers with SYNC forward padding:
|
||||
- 273 SYNC
|
||||
- 1 Integer for the Status
|
||||
- 8 Integers for the 8 channels
|
||||
*/
|
||||
//this->consumeBytes(sizeof(int) * (getSampleCount() - m_nChannel - 1));
|
||||
|
||||
// We consume the integer from STATUS channel.
|
||||
const int value = *(reinterpret_cast<int*>(&m_buffers[m_consumptionByteIdx]));
|
||||
if (!this->updateStatusFromValue(value))
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("something bad in latest status value !\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// EEG channels fill the user buffer.
|
||||
if (pSampleBuffer != nullptr)
|
||||
{
|
||||
/*
|
||||
The receiver converts every 24-bit word from the AD-box into a 32-bit Signed Integer,
|
||||
by adding an extra zero Least Significant Byte to the ADC data.
|
||||
The 24-bit ADC output has an LSB value of 1/32th uV.
|
||||
The 32-bit Integer received for the USB interface has an LSB value of 1/32*1/256 = 1/8192th uV
|
||||
*/
|
||||
const float factor = 1.f / 8192;
|
||||
|
||||
uint32_t sampleLongConsumed = 0;
|
||||
uint32_t sampleInBuffer = 0;
|
||||
const uint32_t maxEEGChannel = isUseEXChannels() ? uiBufferValueCount - getEXChannelCount() : uiBufferValueCount;
|
||||
while (sampleInBuffer < maxEEGChannel)
|
||||
{
|
||||
const int iValue = *(reinterpret_cast<int*>(&m_buffers[m_consumptionByteIdx]));
|
||||
|
||||
// We don't add more samples that the number that should be displayed,
|
||||
// to avoid displaying an electrode channel instaed of an EX channel
|
||||
if (sampleInBuffer < m_nChannel) { pSampleBuffer[sampleInBuffer] = iValue * factor; }
|
||||
else
|
||||
{
|
||||
// If the number of requested channel is superior to the number of available channels, pad with 0
|
||||
pSampleBuffer[sampleInBuffer] = 0;
|
||||
}
|
||||
|
||||
sampleInBuffer++;
|
||||
sampleLongConsumed++;
|
||||
this->consumeBytes(sizeof(int));
|
||||
}
|
||||
if (m_useEXChannels)
|
||||
{
|
||||
// Consuming all sync samples until EX channels
|
||||
// ELECTRODE CHANNEL # - CHANNEL#
|
||||
this->consumeBytes(sizeof(int) * (getElectrodeChannelCount() - maxEEGChannel));
|
||||
while (sampleLongConsumed < maxEEGChannel + getEXChannelCount())
|
||||
{
|
||||
const int iValue = *(reinterpret_cast<int*>(&m_buffers[m_consumptionByteIdx]));
|
||||
if (sampleInBuffer < uiBufferValueCount)
|
||||
{
|
||||
pSampleBuffer[sampleInBuffer] = iValue * factor;
|
||||
sampleInBuffer++;
|
||||
}
|
||||
sampleLongConsumed++;
|
||||
this->consumeBytes(sizeof(int));
|
||||
}
|
||||
// Consuming all remaining samples (sensor channels)
|
||||
// SAMPLE# - ELECTRODE CHANNEL# - EX CHANNEL# - SYNC
|
||||
this->consumeBytes(sizeof(int) * (getSampleCount() - (getElectrodeChannelCount() + getEXChannelCount() + 1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Consuming all sync samples plus EX and sensor channels
|
||||
// SAMPLE# - CHANNEL# - SYNC
|
||||
this->consumeBytes(sizeof(int) * (getSampleCount() - maxEEGChannel - 1));
|
||||
}
|
||||
}
|
||||
//if pSample == nullptr
|
||||
else
|
||||
{
|
||||
// Consuming all sync samples plus EX and sensor channels
|
||||
// SAMPLE# - CHANNEL# - SYNC
|
||||
this->consumeBytes(sizeof(int) * (getSampleCount() - 1));
|
||||
}
|
||||
/*if(*(reinterpret_cast<int*>(&m_buffers[m_consumptionByteIdx])) != BIOSEMI_ACTIVETWO_SYNCBYTES)
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("Lost sync at consume time ! byte index is [%i]\n",m_consumptionByteIdx);
|
||||
m_lastError = BioSemiError_SyncLost;
|
||||
return false;
|
||||
}*/
|
||||
//this->consumeBytes(sizeof(int) * m_nChannel);
|
||||
|
||||
//__BioSemiBridgeLogConsole__("consumed "<<l_uiByteConsumed<<" bytes this round.\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
uint32_t CBridgeBioSemiActiveTwo::getElectrodeChannelCount() const
|
||||
{
|
||||
if (!m_bridgeSyncedWithDevice) { return 0; }
|
||||
// this will be rounded
|
||||
return (BioSemi::activeTwoMarkElectrodeChannelPerSample[(m_activeTwoMarkII ? 1 : 0)][m_speedMode]);
|
||||
}
|
||||
|
||||
uint32_t CBridgeBioSemiActiveTwo::getEXChannelCount() const
|
||||
{
|
||||
if (!m_useEXChannels || !m_bridgeSyncedWithDevice) { return 0; }
|
||||
// this will be rounded
|
||||
return (BioSemi::activeTwoMarkExChannelPerSample[(m_activeTwoMarkII ? 1 : 0)][m_speedMode]);
|
||||
}
|
||||
|
||||
uint32_t CBridgeBioSemiActiveTwo::getSampleCount() const
|
||||
{
|
||||
if (!m_bridgeSyncedWithDevice) { return 0; }
|
||||
// this will be rounded
|
||||
return (BioSemi::activeTwoMarkLongPerSample[(m_activeTwoMarkII ? 1 : 0)][m_speedMode]);
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CBridgeBioSemiActiveTwo::stop()
|
||||
{
|
||||
//float bytesPerMsec = GET_BYTES_PER_MSEC();
|
||||
//__BioSemiBridgeLogConsole__("STATS: Bytes per ms: " << bytesPerMsec << std::endl;
|
||||
|
||||
// To stop, we disable the handshake
|
||||
m_controlBuffers.clear();
|
||||
m_controlBuffers.resize(BIOSEMI_ACTIVETWO_CONTROLBUFFERBYTES);
|
||||
m_controlBuffers[0] = 0; // stop byte
|
||||
|
||||
const BOOL status = USB_WRITE(m_hDevice, &m_controlBuffers[0]);
|
||||
if (!status)
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("usb_write for disabling handshake failed with [%i]\n", GetLastError());
|
||||
m_lastError = BioSemiError_DisableUSBHandshakeFailed;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBridgeBioSemiActiveTwo::close()
|
||||
{
|
||||
if (!CLOSE_DRIVER(m_hDevice))
|
||||
{
|
||||
__BioSemiBridgeLogConsole__("device driver cannot be closed!!\n");
|
||||
m_lastError = BioSemiError_OSCloseFailed;
|
||||
return false;
|
||||
}
|
||||
|
||||
__BioSemiBridgeLogConsole__("device driver now closed.\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_HAS_ThirdPartyBioSemiAPI
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* ovasCDriverBioSemiActiveTwo.h
|
||||
*
|
||||
* Copyright (c) 2012, Mensia Technologies SA. All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef TARGET_HAS_ThirdPartyBioSemiAPI
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
#define BIOSEMI_ACTIVETWO_MAXCHANNELCOUNT 256
|
||||
#define BIOSEMI_ACTIVETWO_EXCHANNELCOUNT 8
|
||||
|
||||
namespace OpenViBE {
|
||||
enum EBioSemiError
|
||||
{
|
||||
BioSemiError_NoError,
|
||||
|
||||
BioSemiError_OSOpenFailed,
|
||||
BioSemiError_OSCloseFailed,
|
||||
|
||||
BioSemiError_EnableUSBHandshakeFailed,
|
||||
BioSemiError_DisableUSBHandshakeFailed,
|
||||
|
||||
BioSemiError_ReadPointerFailed,
|
||||
|
||||
BioSemiError_DeviceTypeChanged,
|
||||
BioSemiError_SpeedmodeChanged,
|
||||
BioSemiError_InvalidSpeedmode,
|
||||
|
||||
BioSemiError_NoData,
|
||||
BioSemiError_NotEnoughDataInBuffer,
|
||||
BioSemiError_NoSync,
|
||||
BioSemiError_SyncLost,
|
||||
BioSemiError_BufferOverflow
|
||||
};
|
||||
|
||||
|
||||
class CBridgeBioSemiActiveTwo final
|
||||
{
|
||||
public:
|
||||
|
||||
CBridgeBioSemiActiveTwo();
|
||||
virtual ~CBridgeBioSemiActiveTwo();
|
||||
|
||||
bool isDeviceConnected();
|
||||
bool open();
|
||||
bool start();
|
||||
int read();
|
||||
bool discard();
|
||||
uint32_t getAvailableSampleCount();
|
||||
uint32_t getElectrodeChannelCount() const;
|
||||
uint32_t getEXChannelCount() const;
|
||||
uint32_t getSampleCount() const;
|
||||
uint32_t getSpeedMode() const { return m_speedMode; }
|
||||
size_t getChannelCount() const { return m_nChannel; }
|
||||
bool consumeOneSamplePerChannel(float* pSampleBuffer, uint32_t uiBufferValueCount);
|
||||
bool stop();
|
||||
bool close();
|
||||
|
||||
bool isSynced() const { return m_bridgeSyncedWithDevice; }
|
||||
|
||||
bool getTrigger(const uint32_t index) const { return (index > m_triggers.size() ? false : m_triggers[index]); }
|
||||
bool isCMSInRange() const { return m_cmsInRange; }
|
||||
bool isBatteryLow() const { return m_batteryLow; }
|
||||
bool isDeviceMarkII() const { return m_activeTwoMarkII; }
|
||||
uint32_t getSamplingFrequency() const;
|
||||
|
||||
uint32_t getLastError() const { return m_lastError; }
|
||||
|
||||
bool isUseEXChannels() const { return m_useEXChannels; }
|
||||
void setUseEXChannels(const bool useEXChannels) { m_useEXChannels = useEXChannels; }
|
||||
|
||||
protected:
|
||||
bool updateStatusFromValue(int value);
|
||||
uint32_t getAvailableByteCount() const;
|
||||
void consumeBytes(uint32_t count);
|
||||
|
||||
// Handle to the device
|
||||
void* m_hDevice = nullptr;
|
||||
// Ring buffer
|
||||
std::vector<char> m_buffers;
|
||||
// 64bits buffer for USB_WRITE operations
|
||||
std::vector<char> m_controlBuffers;
|
||||
|
||||
/* deduced from the SYNC channel on first read */
|
||||
bool m_firstRead = false;
|
||||
bool m_bridgeSyncedWithDevice = false;
|
||||
uint32_t m_nChannel = 0;
|
||||
uint32_t m_nInitialChannel = 0;
|
||||
|
||||
/* From Status channel */
|
||||
std::vector<bool> m_triggers;
|
||||
bool m_epochStarted = false;
|
||||
bool m_cmsInRange = false;
|
||||
bool m_batteryLow = false;
|
||||
bool m_activeTwoMarkII = false;
|
||||
bool m_initialActiveTwoMarkII = false;
|
||||
uint32_t m_speedMode = 0;
|
||||
uint32_t m_initialSpeedmode = 0;
|
||||
|
||||
// ring buffer indices
|
||||
#if TARGET_ARCHITECTURE_x64
|
||||
long long m_lastRingBufferByteIdx = 0; //Buffer filled up to this point in last read()
|
||||
#else
|
||||
uint32_t m_lastRingBufferByteIdx = 0; //Buffer filled up to this point in last read()]
|
||||
#endif
|
||||
uint32_t m_consumptionByteIdx = 0; //Next consume() should start here, it should ALWAYS be a SYNC byte.
|
||||
|
||||
// just for stats
|
||||
uint32_t m_nTotalByteRead = 0;
|
||||
uint32_t m_lastError = 0;
|
||||
bool m_useEXChannels = false;
|
||||
};
|
||||
} // namespace OpenViBE
|
||||
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyBioSemiAPI
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* ovasCConfigurationBioSemiActiveTwo.cpp
|
||||
*
|
||||
* Copyright (c) 2012, Mensia Technologies SA. All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifdef TARGET_HAS_ThirdPartyBioSemiAPI
|
||||
|
||||
#include "ovasCConfigurationBioSemiActiveTwo.h"
|
||||
#include "ovasIHeader.h"
|
||||
#include "mCBridgeBioSemiActiveTwo.h"
|
||||
|
||||
#include <system/ovCTime.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
static void gtk_combo_box_set_active_text(GtkComboBox* pComboBox, const gchar* sActiveText)
|
||||
{
|
||||
GtkTreeModel* treeModel = gtk_combo_box_get_model(pComboBox);
|
||||
GtkTreeIter it;
|
||||
int index = 0;
|
||||
gchar* entryName = nullptr;
|
||||
if (gtk_tree_model_get_iter_first(treeModel, &it))
|
||||
{
|
||||
do
|
||||
{
|
||||
gtk_tree_model_get(treeModel, &it, 0, &entryName, -1);
|
||||
if (std::string(entryName) == std::string(sActiveText))
|
||||
{
|
||||
gtk_combo_box_set_active(pComboBox, index);
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
} while (gtk_tree_model_iter_next(treeModel, &it));
|
||||
}
|
||||
}
|
||||
|
||||
CConfigurationBioSemiActiveTwo::CConfigurationBioSemiActiveTwo(const char* gtkBuilderFilename, bool& useExChannels)
|
||||
: CConfigurationBuilder(gtkBuilderFilename), m_useEXChannels(useExChannels) {}
|
||||
|
||||
bool CConfigurationBioSemiActiveTwo::preConfigure()
|
||||
{
|
||||
if (!CConfigurationBuilder::preConfigure()) { return false; }
|
||||
|
||||
// deduced from connection to device, cannot be edited.
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_sampling_frequency")), false);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_speed_mode")), false);
|
||||
|
||||
CBridgeBioSemiActiveTwo bridge;
|
||||
bool status = false;
|
||||
if (bridge.open() && bridge.start())
|
||||
{
|
||||
// to let the device send a first packet from which the bridge can deduce the SF and channel count
|
||||
System::Time::sleep(500);
|
||||
if (bridge.read() > 0)
|
||||
{
|
||||
gtk_image_set_from_stock(GTK_IMAGE(gtk_builder_get_object(m_builder, "image_status")),GTK_STOCK_YES, GTK_ICON_SIZE_BUTTON);
|
||||
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(m_builder, "spinbutton_number_of_channels")), true);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(m_builder, "button_apply")), true);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(m_builder, "button_change_channel_names")), true);
|
||||
|
||||
// If this option is selected then 8 channels of the header are dedicated to the EX channels and they should be removed of the displayed channel count
|
||||
if (m_useEXChannels)
|
||||
{
|
||||
gtk_adjustment_set_value(
|
||||
GTK_ADJUSTMENT(gtk_builder_get_object(m_builder, "adjustment_channel_count")),
|
||||
gtk_adjustment_get_value(GTK_ADJUSTMENT(gtk_builder_get_object(m_builder, "adjustment_channel_count"))) - 8);
|
||||
}
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(m_builder, "checkbutton_ex_channel")), true);
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_ex_channel")), m_useEXChannels);
|
||||
|
||||
//Set maximum in function of the speedmode used
|
||||
gtk_adjustment_set_upper(GTK_ADJUSTMENT(gtk_builder_get_object(m_builder, "adjustment_channel_count")), bridge.getElectrodeChannelCount());
|
||||
|
||||
std::stringstream ss;
|
||||
ss << bridge.getSamplingFrequency();
|
||||
gtk_combo_box_set_active_text(GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_sampling_frequency")), ss.str().c_str());
|
||||
|
||||
ss.str("");
|
||||
ss << bridge.getSpeedMode();
|
||||
gtk_combo_box_set_active_text(GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_speed_mode")), ss.str().c_str());
|
||||
|
||||
gtk_label_set_markup(
|
||||
GTK_LABEL(gtk_builder_get_object(m_builder, "label_device_mark")),
|
||||
(bridge.isDeviceMarkII() ? "- <i>ActiveTwo Mark II</i> -" : "- <i>ActiveTwo Mark I</i> -"));
|
||||
gtk_label_set_markup(
|
||||
GTK_LABEL(gtk_builder_get_object(m_builder, "label_device_battery")),
|
||||
(bridge.isBatteryLow() ? "<span color=\"red\"><b>LOW BATTERY</b></span> -" : "<b>BATTERY OK</b>"));
|
||||
|
||||
// discard any data.
|
||||
bridge.discard();
|
||||
|
||||
status = true;
|
||||
}
|
||||
bridge.stop();
|
||||
bridge.close();
|
||||
}
|
||||
|
||||
if (!status)
|
||||
{
|
||||
gtk_combo_box_set_active_text(GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_sampling_frequency")), "");
|
||||
gtk_combo_box_set_active_text(GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_speed_mode")), "");
|
||||
gtk_image_set_from_stock(GTK_IMAGE(gtk_builder_get_object(m_builder, "image_status")),GTK_STOCK_NO, GTK_ICON_SIZE_BUTTON);
|
||||
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(m_builder, "spinbutton_number_of_channels")), false);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(m_builder, "button_apply")), false);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(m_builder, "button_change_channel_names")), false);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(m_builder, "checkbutton_ex_channel")), false);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CConfigurationBioSemiActiveTwo::postConfigure()
|
||||
{
|
||||
if (m_applyConfig)
|
||||
{
|
||||
m_useEXChannels = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_ex_channel"))) ? true : false;
|
||||
}
|
||||
|
||||
if (!CConfigurationBuilder::postConfigure()) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* ovasCConfigurationBioSemiActiveTwo.h
|
||||
*
|
||||
* Copyright (c) 2012, Mensia Technologies SA. All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef TARGET_HAS_ThirdPartyBioSemiAPI
|
||||
|
||||
#include "../ovasCConfigurationBuilder.h"
|
||||
#include <gtk/gtk.h>
|
||||
#include <vector>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
class CConfigurationBioSemiActiveTwo final : public CConfigurationBuilder
|
||||
{
|
||||
public:
|
||||
|
||||
CConfigurationBioSemiActiveTwo(const char* gtkBuilderFilename, bool& useExChannels);
|
||||
|
||||
bool preConfigure() override;
|
||||
bool postConfigure() override;
|
||||
|
||||
protected:
|
||||
bool& m_useEXChannels;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyBioSemiAPI
|
||||
+550
@@ -0,0 +1,550 @@
|
||||
/*
|
||||
* ovasCDriverBioSemiActiveTwo.cpp
|
||||
*
|
||||
* Copyright (c) 2012, Mensia Technologies SA. All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifdef TARGET_HAS_ThirdPartyBioSemiAPI
|
||||
|
||||
#include "ovasCDriverBioSemiActiveTwo.h"
|
||||
#include "ovasCConfigurationBioSemiActiveTwo.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
// linux, mac
|
||||
#include <unistd.h>
|
||||
#endif // WIN32
|
||||
|
||||
#include <toolkit/ovtk_all.h>
|
||||
#include <system/ovCTime.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
CDriverBioSemiActiveTwo::CDriverBioSemiActiveTwo(IDriverContext& ctx)
|
||||
: IDriver(ctx), m_settings("AcquisitionServer_Driver_BioSemiActiveTwo", m_driverCtx.getConfigurationManager())
|
||||
{
|
||||
m_header.setSamplingFrequency(2048);
|
||||
|
||||
// The amplifier can send up to 256+8 channels
|
||||
// as a request from BioSemi, we will make available the maximum channel count
|
||||
// User is able to select from 1 to MAX channels. If no data is present on the
|
||||
// corresponding channels, zeros will be sent.
|
||||
// The number of channels present in the data flow will still be displayed in
|
||||
// the driver configuration window. Previously selected value will be saved
|
||||
// with other settings.
|
||||
m_header.setChannelCount(BIOSEMI_ACTIVETWO_MAXCHANNELCOUNT + BIOSEMI_ACTIVETWO_EXCHANNELCOUNT);
|
||||
m_useEXChannel = true;
|
||||
|
||||
m_triggers.resize(16, false);
|
||||
|
||||
m_lastWarningTime = 0;
|
||||
m_startTime = 0;
|
||||
m_bCMCurrentlyInRange = true;
|
||||
m_bBatteryCurrentlyOk = true;
|
||||
|
||||
m_settings.add("Header", &m_header);
|
||||
m_settings.add("UseEXChannel", &m_useEXChannel);
|
||||
m_settings.load();
|
||||
m_bridge.setUseEXChannels(m_useEXChannel);
|
||||
}
|
||||
|
||||
void CDriverBioSemiActiveTwo::setupInformationWindow()
|
||||
{
|
||||
if (!m_infoWindow)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Information window not allocated\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_infoWindow->m_mutex);
|
||||
|
||||
m_infoWindow->m_builder = gtk_builder_new();
|
||||
if (!gtk_builder_add_from_file(m_infoWindow->m_builder, Directories::getDataDir() + "/applications/acquisition-server/interface-BioSemi-ActiveTwo.ui", nullptr))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "File not found: " << Directories::getDataDir() << "/applications/acquisition-server/interface-BioSemi-ActiveTwo.ui\n";
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_label_set_markup(GTK_LABEL(gtk_builder_get_object(m_infoWindow->m_builder, "label-device-type")), (m_bridge.isDeviceMarkII() ? "- <i>ActiveTwo Mark II</i> -" : "- <i>ActiveTwo Mark I</i> -"));
|
||||
m_infoWindow->m_isCMSInRange = m_bridge.isCMSInRange();
|
||||
m_infoWindow->m_isBatteryLow = m_bridge.isBatteryLow();
|
||||
m_infoWindow->m_isChanged = true;
|
||||
gtk_widget_show_all(GTK_WIDGET(gtk_builder_get_object(m_infoWindow->m_builder, "device-information")));
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
gint setup_information_window_callback(void* data)
|
||||
{
|
||||
CDriverBioSemiActiveTwo* pTmp = static_cast<CDriverBioSemiActiveTwo*>(data);
|
||||
|
||||
pTmp->setupInformationWindow();
|
||||
|
||||
return FALSE; // Don't run again
|
||||
}
|
||||
|
||||
gint information_window_callback(void* infoWindow)
|
||||
{
|
||||
if (!infoWindow)
|
||||
{
|
||||
// Not initialized yet?
|
||||
return TRUE;
|
||||
}
|
||||
SInformationWindow* window = reinterpret_cast<SInformationWindow*>(infoWindow);
|
||||
std::lock_guard<std::mutex> lock(reinterpret_cast<SInformationWindow*>(infoWindow)->m_mutex);
|
||||
// If nothing changed, directly return from the callback
|
||||
if (!window->m_isChanged) { return TRUE; }
|
||||
if (window->m_isAcquisitionEnded)
|
||||
{
|
||||
// If the acquisition is ended, delete the window
|
||||
gtk_widget_destroy(GTK_WIDGET(gtk_builder_get_object(window->m_builder, "device-information")));
|
||||
g_object_unref(window->m_builder);
|
||||
|
||||
// If we get here, we know that the other thread has passed uninitialize() and will no longer access Information Window.
|
||||
delete window;
|
||||
// The loop should now be stopped
|
||||
return FALSE;
|
||||
}
|
||||
if (window->m_isBatteryLow)
|
||||
{
|
||||
gtk_label_set_markup(GTK_LABEL(gtk_builder_get_object(window->m_builder, "label-battery-level")), "- <i>Device battery is low !</i> -");
|
||||
gtk_image_set_from_stock(GTK_IMAGE(gtk_builder_get_object(window->m_builder, "image-battery-level")), GTK_STOCK_NO, GTK_ICON_SIZE_BUTTON);
|
||||
}
|
||||
else
|
||||
{
|
||||
gtk_label_set_markup(GTK_LABEL(gtk_builder_get_object(window->m_builder, "label-battery-level")), "- <i> Device battery is ok </i> -");
|
||||
gtk_image_set_from_stock(GTK_IMAGE(gtk_builder_get_object(window->m_builder, "image-battery-level")), GTK_STOCK_YES, GTK_ICON_SIZE_BUTTON);
|
||||
}
|
||||
|
||||
if (window->m_isCMSInRange)
|
||||
{
|
||||
gtk_label_set_markup(GTK_LABEL(gtk_builder_get_object(window->m_builder, "label-CMS-status")), "- <i>CMS/DRL is in range </i> -");
|
||||
gtk_image_set_from_stock(GTK_IMAGE(gtk_builder_get_object(window->m_builder, "image-CMS-status")), GTK_STOCK_YES, GTK_ICON_SIZE_BUTTON);
|
||||
}
|
||||
else
|
||||
{
|
||||
gtk_label_set_markup(GTK_LABEL(gtk_builder_get_object(window->m_builder, "label-CMS-status")), "- <i>CMS/DRL is not in range </i> -");
|
||||
gtk_image_set_from_stock(GTK_IMAGE(gtk_builder_get_object(window->m_builder, "image-CMS-status")), GTK_STOCK_NO, GTK_ICON_SIZE_BUTTON);
|
||||
}
|
||||
gtk_label_set_markup(GTK_LABEL(gtk_builder_get_object(window->m_builder, "label-error-message")), (window->m_sErrorMessage).c_str());
|
||||
|
||||
window->m_isChanged = false;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
bool CDriverBioSemiActiveTwo::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
|
||||
{
|
||||
m_acquisitionStopped = false;
|
||||
|
||||
if (m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
m_callback = &callback;
|
||||
|
||||
if (!m_bridge.open())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Could not open the device.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_bridge.start())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Could not start the device.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// wait to be sure we get the first packet from which we deduce the actual channel count and other initial configuration.
|
||||
System::Time::sleep(500);
|
||||
|
||||
const int byteRead = m_bridge.read();
|
||||
if (byteRead < 0)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "An error occured while reading first data packet from device !\n";
|
||||
m_bridge.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_bridge.discard()) // we discard the samples.
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "An error occured while dropping unused samples at initialization time.\n";
|
||||
m_bridge.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Bridge initialized with: [SF:" << m_bridge.getSamplingFrequency()
|
||||
<< "] [CH:" << m_bridge.getChannelCount()
|
||||
<< "] [MKII:" << m_bridge.isDeviceMarkII()
|
||||
<< "] [CMInRange:" << m_bridge.isCMSInRange()
|
||||
<< "] [LowBat:" << m_bridge.isBatteryLow() << "]\n";
|
||||
|
||||
if (m_bridge.isBatteryLow()) { m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Device battery is low !\n"; }
|
||||
|
||||
// the sample buffer is resized to get samples from ALL channels even if the user selected
|
||||
// less channels. We will adjust the content when calling setSamples(...)
|
||||
// in case the user required more channels than the number available in the stream, we will
|
||||
// add 0-padding.
|
||||
const size_t nChannelInStream = m_bridge.getChannelCount();
|
||||
const size_t nChannelRequested = m_header.getChannelCount();
|
||||
m_samples.clear();
|
||||
m_samples.resize(nChannelRequested, 0);
|
||||
if (nChannelRequested > nChannelInStream)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning <<
|
||||
"The required channel count cannot be reached in current device configuration (data stream contains " << m_bridge.getChannelCount() <<
|
||||
" channels). Please check the device speed mode and setup capabilities. Channels with no data will be filled with zeros.\n";
|
||||
}
|
||||
|
||||
m_nSample = 0;
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Driver initialized...\n";
|
||||
|
||||
//Check speed mode: speed modes 1, 2 and 3 should not be used for acquisition
|
||||
if (m_bridge.getSpeedMode() < 4)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Speed modes 1 to 3 are designed to realize daisy chained montages, they should not be used for acquisition.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Rename EX channels after settings were saved
|
||||
if (m_bridge.isUseEXChannels())
|
||||
{
|
||||
int j = 1;
|
||||
for (size_t i = m_header.getChannelCount() - BIOSEMI_ACTIVETWO_EXCHANNELCOUNT; i < m_header.getChannelCount(); ++i, ++j)
|
||||
{
|
||||
m_header.setChannelName(i, ("EX " + std::to_string(j)).c_str());
|
||||
m_header.setChannelUnits(i, OVTK_UNIT_Volts, OVTK_FACTOR_Micro);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Channel name: " << m_header.getChannelName(i) << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
m_infoWindow = new SInformationWindow();
|
||||
|
||||
// Initialize information window
|
||||
// n.b. do all gtk stuff from callbacks to avoid threading issues.
|
||||
gdk_threads_add_idle(setup_information_window_callback, this);
|
||||
|
||||
//Launch idle loop: update the information window in a separate glib thread
|
||||
gdk_threads_add_idle(information_window_callback, m_infoWindow);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBioSemiActiveTwo::start()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
m_startTime = System::Time::getTime();
|
||||
m_lastWarningTime = 0;
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Acquisition started...\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBioSemiActiveTwo::loop()
|
||||
{
|
||||
if (m_acquisitionStopped || !m_driverCtx.isConnected()) { return true; }
|
||||
|
||||
if (m_driverCtx.isStarted())
|
||||
{
|
||||
//size_t nChannelInStream = m_bridge.getChannelCount();
|
||||
const size_t nChannelRequested = m_header.getChannelCount();
|
||||
|
||||
//uint32_t max = (nChannelRequested > nChannelInStream) ? nChannelRequested : nChannelInStream;
|
||||
const uint32_t max = nChannelRequested;
|
||||
const int byteRead = m_bridge.read();
|
||||
if (byteRead > 0)
|
||||
{
|
||||
for (uint32_t i = 0; i < m_bridge.getAvailableSampleCount(); ++i)
|
||||
{
|
||||
// we consume one sample per channel, values are given in uV
|
||||
if (!m_bridge.consumeOneSamplePerChannel(&m_samples[0], max))
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_infoWindow->m_mutex);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Something bad happened while consuming samples from device.\n";
|
||||
m_infoWindow->m_isChanged = true;
|
||||
m_infoWindow->m_sErrorMessage = "<span color=\"darkred\">Something bad happened while consuming samples from device.</span>\n";
|
||||
if (m_bridge.getLastError() == BioSemiError_SyncLost)
|
||||
{
|
||||
m_infoWindow->m_sErrorMessage += "\t <span color=\"darkred\"> Synchronization lost during acquisition. Fiber optic may be loose or damaged.</span>";
|
||||
m_driverCtx.getLogManager() << "\t >Synchronization lost during acquisition. Fiber optic may be loose or damaged.\n";
|
||||
}
|
||||
if (m_bridge.getLastError() == BioSemiError_BufferOverflow)
|
||||
{
|
||||
m_infoWindow->m_sErrorMessage += "\t <span color=\"darkred\"> Buffer overflow. Please check that you have enough CPU and memory available to run the acquisition server at full speed before retrying.</span>";
|
||||
m_driverCtx.getLogManager() << "\t > Buffer overflow. Please check that you have enough CPU and memory available to run the acquisition server at full speed before retrying.\n";
|
||||
}
|
||||
m_acquisitionStopped = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// this call uses the header's channel count, so it will naturally take the first samples (not necessarily all channels).
|
||||
m_callback->setSamples(&m_samples[0], 1);
|
||||
|
||||
// triggers:
|
||||
// we simply send OVTK_StimulationId_Label_X where X is the trigger index between 1 and 16
|
||||
// We don't handle rising and falling edges.
|
||||
for (uint32_t j = 0; j < m_triggers.size(); ++j)
|
||||
{
|
||||
if (m_bridge.getTrigger(j) != m_triggers[j])
|
||||
{
|
||||
m_triggers[j] = m_bridge.getTrigger(j);
|
||||
const uint64_t date = (1LL << 32) / m_bridge.getSamplingFrequency(); // date is relative to the buffer start. I only have one sample in the buffer so it's fairly simple
|
||||
m_stimSet.appendStimulation(OVTK_StimulationId_Label(j+1), date, 0);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Trigger " << j + 1 << "/16 has switched to " << m_triggers[j] << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// "CMS/DRL in range" warning, once every 2 seconds max
|
||||
/*
|
||||
From: Coen (BioSemi):
|
||||
|
||||
The current flow via the DRL is constantly monitored by the safety circuitry inside the AD-box. The current flow is limited to 50 uA (IEC 601 safety limit).
|
||||
If the current runs into the limit, the CMS/DRL circuit cannot keep the Common Mode value within its normal working range, and the blue LED turns off.
|
||||
|
||||
The safety circuitry reacts on this error situation by shutting the power supply to ALL active electrodes off. Consequently, no meaningful signals can be measured so long as the blue LED is off.
|
||||
The circuit operation described above implies that any electrode can be the cause of a CM out of range error.
|
||||
Examples of errors are broken wires, bad connector contacts (pollution of connector with gel), defect IC inside the electrode,
|
||||
bare electrode wire contacting the subject (damaged cable isolation) etc.. For example, if one of the active electrode wires is broken,
|
||||
the electrode input circuit is not anymore biased correctly, and the input resistance may fall below its specified value of 10^12 Ohm.
|
||||
The resultant extra input current is detected by the CMS/DRL circuit, and the blue LED goes off.
|
||||
Save operation of the system is ensured because the power supply to the active electrodes is only restored if ALL electrodes connected to the subject work correctly.
|
||||
In other words, both cap en EX electrodes can in principle cause CM out of range errors.
|
||||
*/
|
||||
|
||||
bool warningDisplayed = false;
|
||||
if (!m_bridge.isCMSInRange())
|
||||
{
|
||||
// we print a warning message once every 2secs maximum
|
||||
if (System::Time::getTime() > m_lastWarningTime + 2000)
|
||||
{
|
||||
warningDisplayed = true;
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "(" << ((System::Time::getTime() - m_startTime) / 1000) << "') CMS/DRL is not in range. For safety purpose, any active electrode connected has been shut down and signals should not be used.\n";
|
||||
m_driverCtx.getLogManager() << "\t The corresponding sample range has been tagged with OVTK_StimulationId_SegmentStart and OVTK_StimulationId_SegmentStop.\n";
|
||||
m_driverCtx.getLogManager() << "\t Possible causes include broken wires, damaged cable isolation, bad connector contacts, defect IC inside the electrode.\n";
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_infoWindow->m_mutex);
|
||||
m_infoWindow->m_isCMSInRange = false;
|
||||
m_infoWindow->m_isChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// the possibly incorrect sample range is tagged using OVTK_StimulationId_SegmentStart and OVTK_StimulationId_SegmentStop
|
||||
// CM is now not in range
|
||||
if (m_bCMCurrentlyInRange)
|
||||
{
|
||||
const uint64_t date = (1LL << 32) / m_bridge.getSamplingFrequency();
|
||||
m_stimSet.appendStimulation(OVTK_StimulationId_SegmentStart, date, 0);
|
||||
}
|
||||
m_bCMCurrentlyInRange = false;
|
||||
m_bCMSBackInRange = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// CMS/DRL is now back in range
|
||||
if (!m_bCMCurrentlyInRange)
|
||||
{
|
||||
m_lastCmsBackInRange = System::Time::getTime();
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "isCMSInRange \n";
|
||||
const uint64_t date = (1LL << 32) / m_bridge.getSamplingFrequency();
|
||||
m_stimSet.appendStimulation(OVTK_StimulationId_SegmentStop, date, 0);
|
||||
m_bCMCurrentlyInRange = true;
|
||||
}
|
||||
// CMS is considered "back in range" if it stayed in range for more than 500ms
|
||||
// -> avoids changing the gtk markup too often (which would not be readable for the user nor efficient)
|
||||
else if (!m_bCMSBackInRange && (System::Time::getTime() > m_lastCmsBackInRange + 100))
|
||||
{
|
||||
m_bCMSBackInRange = true;
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Back in range \n";
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_infoWindow->m_mutex);
|
||||
m_infoWindow->m_isCMSInRange = true;
|
||||
m_infoWindow->m_isChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_bridge.isBatteryLow())
|
||||
{
|
||||
// we print a warning message once every 2secs maximum
|
||||
if (System::Time::getTime() > m_lastWarningTime + 2000)
|
||||
{
|
||||
warningDisplayed = true;
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "(" << ((System::Time::getTime() - m_startTime) / 1000) << "') Device battery is low !\n";
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_infoWindow->m_mutex);
|
||||
m_infoWindow->m_isBatteryLow = true;
|
||||
m_infoWindow->m_isChanged = true;
|
||||
}
|
||||
}
|
||||
m_bBatteryCurrentlyOk = false;
|
||||
m_bBatteryBackOk = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_bBatteryCurrentlyOk)
|
||||
{
|
||||
m_lastBatteryLow = System::Time::getTime();
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "(" << ((System::Time::getTime() - m_startTime) / 1000) << "') Device battery seems ok.\n";
|
||||
m_bBatteryCurrentlyOk = true;
|
||||
}
|
||||
// CMS is considered "back in range" if it stayed in range for more than 500ms
|
||||
// -> avoids changing the gtk markup too often (which would not be readable for the user nor efficient)
|
||||
else if (!m_bBatteryBackOk && (System::Time::getTime() > m_lastBatteryLow + 100))
|
||||
{
|
||||
m_bBatteryBackOk = true;
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Back in range \n";
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_infoWindow->m_mutex);
|
||||
m_infoWindow->m_isBatteryLow = false;
|
||||
m_infoWindow->m_isChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (warningDisplayed) { m_lastWarningTime = System::Time::getTime(); }
|
||||
|
||||
m_callback->setStimulationSet(m_stimSet);
|
||||
m_stimSet.clear();
|
||||
}
|
||||
}
|
||||
if (byteRead < 0)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "An error occured while reading data from device !\n";
|
||||
m_acquisitionStopped = true;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_infoWindow->m_mutex);
|
||||
m_infoWindow->m_sErrorMessage = "<span color=\"darkred\">An error occured while reading data from device !</span>";
|
||||
m_infoWindow->m_isChanged = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
|
||||
}
|
||||
else
|
||||
{
|
||||
// acquisition is not started, but device is.
|
||||
int byteRead = m_bridge.read();
|
||||
while (byteRead > 0)
|
||||
{
|
||||
if (!m_bridge.discard()) // we discard the samples.
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "An error occured while dropping samples.\n";
|
||||
m_acquisitionStopped = true;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_infoWindow->m_mutex);
|
||||
m_infoWindow->m_sErrorMessage = "<span color=\"darkred\">An error occured while dropping samples.</span>";
|
||||
m_infoWindow->m_isChanged = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
byteRead = m_bridge.read();
|
||||
}
|
||||
if (byteRead < 0)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "An error occured while reading data from device (drop)!\n";
|
||||
m_acquisitionStopped = true;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_infoWindow->m_mutex);
|
||||
m_infoWindow->m_sErrorMessage = "<span color=\"darkred\">An error occured while reading data from device (drop)!</span>";
|
||||
m_infoWindow->m_isChanged = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBioSemiActiveTwo::stop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Acquisition stopped...\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBioSemiActiveTwo::uninitialize()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_infoWindow->m_mutex);
|
||||
|
||||
m_infoWindow->m_isChanged = true;
|
||||
m_infoWindow->m_isAcquisitionEnded = true;
|
||||
m_infoWindow = nullptr; // The callback will free the memory
|
||||
}
|
||||
|
||||
// Rename EX channels as "" so that the names are not saved as settings
|
||||
if (m_bridge.isUseEXChannels())
|
||||
{
|
||||
for (size_t i = m_header.getChannelCount() - m_bridge.getEXChannelCount(); i < m_header.getChannelCount(); ++i)
|
||||
{
|
||||
m_header.setChannelName(i, "");
|
||||
m_header.setChannelUnits(i, OVTK_UNIT_Volts, OVTK_FACTOR_Micro);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
if (!m_bridge.close())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Could not close the device.\n";
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Driver uninitialized...\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverBioSemiActiveTwo::configure()
|
||||
{
|
||||
CConfigurationBioSemiActiveTwo config(Directories::getDataDir() + "/applications/acquisition-server/interface-BioSemi-ActiveTwo.ui", m_useEXChannel);
|
||||
|
||||
if (config.configure(m_header))
|
||||
{
|
||||
m_bridge.setUseEXChannels(m_useEXChannel);
|
||||
if (m_bridge.isUseEXChannels()) { m_header.setChannelCount(m_header.getChannelCount() + BIOSEMI_ACTIVETWO_EXCHANNELCOUNT); }
|
||||
|
||||
m_settings.save();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
size_t CDriverBioSemiActiveTwo::getChannelCount()
|
||||
{
|
||||
if(m_useEXChannel)
|
||||
{
|
||||
return m_header.getChannelCount() + m_bridge.getEXChannelCount();
|
||||
}
|
||||
else { return m_header.getChannelCount(); }
|
||||
}
|
||||
*/
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* ovasCDriverBioSemiActiveTwo.h
|
||||
*
|
||||
* Copyright (c) 2012, Mensia Technologies SA. All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301 USA
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef TARGET_HAS_ThirdPartyBioSemiAPI
|
||||
|
||||
#include "ovasIDriver.h"
|
||||
#include "../ovasCHeader.h"
|
||||
|
||||
#include "../ovasCSettingsHelper.h"
|
||||
#include "../ovasCSettingsHelperOperators.h"
|
||||
|
||||
#include "mCBridgeBioSemiActiveTwo.h"
|
||||
|
||||
#include "gtk/gtk.h"
|
||||
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* SInformationWindow: encapsulate the GtkBuilder used to build the window
|
||||
* and the information that we want to display in it.
|
||||
* The use of a structure allows to use a GLib idle loop.
|
||||
*/
|
||||
typedef struct SInformationWindow
|
||||
{
|
||||
SInformationWindow() {}
|
||||
// Builder of the window
|
||||
GtkBuilder* m_builder = nullptr;
|
||||
// When set to true, the idle loop is stopped and the window is destroyed
|
||||
bool m_isAcquisitionEnded = false;
|
||||
// Set to true when at least one of the acquisition window member changed
|
||||
bool m_isChanged = false;
|
||||
bool m_isCMSInRange = false;
|
||||
bool m_isBatteryLow = false;
|
||||
std::string m_sErrorMessage = "";
|
||||
|
||||
// The window is changed in a idle loop in function of m_isCMSInRange,
|
||||
// m_isBatteryLow and m_sErrorMessage that change in the driver loop
|
||||
// A mutex is necessary to secure the access to the data
|
||||
std::mutex m_mutex;
|
||||
} SInformationWindow;
|
||||
|
||||
/**
|
||||
* \class CDriverBioSemiActiveTwo
|
||||
* \author Mensia Technologies
|
||||
*/
|
||||
class CDriverBioSemiActiveTwo final : public IDriver
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CDriverBioSemiActiveTwo(IDriverContext& ctx);
|
||||
void release() { delete this; }
|
||||
const char* getName() override { return "BioSemi Active Two (MkI and MkII)"; }
|
||||
|
||||
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
|
||||
bool uninitialize() override;
|
||||
|
||||
bool start() override;
|
||||
bool stop() override;
|
||||
bool loop() override;
|
||||
|
||||
bool isConfigurable() override { return true; }
|
||||
bool configure() override;
|
||||
const IHeader* getHeader() override { return &m_header; }
|
||||
//size_t getChannelCount();
|
||||
|
||||
// Called from gtk callback
|
||||
void setupInformationWindow();
|
||||
|
||||
protected:
|
||||
|
||||
SettingsHelper m_settings;
|
||||
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
CHeader m_header;
|
||||
|
||||
std::vector<uint32_t> m_impedances;
|
||||
std::vector<float> m_samples;
|
||||
|
||||
CStimulationSet m_stimSet;
|
||||
|
||||
CBridgeBioSemiActiveTwo m_bridge;
|
||||
|
||||
std::vector<bool> m_triggers;
|
||||
uint64_t m_nSample = 0;
|
||||
|
||||
bool m_bCMCurrentlyInRange = false;
|
||||
bool m_bBatteryCurrentlyOk = false;
|
||||
uint32_t m_lastWarningTime = 0;
|
||||
// Used to determine for how long CMS was in range
|
||||
uint32_t m_lastCmsBackInRange = 0;
|
||||
uint32_t m_lastBatteryLow = 0;
|
||||
// True if CMS is in range for more than 100ms
|
||||
bool m_bCMSBackInRange = true;
|
||||
bool m_bBatteryBackOk = true;
|
||||
uint32_t m_startTime = 0;
|
||||
bool m_useEXChannel = false;
|
||||
// Set to true
|
||||
bool m_acquisitionStopped = false;
|
||||
|
||||
SInformationWindow* m_infoWindow = nullptr;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyBioSemiAPI
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* ovasCConfigurationBrainProductsActiCHamp.cpp
|
||||
*
|
||||
* Copyright (c) 2012, Mensia Technologies SA. All rights reserved.
|
||||
* -- Rights transferred to Inria, contract signed 21.11.2014
|
||||
*
|
||||
*/
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyActiCHampAPI
|
||||
|
||||
#include "ovasCConfigurationBrainProductsActiCHamp.h"
|
||||
|
||||
#include "ovasIHeader.h"
|
||||
|
||||
#include <actichamp.h>
|
||||
#include <system/ovCTime.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <shellapi.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
static void DeviceChangedCB(GtkComboBox* /*comboBox*/, gpointer data)
|
||||
{
|
||||
reinterpret_cast<CConfigurationBrainProductsActiCHamp*>(data)->comboBoxDeviceChangedCB();
|
||||
}
|
||||
|
||||
static void ModuleToggledCB(GtkToggleButton* button, gpointer data)
|
||||
{
|
||||
reinterpret_cast<CConfigurationBrainProductsActiCHamp*>(data)->buttonModuleToggledCB(gtk_toggle_button_get_active(button) != 0);
|
||||
}
|
||||
|
||||
static void AUXChannelsToggledCB(GtkToggleButton* button, gpointer data)
|
||||
{
|
||||
reinterpret_cast<CConfigurationBrainProductsActiCHamp*>(data)->buttonAuxChannelsToggledCB(gtk_toggle_button_get_active(button) != 0);
|
||||
}
|
||||
|
||||
CConfigurationBrainProductsActiCHamp::CConfigurationBrainProductsActiCHamp(
|
||||
const char* gtkBuilderFilename, uint32_t& deviceID, uint32_t& mode, uint32_t& physicalSampling, uint32_t& adcDataFilter,
|
||||
uint32_t& adcDataDecimation, uint32_t& activeShieldGain, uint32_t& moduleEnabled, bool& useAuxChannels, uint32_t& goodImpedanceLimit,
|
||||
uint32_t& badImpedanceLimit)
|
||||
: CConfigurationBuilder(gtkBuilderFilename), m_deviceID(deviceID), m_mode(mode), m_physicalSampling(physicalSampling),
|
||||
m_adcDataFilter(adcDataFilter)
|
||||
, m_adcDataDecimation(adcDataDecimation), m_activeShieldGain(activeShieldGain), m_moduleEnabled(moduleEnabled), m_useAuxChannels(useAuxChannels)
|
||||
, m_goodImpedanceLimit(goodImpedanceLimit), m_badImpedanceLimit(badImpedanceLimit) {}
|
||||
|
||||
namespace {
|
||||
void open_url_brainproducts_download_actichamp_fw_cb(GtkWidget* /*widget*/, gpointer /*data*/)
|
||||
{
|
||||
ShellExecute(nullptr, nullptr, "http://www.brainproducts.com/files/public/downloads/actiCHamp_rev03_FW-Update.zip", nullptr, nullptr, SW_SHOW);
|
||||
}
|
||||
}
|
||||
|
||||
bool CConfigurationBrainProductsActiCHamp::preConfigure()
|
||||
{
|
||||
if (!CConfigurationBuilder::preConfigure()) { return false; }
|
||||
|
||||
m_imageErrorIcon = GTK_WIDGET(gtk_builder_get_object(m_builder, "image_error_icon"));
|
||||
m_labelErrorMessage = GTK_LABEL(gtk_builder_get_object(m_builder, "label_error_message"));
|
||||
m_buttonErrorDLLLink = GTK_BUTTON(gtk_builder_get_object(m_builder, "button_bp_actichamp_download_firmware"));
|
||||
m_comboBoxDeviceId = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_device_id"));
|
||||
m_comboBoxMode = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_mode"));
|
||||
m_comboBoxPhysicalSampleRate = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_physical_sample_rate"));
|
||||
m_comboBoxADCDataFilter = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_adc_data_filter"));
|
||||
m_comboBoxADCDataDecimation = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_adc_data_decimation"));
|
||||
m_buttonActiveShieldGain = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_active_shield_gain"));
|
||||
m_buttonUseAuxChannels = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_aux_channels"));
|
||||
m_buttonGoodImpedanceLimit = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_good_imp"));
|
||||
m_buttonBadImpedanceLimit = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_bad_imp"));
|
||||
|
||||
m_module1 = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "togglebutton_module_1"));
|
||||
m_module2 = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "togglebutton_module_2"));
|
||||
m_module3 = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "togglebutton_module_3"));
|
||||
m_module4 = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "togglebutton_module_4"));
|
||||
m_module5 = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "togglebutton_module_5"));
|
||||
|
||||
g_signal_connect(G_OBJECT(m_module1), "toggled", G_CALLBACK(ModuleToggledCB), this);
|
||||
g_signal_connect(G_OBJECT(m_module2), "toggled", G_CALLBACK(ModuleToggledCB), this);
|
||||
g_signal_connect(G_OBJECT(m_module3), "toggled", G_CALLBACK(ModuleToggledCB), this);
|
||||
g_signal_connect(G_OBJECT(m_module4), "toggled", G_CALLBACK(ModuleToggledCB), this);
|
||||
g_signal_connect(G_OBJECT(m_module5), "toggled", G_CALLBACK(ModuleToggledCB), this);
|
||||
|
||||
g_signal_connect(G_OBJECT(m_buttonUseAuxChannels), "toggled", G_CALLBACK(AUXChannelsToggledCB), this);
|
||||
g_signal_connect(G_OBJECT(m_buttonErrorDLLLink), "clicked", G_CALLBACK(open_url_brainproducts_download_actichamp_fw_cb), nullptr);
|
||||
|
||||
g_signal_connect(G_OBJECT(m_comboBoxDeviceId), "changed", G_CALLBACK(DeviceChangedCB), this);
|
||||
|
||||
const uint32_t nDevice = uint32_t(champGetCount());
|
||||
bool selected = false;
|
||||
|
||||
// autodetection of the connected device(s)
|
||||
for (uint32_t i = 0; i < nDevice; ++i)
|
||||
{
|
||||
gtk_combo_box_append_text(m_comboBoxDeviceId, ("Device " + std::to_string(i)).c_str());
|
||||
{
|
||||
if (m_deviceID == i)
|
||||
{
|
||||
gtk_combo_box_set_active(m_comboBoxDeviceId, i);
|
||||
selected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nDevice == 0) { gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_nChannels), 0); }
|
||||
|
||||
if (!selected && nDevice != 0) { gtk_combo_box_set_active(m_comboBoxDeviceId, 0); }
|
||||
|
||||
gtk_combo_box_set_active(m_comboBoxMode, m_mode);
|
||||
gtk_combo_box_set_active(m_comboBoxPhysicalSampleRate, m_physicalSampling);
|
||||
gtk_combo_box_set_active(m_comboBoxADCDataFilter, m_adcDataFilter);
|
||||
gtk_combo_box_set_active(m_comboBoxADCDataDecimation, (m_adcDataDecimation == CHAMP_DECIMATION_2 ? 1 : 0));
|
||||
gtk_spin_button_set_value(m_buttonActiveShieldGain, m_activeShieldGain);
|
||||
gtk_spin_button_set_value(m_buttonGoodImpedanceLimit, m_goodImpedanceLimit);
|
||||
gtk_spin_button_set_value(m_buttonBadImpedanceLimit, m_badImpedanceLimit);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CConfigurationBrainProductsActiCHamp::postConfigure()
|
||||
{
|
||||
GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_device"));
|
||||
|
||||
if (m_applyConfig)
|
||||
{
|
||||
gtk_spin_button_update(GTK_SPIN_BUTTON(m_buttonActiveShieldGain));
|
||||
gtk_spin_button_update(GTK_SPIN_BUTTON(m_buttonGoodImpedanceLimit));
|
||||
gtk_spin_button_update(GTK_SPIN_BUTTON(m_buttonBadImpedanceLimit));
|
||||
|
||||
m_deviceID = gtk_combo_box_get_active(m_comboBoxDeviceId);
|
||||
m_mode = gtk_combo_box_get_active(m_comboBoxMode);
|
||||
m_physicalSampling = gtk_combo_box_get_active(m_comboBoxPhysicalSampleRate);
|
||||
m_adcDataFilter = gtk_combo_box_get_active((m_comboBoxADCDataFilter));
|
||||
m_adcDataDecimation = ((gtk_combo_box_get_active(m_comboBoxADCDataDecimation) == 1) ? CHAMP_DECIMATION_2 : CHAMP_DECIMATION_0
|
||||
); // The decimation enum gives FACTOR_2 = 2 and not 1.
|
||||
m_activeShieldGain = uint32_t(gtk_spin_button_get_value(m_buttonActiveShieldGain));
|
||||
m_goodImpedanceLimit = uint32_t(gtk_spin_button_get_value(m_buttonGoodImpedanceLimit));
|
||||
m_badImpedanceLimit = uint32_t(gtk_spin_button_get_value(m_buttonBadImpedanceLimit));
|
||||
m_useAuxChannels = gtk_toggle_button_get_active(m_buttonUseAuxChannels) != 0;
|
||||
m_moduleEnabled = 0x01
|
||||
| (gtk_toggle_button_get_active(m_module1) ? 0x02 : 0x00)
|
||||
| (gtk_toggle_button_get_active(m_module2) ? 0x04 : 0x00)
|
||||
| (gtk_toggle_button_get_active(m_module3) ? 0x08 : 0x00)
|
||||
| (gtk_toggle_button_get_active(m_module4) ? 0x10 : 0x00)
|
||||
| (gtk_toggle_button_get_active(m_module5) ? 0x20 : 0x00);
|
||||
}
|
||||
|
||||
if (!CConfigurationBuilder::postConfigure()) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
void CConfigurationBrainProductsActiCHamp::comboBoxDeviceChangedCB()
|
||||
{
|
||||
void* handle = nullptr;
|
||||
const uint32_t deviceID = gtk_combo_box_get_active(m_comboBoxDeviceId);
|
||||
|
||||
// retry opening the device several times, as sometimes the champOpen fails for no reason
|
||||
|
||||
int retriesCount = 0;
|
||||
while (retriesCount++ < 1)
|
||||
{
|
||||
handle = champOpen(deviceID);
|
||||
if (handle == nullptr) { System::Time::sleep(500); }
|
||||
else { break; }
|
||||
}
|
||||
// Opens device
|
||||
if (handle != nullptr)
|
||||
{
|
||||
// Clear error messages
|
||||
gtk_widget_hide(m_imageErrorIcon);
|
||||
gtk_widget_hide(GTK_WIDGET(m_labelErrorMessage));
|
||||
gtk_widget_hide(GTK_WIDGET(m_buttonErrorDLLLink));
|
||||
|
||||
// Gets properties
|
||||
t_champProperty properties;
|
||||
champGetProperty(handle, &properties);
|
||||
|
||||
// Sets channel count
|
||||
//m_header->setSamplingFrequency(uint32_t(l_oProperties.Rate));
|
||||
//m_header->setChannelCount(l_oProperties.CountEeg + properties.CountAux);
|
||||
//gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_nChannels), properties.CountEeg + properties.CountAux);
|
||||
|
||||
// The channel count is updated with the toggled button callbacks.
|
||||
m_header->setChannelCount(0);
|
||||
|
||||
// Gets modules
|
||||
t_champModules modules;
|
||||
champGetModules(handle, &modules);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(m_module1), (modules.Present & 0x02) ? TRUE : FALSE);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(m_module2), (modules.Present & 0x04) ? TRUE : FALSE);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(m_module3), (modules.Present & 0x08) ? TRUE : FALSE);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(m_module4), (modules.Present & 0x10) ? TRUE : FALSE);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(m_module5), (modules.Present & 0x20) ? TRUE : FALSE);
|
||||
|
||||
|
||||
// Since the toggle_button method fires the event we have to clean up all boutons
|
||||
// and the channel count before actually activating the modules
|
||||
gtk_toggle_button_set_active(m_module1, FALSE);
|
||||
gtk_toggle_button_set_active(m_module2, FALSE);
|
||||
gtk_toggle_button_set_active(m_module3, FALSE);
|
||||
gtk_toggle_button_set_active(m_module4, FALSE);
|
||||
gtk_toggle_button_set_active(m_module5, FALSE);
|
||||
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_nChannels), 0);
|
||||
|
||||
gtk_toggle_button_set_active(m_module1, (m_moduleEnabled & 0x02 && modules.Present & 0x02) ? TRUE : FALSE); // calls the moduleToggledCB.
|
||||
gtk_toggle_button_set_active(m_module2, (m_moduleEnabled & 0x04 && modules.Present & 0x04) ? TRUE : FALSE); // calls the moduleToggledCB.
|
||||
gtk_toggle_button_set_active(m_module3, (m_moduleEnabled & 0x08 && modules.Present & 0x08) ? TRUE : FALSE); // calls the moduleToggledCB.
|
||||
gtk_toggle_button_set_active(m_module4, (m_moduleEnabled & 0x10 && modules.Present & 0x10) ? TRUE : FALSE); // calls the moduleToggledCB.
|
||||
gtk_toggle_button_set_active(m_module5, (m_moduleEnabled & 0x20 && modules.Present & 0x20) ? TRUE : FALSE); // calls the moduleToggledCB.
|
||||
|
||||
//Aux channels
|
||||
gtk_toggle_button_set_active(m_buttonUseAuxChannels, m_useAuxChannels);
|
||||
|
||||
// TODO: champClose function returns an error code which is never ever checked,
|
||||
// closing a device can and does fail in some circumstances, but we can not really
|
||||
// do much about it.
|
||||
const int error = champClose(handle);
|
||||
if (error) { std::cerr << "Closing ActiCHamp device failed" << std::endl; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// The device failed to open, we disable all of the controls and display an error message
|
||||
gtk_widget_show(m_imageErrorIcon);
|
||||
gtk_widget_show(GTK_WIDGET(m_labelErrorMessage));
|
||||
gtk_widget_show(GTK_WIDGET(m_buttonErrorDLLLink));
|
||||
|
||||
m_header->setChannelCount(0);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_nChannels), 0);
|
||||
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(m_module1), FALSE);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(m_module2), FALSE);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(m_module3), FALSE);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(m_module4), FALSE);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(m_module5), FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
void CConfigurationBrainProductsActiCHamp::buttonModuleToggledCB(const bool state)
|
||||
{
|
||||
const uint32_t nChannel = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(m_nChannels));
|
||||
// there is minimum 1 channel.
|
||||
if (state) { gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_nChannels), nChannel + (nChannel == 1 ? 31 : 32)); }
|
||||
else { gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_nChannels), nChannel - (nChannel == 1 ? 31 : 32)); }
|
||||
}
|
||||
|
||||
void CConfigurationBrainProductsActiCHamp::buttonAuxChannelsToggledCB(const bool state)
|
||||
{
|
||||
const uint32_t nChannel = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(m_nChannels));
|
||||
|
||||
if (state) { gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_nChannels), nChannel + (nChannel == 1 ? 7 : 8)); }
|
||||
else { gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_nChannels), nChannel - (nChannel == 1 ? 7 : 8)); }
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_HAS_ThirdPartyActiCHampAPI
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* ovasCConfigurationBrainProductsActiCHamp.h
|
||||
*
|
||||
* Copyright (c) 2012, Mensia Technologies SA. All rights reserved.
|
||||
* -- Rights transferred to Inria, contract signed 21.11.2014
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyActiCHampAPI
|
||||
|
||||
#include "../ovasCConfigurationBuilder.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
class CConfigurationBrainProductsActiCHamp final : public CConfigurationBuilder
|
||||
{
|
||||
public:
|
||||
|
||||
CConfigurationBrainProductsActiCHamp(const char* gtkBuilderFilename, uint32_t& deviceID, uint32_t& mode, uint32_t& physicalSampling,
|
||||
uint32_t& adcDataFilter, uint32_t& adcDataDecimation, uint32_t& activeShieldGain, uint32_t& moduleEnabled,
|
||||
bool& useAuxChannels, uint32_t& goodImpedanceLimit, uint32_t& badImpedanceLimit);
|
||||
|
||||
bool preConfigure() override;
|
||||
bool postConfigure() override;
|
||||
|
||||
void comboBoxDeviceChangedCB();
|
||||
void buttonModuleToggledCB(bool state);
|
||||
void buttonAuxChannelsToggledCB(bool state);
|
||||
|
||||
protected:
|
||||
|
||||
uint32_t& m_deviceID;
|
||||
uint32_t& m_mode;
|
||||
uint32_t& m_physicalSampling;
|
||||
uint32_t& m_adcDataFilter;
|
||||
uint32_t& m_adcDataDecimation;
|
||||
uint32_t& m_activeShieldGain;
|
||||
uint32_t& m_moduleEnabled;
|
||||
bool& m_useAuxChannels;
|
||||
uint32_t& m_goodImpedanceLimit;
|
||||
uint32_t& m_badImpedanceLimit;
|
||||
|
||||
GtkWidget* m_imageErrorIcon = nullptr;
|
||||
GtkLabel* m_labelErrorMessage = nullptr;
|
||||
GtkButton* m_buttonErrorDLLLink = nullptr;
|
||||
GtkComboBox* m_comboBoxDeviceId = nullptr;
|
||||
GtkComboBox* m_comboBoxMode = nullptr;
|
||||
GtkComboBox* m_comboBoxPhysicalSampleRate = nullptr;
|
||||
GtkComboBox* m_comboBoxADCDataFilter = nullptr;
|
||||
GtkComboBox* m_comboBoxADCDataDecimation = nullptr;
|
||||
GtkSpinButton* m_buttonActiveShieldGain = nullptr;
|
||||
GtkToggleButton* m_buttonUseAuxChannels = nullptr;
|
||||
GtkSpinButton* m_buttonGoodImpedanceLimit = nullptr;
|
||||
GtkSpinButton* m_buttonBadImpedanceLimit = nullptr;
|
||||
|
||||
GtkToggleButton* m_module1 = nullptr;
|
||||
GtkToggleButton* m_module2 = nullptr;
|
||||
GtkToggleButton* m_module3 = nullptr;
|
||||
GtkToggleButton* m_module4 = nullptr;
|
||||
GtkToggleButton* m_module5 = nullptr;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyActiCHampAPI
|
||||
+777
@@ -0,0 +1,777 @@
|
||||
/*
|
||||
* ovasCDriverBrainProductsActiCHamp.cpp
|
||||
*
|
||||
* Copyright (c) 2012, Mensia Technologies SA. All rights reserved.
|
||||
* -- Rights transferred to Inria, contract signed 21.11.2014
|
||||
*
|
||||
*/
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyActiCHampAPI
|
||||
|
||||
#include "ovasCDriverBrainProductsActiCHamp.h"
|
||||
#include "ovasCConfigurationBrainProductsActiCHamp.h"
|
||||
|
||||
#include <toolkit/ovtk_all.h>
|
||||
#include <system/ovCTime.h>
|
||||
|
||||
#include <ActiChamp.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
namespace {
|
||||
typedef union
|
||||
{
|
||||
t_champDataModelAux ModelAux;
|
||||
t_champDataModel32 Model32;
|
||||
t_champDataModel64 Model64;
|
||||
t_champDataModel96 Model96;
|
||||
t_champDataModel128 Model128;
|
||||
t_champDataModel160 Model160;
|
||||
} UBuffer;
|
||||
|
||||
const uint32_t NB_MAX_BUFFER_IN_CACHE = 1000;
|
||||
UBuffer Buffer[NB_MAX_BUFFER_IN_CACHE];
|
||||
|
||||
// Low pass FIR filters before downsampling
|
||||
//
|
||||
// Computed with python and scipy
|
||||
// References :
|
||||
// http://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.tofile.html
|
||||
// http://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.firwin.html
|
||||
//
|
||||
// The following filters have roughly 50ms delay as described in "2.1.4 What is the
|
||||
// delay of a linear-phase FIR?" at http://www.dspguru.com/book/export/html/3 :
|
||||
// n = 500, Fs = 5000, delay = (n-1)/(2*Fs) = 0.04990
|
||||
// n = 1000, Fs = 10000, delay = (n-1)/(2*Fs) = 0.04995
|
||||
// n = 2500, Fs = 25000, delay = (n-1)/(2*Fs) = 0.04998
|
||||
// n = 5000, Fs = 50000, delay = (n-1)/(2*Fs) = 0.04999
|
||||
// n = 10000, Fs = 100000, delay = (n-1)/(2*Fs) = 0.049995
|
||||
//
|
||||
// In order to correct this delay, filtering should be done as a two steps process with a forward filter
|
||||
// followed by a backward filter. However, this leads to an n square complexity where a linear complexity is
|
||||
// sufficient in forward only filtering (using 100kHz input signal, n=10000 taps !)
|
||||
//
|
||||
// To avoid such complexity, it is chosen to antedate acquired samples by 50ms cheating the drift correction
|
||||
// process. Indeed, the acquisition server application monitors the drifting of the acquisition process and
|
||||
// corrects this drift upon demand. It is up to the driver to require this correction and it can be chosen not
|
||||
// to fit the 0 drift, but to fit an arbitrary fixed drift instead.
|
||||
//
|
||||
// The offset for this correction is stored in the m_nDriftOffsetSample variable
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------------------
|
||||
from scipy import signal
|
||||
N=500
|
||||
signal.firwin(N, cutoff=2048./(0.5*5000.), window='hamming').tofile("c:/openvibe/f64_5k_4096.bin")
|
||||
signal.firwin(N, cutoff=1024./(0.5*5000.), window='hamming').tofile("c:/openvibe/f64_5k_2048.bin")
|
||||
signal.firwin(N, cutoff= 512./(0.5*5000.), window='hamming').tofile("c:/openvibe/f64_5k_1024.bin")
|
||||
signal.firwin(N, cutoff= 256./(0.5*5000.), window='hamming').tofile("c:/openvibe/f64_5k_512.bin")
|
||||
signal.firwin(N, cutoff= 128./(0.5*5000.), window='hamming').tofile("c:/openvibe/f64_5k_256.bin")
|
||||
signal.firwin(N, cutoff= 64./(0.5*5000.), window='hamming').tofile("c:/openvibe/f64_5k_128.bin")
|
||||
|
||||
N=1000
|
||||
signal.firwin(N, cutoff=2048./(0.5*10000.), window='hamming').tofile("c:/openvibe/f64_10k_4096.bin")
|
||||
signal.firwin(N, cutoff=1024./(0.5*10000.), window='hamming').tofile("c:/openvibe/f64_10k_2048.bin")
|
||||
signal.firwin(N, cutoff= 512./(0.5*10000.), window='hamming').tofile("c:/openvibe/f64_10k_1024.bin")
|
||||
signal.firwin(N, cutoff= 256./(0.5*10000.), window='hamming').tofile("c:/openvibe/f64_10k_512.bin")
|
||||
signal.firwin(N, cutoff= 128./(0.5*10000.), window='hamming').tofile("c:/openvibe/f64_10k_256.bin")
|
||||
signal.firwin(N, cutoff= 64./(0.5*10000.), window='hamming').tofile("c:/openvibe/f64_10k_128.bin")
|
||||
|
||||
N=2500
|
||||
signal.firwin(N, cutoff=2048./(0.5*25000.), window='hamming').tofile("c:/openvibe/f64_25k_4096.bin")
|
||||
signal.firwin(N, cutoff=1024./(0.5*25000.), window='hamming').tofile("c:/openvibe/f64_25k_2048.bin")
|
||||
signal.firwin(N, cutoff= 512./(0.5*25000.), window='hamming').tofile("c:/openvibe/f64_25k_1024.bin")
|
||||
signal.firwin(N, cutoff= 256./(0.5*25000.), window='hamming').tofile("c:/openvibe/f64_25k_512.bin")
|
||||
signal.firwin(N, cutoff= 128./(0.5*25000.), window='hamming').tofile("c:/openvibe/f64_25k_256.bin")
|
||||
signal.firwin(N, cutoff= 64./(0.5*25000.), window='hamming').tofile("c:/openvibe/f64_25k_128.bin")
|
||||
|
||||
N=5000
|
||||
signal.firwin(N, cutoff=2048./(0.5*50000.), window='hamming').tofile("c:/openvibe/f64_50k_4096.bin")
|
||||
signal.firwin(N, cutoff=1024./(0.5*50000.), window='hamming').tofile("c:/openvibe/f64_50k_2048.bin")
|
||||
signal.firwin(N, cutoff= 512./(0.5*50000.), window='hamming').tofile("c:/openvibe/f64_50k_1024.bin")
|
||||
signal.firwin(N, cutoff= 256./(0.5*50000.), window='hamming').tofile("c:/openvibe/f64_50k_512.bin")
|
||||
signal.firwin(N, cutoff= 128./(0.5*50000.), window='hamming').tofile("c:/openvibe/f64_50k_256.bin")
|
||||
signal.firwin(N, cutoff= 64./(0.5*50000.), window='hamming').tofile("c:/openvibe/f64_50k_128.bin")
|
||||
|
||||
N=10000
|
||||
signal.firwin(N, cutoff=2048./(0.5*100000.), window='hamming').tofile("c:/openvibe/f64_100k_4096.bin")
|
||||
signal.firwin(N, cutoff=1024./(0.5*100000.), window='hamming').tofile("c:/openvibe/f64_100k_2048.bin")
|
||||
signal.firwin(N, cutoff= 512./(0.5*100000.), window='hamming').tofile("c:/openvibe/f64_100k_1024.bin")
|
||||
signal.firwin(N, cutoff= 256./(0.5*100000.), window='hamming').tofile("c:/openvibe/f64_100k_512.bin")
|
||||
signal.firwin(N, cutoff= 128./(0.5*100000.), window='hamming').tofile("c:/openvibe/f64_100k_256.bin")
|
||||
signal.firwin(N, cutoff= 64./(0.5*100000.), window='hamming').tofile("c:/openvibe/f64_100k_128.bin")
|
||||
--------------------------------------------------------------------------------------------------------------- */
|
||||
|
||||
bool loadFilter(const char* filename, std::vector<double>& vFilter)
|
||||
{
|
||||
FILE* file = fopen(filename, "rb");
|
||||
if (!file)
|
||||
{
|
||||
vFilter.clear();
|
||||
vFilter.push_back(1);
|
||||
return false;
|
||||
}
|
||||
fseek(file, 0, SEEK_END);
|
||||
const size_t len = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
vFilter.resize(len / sizeof(double));
|
||||
fread(&vFilter[0], len, 1, file);
|
||||
fclose(file);
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
CDriverBrainProductsActiCHamp::CDriverBrainProductsActiCHamp(IDriverContext& ctx)
|
||||
: IDriver(ctx), m_mode(CHAMP_MODE_ACTIVE_SHIELD), m_adcDataDecimation(CHAMP_DECIMATION_2)
|
||||
, m_activeShieldGain(5) // default value from pyCorder = 5/100
|
||||
, m_moduleEnabled(0xffffffff)
|
||||
{
|
||||
t_champVersion version;
|
||||
champGetVersion(nullptr, &version);
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Got actiCHamp version (Dll:" << size_t(version.Dll) << ")\n";
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Number of attached devices : " << size_t(champGetCount()) << "\n";
|
||||
|
||||
m_header.setSamplingFrequency(512);
|
||||
m_header.setChannelCount(32);
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverBrainProductsActiCHamp::initialize(const uint32_t /*nSamplePerSentBlock*/, IDriverCallback& callback)
|
||||
{
|
||||
if (m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
// Opens device
|
||||
// We change the working directory to be sure to have ActiChamp.bit in it
|
||||
if (!SetCurrentDirectory(Directories::getBinDir()))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Can not change current working directory!" << m_deviceID << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
while (count++ < 5)
|
||||
{
|
||||
m_handle = champOpen(m_deviceID);
|
||||
if (m_handle == nullptr)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Failed to open the device, retrying (" << count << ")" << "\n";
|
||||
System::Time::sleep(500);
|
||||
}
|
||||
else { break; }
|
||||
}
|
||||
|
||||
if (m_handle == nullptr)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Can not open actiCHamp device id " << m_deviceID << "\n";
|
||||
return false;
|
||||
}
|
||||
// set back to previous working directory
|
||||
if (!SetCurrentDirectory(Directories::getDistRootDir()))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Can not change current working directory!" << m_deviceID << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Gets versions
|
||||
t_champVersion version;
|
||||
champGetVersion(m_handle, &version);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Got actiCHamp version (Dll:" << size_t(version.Dll) << ", Driver:" <<
|
||||
size_t(version.Driver) << ", Cypress:" << size_t(version.Cypress) << ", Fpga:" << size_t(version.Fpga) << ", Msp430:" <<
|
||||
size_t(version.Msp430) << ")\n";
|
||||
|
||||
// Gets battery voltages
|
||||
t_champVoltages voltage;
|
||||
if (champGetVoltages(m_handle, &voltage))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not get voltages - Got error [" << getError() << "]\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
int state = 0;
|
||||
if (voltage.VDC < 5.5) { state = 1; }
|
||||
if (voltage.VDC < 5.2) { state = 2; }
|
||||
switch (state)
|
||||
{
|
||||
case 0: m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Battery level is fine (" << float(voltage.VDC) << ")\n";
|
||||
break;
|
||||
case 1: m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Battery level is critical (" << float(voltage.VDC) << ")\n";
|
||||
break;
|
||||
case 2: m_driverCtx.getLogManager() << Kernel::LogLevel_ImportantWarning << "Battery level is bad (" << float(voltage.VDC) << ")\n";
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
// Gets and sets modules status
|
||||
t_champModules modules;
|
||||
champGetModules(m_handle, &modules);
|
||||
modules.Enabled = m_moduleEnabled;
|
||||
champSetModules(m_handle, &modules);
|
||||
champGetModules(m_handle, &modules);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Got modules status : Present=" << modules.Present << " / Enabled=" << modules.Enabled << "\n";
|
||||
|
||||
// Active shield
|
||||
if (champSetActiveShieldGain(m_handle, m_activeShieldGain))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not set active shield gain - Got error [" << getError() << "]\n";
|
||||
champClose(m_handle);
|
||||
m_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Active Shield Gain set to [" << m_activeShieldGain << "]\n";
|
||||
|
||||
// Sets settings to acquisition state
|
||||
t_champSettingsEx oSettings;
|
||||
oSettings.Mode = t_champMode(m_mode);
|
||||
oSettings.Rate = t_champRate(m_physicalSampling);
|
||||
oSettings.AdcFilter = t_champAdcFilter(m_adcDataFilter);
|
||||
oSettings.Decimation = t_champDecimation(m_adcDataDecimation);
|
||||
if (champSetSettingsEx(m_handle, &oSettings))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not set settings - Got error [" << getError() << "]\n";
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Acquisition Settings set to [Mode: " << m_mode << "] [PhysicalSamplingRate: " <<
|
||||
m_physicalSampling << "] [ADCDataFilter: " << m_adcDataFilter << "] [ADCDataDecimation: " << m_adcDataDecimation << "]\n";
|
||||
|
||||
// Gets properties
|
||||
t_champProperty properties;
|
||||
if (champGetProperty(m_handle, &properties))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not get properties - Got error [" << getError() << "]\n";
|
||||
champClose(m_handle);
|
||||
m_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sets channel count
|
||||
// as the modules were set before according to the configuration, the property structure
|
||||
// contains the right number of channels.
|
||||
m_nEEG = properties.CountEeg;
|
||||
m_nAux = (m_useAuxChannels ? properties.CountAux : 0);
|
||||
m_nChannel = m_nEEG + m_nAux;
|
||||
if (m_nChannel == 0)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "No channels were selected [EEG: " << m_nEEG << "] [AUX: " << m_nAux <<
|
||||
"]. Close device.\n";
|
||||
champClose(m_handle);
|
||||
m_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Channel counts are [EEG: " << m_nEEG << "] [AUX: " << m_nAux << "]\n";
|
||||
|
||||
m_header.setChannelCount(m_nChannel);
|
||||
|
||||
m_impedances.resize(m_nEEG + 1);
|
||||
m_samples.resize(m_nChannel);
|
||||
m_resolutions.resize(m_nChannel);
|
||||
|
||||
m_physicalSamplingHz = uint32_t(properties.Rate);
|
||||
m_counterStep = (uint64_t(m_header.getSamplingFrequency()) << 32) / uint64_t(m_physicalSamplingHz);
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Sampling rate in Hz [Physical: " << m_physicalSamplingHz << "] [Driver: " << m_header.
|
||||
getSamplingFrequency() << "]\n";
|
||||
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < m_nEEG; i++, j++) { m_resolutions[j] = properties.ResolutionEeg * 1E6f; } // converts to µV
|
||||
for (size_t i = 0; i < m_nAux; i++, j++) { m_resolutions[j] = properties.ResolutionAux * 1E6f; } // converts to µV
|
||||
|
||||
//@TODO ue of i & j are normals ?
|
||||
j = 0;
|
||||
for (size_t i = 0; i < m_nEEG; i++, j++) { m_header.setChannelUnits(i, OVTK_UNIT_Volts, OVTK_FACTOR_Micro); }
|
||||
for (size_t i = 0; i < m_nAux; i++, j++) { m_header.setChannelUnits(i, OVTK_UNIT_Volts, OVTK_FACTOR_Micro); } // converts to µV
|
||||
|
||||
// Sets data pointers
|
||||
// the amplifier model is depending on the number of channels, always including AUX
|
||||
switch ((m_useAuxChannels ? m_nChannel : m_nChannel + 8))
|
||||
{
|
||||
case 8: m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Data Model is [AUX]\n";
|
||||
m_pAux = Buffer[0].ModelAux.Aux;
|
||||
m_eeg = nullptr; // Buffer[0].ModelAux.Main;
|
||||
m_triggers = &Buffer[0].ModelAux.Triggers;
|
||||
m_size = sizeof(Buffer[0].ModelAux);
|
||||
break;
|
||||
|
||||
case 32 + 8: m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Data Model is [Model32]\n";
|
||||
m_pAux = Buffer[0].Model32.Aux;
|
||||
m_eeg = Buffer[0].Model32.Main;
|
||||
m_triggers = &Buffer[0].Model32.Triggers;
|
||||
m_size = sizeof(Buffer[0].Model32);
|
||||
break;
|
||||
|
||||
case 64 + 8: m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Data Model is [Model64]\n";
|
||||
m_pAux = Buffer[0].Model64.Aux;
|
||||
m_eeg = Buffer[0].Model64.Main;
|
||||
m_triggers = &Buffer[0].Model64.Triggers;
|
||||
m_size = sizeof(Buffer[0].Model64);
|
||||
break;
|
||||
|
||||
case 96 + 8: m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Data Model is [Model96]\n";
|
||||
m_pAux = Buffer[0].Model96.Aux;
|
||||
m_eeg = Buffer[0].Model96.Main;
|
||||
m_triggers = &Buffer[0].Model96.Triggers;
|
||||
m_size = sizeof(Buffer[0].Model96);
|
||||
break;
|
||||
|
||||
case 128 + 8: m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Data Model is [Model128]\n";
|
||||
m_pAux = Buffer[0].Model128.Aux;
|
||||
m_eeg = Buffer[0].Model128.Main;
|
||||
m_triggers = &Buffer[0].Model128.Triggers;
|
||||
m_size = sizeof(Buffer[0].Model128);
|
||||
break;
|
||||
|
||||
case 160 + 8: m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Data Model is [Model160]\n";
|
||||
m_pAux = Buffer[0].Model160.Aux;
|
||||
m_eeg = Buffer[0].Model160.Main;
|
||||
m_triggers = &Buffer[0].Model160.Triggers;
|
||||
m_size = sizeof(Buffer[0].Model160);
|
||||
break;
|
||||
|
||||
default: m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Data Model unknown.\n";
|
||||
m_pAux = nullptr;
|
||||
m_eeg = nullptr;
|
||||
m_triggers = nullptr;
|
||||
m_size = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_size == 0)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unknown device model " << m_nEEG << " EEG channels / " << m_nAux <<
|
||||
" Auxiliary channels\n";
|
||||
champClose(m_handle);
|
||||
m_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Device model " << m_nEEG << " EEG channels / " << m_nAux << " Auxiliary channels\n";
|
||||
|
||||
// Prepares low pass filter
|
||||
|
||||
#define __set_filter__(f,f_decim) \
|
||||
if(m_adcDataDecimation == CHAMP_DECIMATION_0) \
|
||||
{ \
|
||||
if(!loadFilter(Directories::getDataDir() + "/applications/acquisition-server/filters/" f ".bin", m_filters)) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to load filter !\n"; } \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
if(!loadFilter(Directories::getDataDir() + "/applications/acquisition-server/filters/" f_decim ".bin", m_filters)) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to load filter !\n"; } \
|
||||
}\
|
||||
valid = true;
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Setting up the FIR filter for signal decimation (physical rate > driver rate).\n";
|
||||
|
||||
bool valid = false;
|
||||
switch (m_header.getSamplingFrequency())
|
||||
{
|
||||
case 128: switch (m_physicalSampling)
|
||||
{
|
||||
case CHAMP_RATE_10KHZ: __set_filter__("f64_10k_128", "f64_5k_128");
|
||||
break;
|
||||
case CHAMP_RATE_50KHZ: __set_filter__("f64_50k_128", "f64_25k_128");
|
||||
break;
|
||||
case CHAMP_RATE_100KHZ: __set_filter__("f64_100k_128", "f64_50k_128");
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 256: switch (m_physicalSampling)
|
||||
{
|
||||
case CHAMP_RATE_10KHZ: __set_filter__("f64_10k_256", "f64_5k_256");
|
||||
break;
|
||||
case CHAMP_RATE_50KHZ: __set_filter__("f64_50k_256", "f64_25k_256");
|
||||
break;
|
||||
case CHAMP_RATE_100KHZ: __set_filter__("f64_100k_256", "f64_50k_256");
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 512: switch (m_physicalSampling)
|
||||
{
|
||||
case CHAMP_RATE_10KHZ: __set_filter__("f64_10k_512", "f64_5k_512");
|
||||
break;
|
||||
case CHAMP_RATE_50KHZ: __set_filter__("f64_50k_512", "f64_25k_512");
|
||||
break;
|
||||
case CHAMP_RATE_100KHZ: __set_filter__("f64_100k_512", "f64_50k_512");
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1024: switch (m_physicalSampling)
|
||||
{
|
||||
case CHAMP_RATE_10KHZ: __set_filter__("f64_10k_1024", "f64_5k_1024");
|
||||
break;
|
||||
case CHAMP_RATE_50KHZ: __set_filter__("f64_50k_1024", "f64_25k_1024");
|
||||
break;
|
||||
case CHAMP_RATE_100KHZ: __set_filter__("f64_100k_1024", "f64_50k_1024");
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2048: switch (m_physicalSampling)
|
||||
{
|
||||
case CHAMP_RATE_10KHZ: __set_filter__("f64_10k_2048", "f64_5k_2048");
|
||||
break;
|
||||
case CHAMP_RATE_50KHZ: __set_filter__("f64_50k_2048", "f64_25k_2048");
|
||||
break;
|
||||
case CHAMP_RATE_100KHZ: __set_filter__("f64_100k_2048", "f64_50k_2048");
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4096: switch (m_physicalSampling)
|
||||
{
|
||||
case CHAMP_RATE_10KHZ: __set_filter__("f64_10k_4096", "f64_5k_4096");
|
||||
break;
|
||||
case CHAMP_RATE_50KHZ: __set_filter__("f64_50k_4096", "f64_25k_4096");
|
||||
break;
|
||||
case CHAMP_RATE_100KHZ: __set_filter__("f64_100k_4096", "f64_50k_4096");
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
#if 0
|
||||
__set_filter__("f64_none");
|
||||
__set_filter__("f64_10k_12");
|
||||
__set_filter__("f64_10k_256");
|
||||
#endif
|
||||
|
||||
#undef __set_filter__
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unhandled soft sampling frequency / physical sampling frequency configuration [" << m_header.
|
||||
getSamplingFrequency() << ", " << m_physicalSampling << "]\n";
|
||||
champClose(m_handle);
|
||||
m_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prepares cache for filtering
|
||||
m_sampleCaches.clear();
|
||||
for (size_t i = 0; i < m_filters.size(); ++i) { m_sampleCaches.push_back(m_samples); }
|
||||
|
||||
m_callback = &callback;
|
||||
m_counter = 0;
|
||||
|
||||
// Setting the inner latency as described in the beginning of the file
|
||||
m_nDriftOffsetSample = int64_t(m_header.getSamplingFrequency() * 50) / 1000;
|
||||
m_driverCtx.setInnerLatencySampleCount(-m_nDriftOffsetSample);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Driver inner latency set to 50ms to compensate FIR filtering.\n";
|
||||
|
||||
if (m_driverCtx.isImpedanceCheckRequested())
|
||||
{
|
||||
// Sets settings manually for impedance check
|
||||
t_champSettingsEx settings;
|
||||
settings.Mode = CHAMP_MODE_IMPEDANCE;
|
||||
settings.Rate = CHAMP_RATE_10KHZ;
|
||||
settings.AdcFilter = CHAMP_ADC_NATIVE;
|
||||
settings.Decimation = CHAMP_DECIMATION_0;
|
||||
if (champSetSettingsEx(m_handle, &settings))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not set settings - Got error [" << getError() << "]\n";
|
||||
champClose(m_handle);
|
||||
m_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Impedance check is requested. Setting the acquisition to [CHAMP_MODE_IMPEDANCE].\n";
|
||||
|
||||
// Sets impedance setup manually
|
||||
t_champImpedanceSetup setup;
|
||||
setup.Good = m_goodImpedanceLimit;// 5000; // 5kOhm
|
||||
setup.Bad = m_badImpedanceLimit; //10000; // 10kOhm
|
||||
setup.LedsDisable = 0;
|
||||
setup.TimeOut = 5;
|
||||
if (champImpedanceSetSetup(m_handle, &setup))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not set impedance setup - Got error [" << getError() << "]\n";
|
||||
champClose(m_handle);
|
||||
m_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Impedance limits are set to [GREEN < " << m_goodImpedanceLimit << "Ohm < YELLOW < " <<
|
||||
m_badImpedanceLimit << "Ohm < RED].\n";
|
||||
|
||||
// Gets/Sets impedance mode
|
||||
t_champImpedanceMode mode;
|
||||
if (champImpedanceGetMode(m_handle, &mode))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not get impedance mode - Got error [" << getError() << "]\n";
|
||||
champClose(m_handle);
|
||||
m_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
mode.Splitter = mode.Splitters;
|
||||
if (champImpedanceSetMode(m_handle, &mode))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not set impedance mode - Got error [" << getError() << "]\n";
|
||||
champClose(m_handle);
|
||||
m_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Impedance splitters set to [" << size_t(mode.Splitters) << "]\n";
|
||||
|
||||
// Starts impedance check
|
||||
if (champStart(m_handle))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Can not start device id " << m_deviceID << " - Got error [" << getError() << "]\n";
|
||||
champClose(m_handle);
|
||||
m_handle = nullptr;
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Impedance check started...\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBrainProductsActiCHamp::start()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
if (m_driverCtx.isImpedanceCheckRequested()) { champStop(m_handle); }
|
||||
|
||||
// Sets settings to acquisition state
|
||||
t_champSettingsEx settings;
|
||||
settings.Mode = t_champMode(m_mode);
|
||||
settings.Rate = t_champRate(m_physicalSampling);
|
||||
settings.AdcFilter = t_champAdcFilter(m_adcDataFilter);
|
||||
settings.Decimation = t_champDecimation(m_adcDataDecimation);
|
||||
if (champSetSettingsEx(m_handle, &settings))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not set settings - Got error [" << getError() << "]\n";
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Acquisition Settings set to [Mode: " << m_mode << "] [PhysicalSamplingRate: " <<
|
||||
m_physicalSampling << "] [ADCDataFilter: " << m_adcDataFilter << "] [ADCDataDecimation: " << m_adcDataDecimation << "]\n";
|
||||
|
||||
// Starts acquisition
|
||||
if (champStart(m_handle))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Can not start device id " << m_deviceID << " - Got error [" << getError() << "]\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Acquisition started...\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBrainProductsActiCHamp::loop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
if (m_driverCtx.isStarted())
|
||||
{
|
||||
t_champDataStatus dataStatus;
|
||||
champGetDataStatus(m_handle, &dataStatus);
|
||||
//m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Status : Samples:" << size_t(l_oDataStatus.Samples) << " Errors:" << size_t(l_oDataStatus.Errors) << " Rate:" << float(l_oDataStatus.Rate) << " Speed:" << float(l_oDataStatus.Speed) << "\n";
|
||||
|
||||
// Reads all the data.
|
||||
// Buffers are aligned : with one call to champGetData we get as much buffers as possible
|
||||
int code;
|
||||
while ((code = champGetData(m_handle, &Buffer[0], m_size * NB_MAX_BUFFER_IN_CACHE)) > 0)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Received " << float(code) / m_size << " new buffers with champGetData.\n";
|
||||
|
||||
for (int buf = 0; buf < int(code / m_size); ++buf)
|
||||
{
|
||||
uint32_t i, j = 0;
|
||||
|
||||
// we find the good pointers (m_eeg and m_pAux being on the first buffer)
|
||||
// Size of an union in memory is the size of the biggest possible type
|
||||
signed int* aux = m_pAux + buf * (m_size / sizeof(signed int));
|
||||
signed int* eeg = m_eeg + buf * (m_size / sizeof(signed int));
|
||||
// m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "AUX ptr = " << (uint32_t)l_pAux << " EEG ptr = "<< (uint32_t)l_pEEG <<"\n";
|
||||
|
||||
// Scales data according to respective resolution
|
||||
for (i = 0; i < m_nEEG; i++, j++) { m_samples[j] = m_resolutions[j] * float(eeg[i]); }
|
||||
if (m_useAuxChannels) { for (i = 0; i < m_nAux; i++, j++) { m_samples[j] = m_resolutions[j] * float(aux[i]); } }
|
||||
//m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Samples saved in cache.\n";
|
||||
|
||||
// Updates cache
|
||||
//m_sampleCaches.erase(m_sampleCaches.begin());
|
||||
m_sampleCaches.pop_front();
|
||||
m_sampleCaches.push_back(m_samples);
|
||||
m_nSample++;
|
||||
|
||||
// Downsamples and sends samples only when relevant
|
||||
m_counter += m_counterStep;
|
||||
if (m_counter >= (1LL << 32))
|
||||
{
|
||||
//m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Enough samples received to build a new buffer. Applying FIR filter.\n";
|
||||
m_counter -= (1LL << 32);
|
||||
|
||||
// Filters last samples
|
||||
for (i = 0; i < m_samples.size(); ++i)
|
||||
{
|
||||
m_samples[i] = 0;
|
||||
auto it = m_sampleCaches.begin();
|
||||
for (j = 0; j < m_filters.size(); ++j)
|
||||
{
|
||||
//m_samples[i]+=m_filters[j]*m_sampleCaches[j][i];
|
||||
m_samples[i] += float(m_filters[j] * (*it)[i]);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
//m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Samples ready for sending.\n";
|
||||
m_callback->setSamples(&m_samples[0], 1);
|
||||
m_callback->setStimulationSet(m_stimSet);
|
||||
m_stimSet.clear();
|
||||
|
||||
// Saves the date of this sample for further use with the triggers
|
||||
m_lastSampleDate += uint64_t((1LL << 32) / m_header.getSamplingFrequency());
|
||||
m_nSample = 0;
|
||||
//m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Sending one sample.\n";
|
||||
}
|
||||
|
||||
//m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Getting trigger...\n";
|
||||
|
||||
// we save the MyButton state and detect if it switched
|
||||
bool bSwitch = m_bMyButtonstate;
|
||||
uint32_t* trigger = m_triggers + buf * (m_size / sizeof(uint32_t));
|
||||
m_bMyButtonstate = ((*trigger & 0x80000000) >> 31) == 1;
|
||||
bSwitch = (bSwitch ? !m_bMyButtonstate : m_bMyButtonstate);
|
||||
|
||||
// Decomposes the trigger bytes:
|
||||
// Digital inputs (bits 0 - 7) + output (bits 8 - 15) state + 15 MSB reserved bits + MyButton bit (bit 31)
|
||||
// apply a mask to silence any modification on the output and reserved parts
|
||||
(*trigger) &= 0x800000ff;
|
||||
|
||||
// Now we can test for modification on the interesting part (input triggers and MyButton)
|
||||
if (*trigger != m_uiLastTriggers)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Current input triggers state [";
|
||||
m_driverCtx.getLogManager() << ((*trigger & 0x00000001)) << " ";
|
||||
m_driverCtx.getLogManager() << ((*trigger & 0x00000002) >> 1) << " ";
|
||||
m_driverCtx.getLogManager() << ((*trigger & 0x00000004) >> 2) << " ";
|
||||
m_driverCtx.getLogManager() << ((*trigger & 0x00000008) >> 3) << " ";
|
||||
m_driverCtx.getLogManager() << ((*trigger & 0x00000010) >> 4) << " ";
|
||||
m_driverCtx.getLogManager() << ((*trigger & 0x00000020) >> 5) << " ";
|
||||
m_driverCtx.getLogManager() << ((*trigger & 0x00000040) >> 6) << " ";
|
||||
m_driverCtx.getLogManager() << ((*trigger & 0x00000080) >> 7) << "] MyButton [";
|
||||
m_driverCtx.getLogManager() << m_bMyButtonstate << "]\n";
|
||||
|
||||
// Every time that a trigger has changed, we send a stimulation.
|
||||
// code of stimulation = OVTK_StimulationId_LabelStart + value of the trigger bytes.
|
||||
// This supposes that the user knows the structure of the triggers bit by bit and can process it in a Lua box for example.
|
||||
// The value received is in [0-255]
|
||||
// The MyButton bit is set to 0 to ensure this range.
|
||||
|
||||
// The date is relative to the last buffer start time (cf the setSamples before setStimulationSet)
|
||||
const uint64_t date = m_nSample * (1LL << 32) / m_physicalSamplingHz;
|
||||
m_stimSet.appendStimulation(OVTK_StimulationId_Label((*trigger)&0x000000ff), date, 0);
|
||||
|
||||
// special case : MyButton
|
||||
// The MyButton trigger bit is too far in the trigger bytes to have a valid value
|
||||
// We use 2 dedicated stims OVTK_StimulationId_Button1_Pressed and OVTK_StimulationId_Button1_Released
|
||||
if (bSwitch)
|
||||
{
|
||||
m_stimSet.appendStimulation((m_bMyButtonstate ? OVTK_StimulationId_Button1_Pressed : OVTK_StimulationId_Button1_Released), date, 0);
|
||||
}
|
||||
}
|
||||
m_uiLastTriggers = *trigger;
|
||||
}
|
||||
}
|
||||
if (code < 0)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "An error occured with last sample request - Got error [" << code << "]\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// As described in at the beginning of this file, the drift
|
||||
// correction process is altered to consider the ~ 50ms delay
|
||||
// of the single way filtering process
|
||||
// Inner latency was set accordingly.
|
||||
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_driverCtx.isImpedanceCheckRequested())
|
||||
{
|
||||
// Reads impedances
|
||||
if (champImpedanceGetData(m_handle, &m_impedances[0], m_impedances.size() * sizeof(uint32_t)))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Can not read impedances on device id " << m_deviceID << " - Got error [" <<
|
||||
getError() << "]\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Updates impedances
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Impedances are [ ";
|
||||
for (size_t j = 0; j < m_nEEG; ++j)
|
||||
{
|
||||
m_driverCtx.updateImpedance(j, m_impedances[j]);
|
||||
m_driverCtx.getLogManager() << m_impedances[j] << " ";
|
||||
}
|
||||
m_driverCtx.getLogManager() << "\n";
|
||||
|
||||
// Drops data
|
||||
while ((champGetData(m_handle, &Buffer, m_size * NB_MAX_BUFFER_IN_CACHE)) > 0) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBrainProductsActiCHamp::stop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
|
||||
if (!m_driverCtx.isImpedanceCheckRequested()) { champStop(m_handle); }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBrainProductsActiCHamp::uninitialize()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
if (m_driverCtx.isImpedanceCheckRequested()) { champStop(m_handle); }
|
||||
|
||||
champClose(m_handle);
|
||||
m_handle = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverBrainProductsActiCHamp::configure()
|
||||
{
|
||||
CConfigurationBrainProductsActiCHamp config(Directories::getDataDir() + "/applications/acquisition-server/interface-BrainProducts-ActiCHamp.ui", m_deviceID,
|
||||
m_mode, m_physicalSampling, m_adcDataFilter, m_adcDataDecimation, m_activeShieldGain, m_moduleEnabled,
|
||||
m_useAuxChannels, m_goodImpedanceLimit, m_badImpedanceLimit);
|
||||
return config.configure(m_header);
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
CString CDriverBrainProductsActiCHamp::getError(int* code) const
|
||||
{
|
||||
int iCode = 0;
|
||||
int* pCode = (code ? code : &iCode);
|
||||
if (!m_handle) { return ""; }
|
||||
char buffer[1024];
|
||||
buffer[0] = '\0';
|
||||
champGetError(m_handle, pCode, buffer, sizeof(buffer));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyActiCHampAPI
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* ovasCDriverBrainProductsActiCHamp.h
|
||||
*
|
||||
* Copyright (c) 2012, Mensia Technologies SA. All rights reserved.
|
||||
* -- Rights transferred to Inria, contract signed 21.11.2014
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyActiCHampAPI
|
||||
|
||||
#include "ovasIDriver.h"
|
||||
#include "../ovasCHeader.h"
|
||||
|
||||
#include <deque>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CDriverBrainProductsActiCHamp
|
||||
* \author Mensia Technologies
|
||||
*/
|
||||
class CDriverBrainProductsActiCHamp final : public IDriver
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CDriverBrainProductsActiCHamp(IDriverContext& ctx);
|
||||
void release() { delete this; }
|
||||
const char* getName() override { return "Brain Products actiCHamp"; }
|
||||
|
||||
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
|
||||
bool uninitialize() override;
|
||||
|
||||
bool start() override;
|
||||
bool stop() override;
|
||||
bool loop() override;
|
||||
|
||||
bool isConfigurable() override { return true; }
|
||||
bool configure() override;
|
||||
const IHeader* getHeader() override { return &m_header; }
|
||||
|
||||
CString getError(int* code = nullptr) const;
|
||||
|
||||
protected:
|
||||
|
||||
void* m_handle = nullptr;
|
||||
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
CHeader m_header;
|
||||
|
||||
std::vector<uint32_t> m_impedances;
|
||||
std::vector<float> m_samples;
|
||||
std::vector<float> m_resolutions;
|
||||
//std::vector < std::vector < float > > m_sampleCaches;
|
||||
std::deque<std::vector<float>> m_sampleCaches;
|
||||
uint32_t m_sampleCacheIdx = 0;
|
||||
std::vector<double> m_filters;
|
||||
|
||||
CStimulationSet m_stimSet;
|
||||
|
||||
uint32_t m_deviceID = 0;
|
||||
uint32_t m_mode = 0;
|
||||
uint32_t m_physicalSampling = 0;
|
||||
uint32_t m_adcDataFilter = 0;
|
||||
uint32_t m_adcDataDecimation = 0;
|
||||
uint32_t m_activeShieldGain = 0;
|
||||
uint32_t m_moduleEnabled = 0;
|
||||
bool m_useAuxChannels = false;
|
||||
|
||||
uint32_t m_physicalSamplingHz = 0;
|
||||
|
||||
uint32_t m_nChannel = 0;
|
||||
uint32_t m_nEEG = 0;
|
||||
uint32_t m_nAux = 0;
|
||||
uint32_t m_nTriggersIn = 0;
|
||||
|
||||
uint64_t m_counter = 0;
|
||||
uint64_t m_counterStep = 0;
|
||||
uint64_t m_nSample = 0;
|
||||
int64_t m_nDriftOffsetSample = 0;
|
||||
|
||||
uint32_t m_goodImpedanceLimit = 5000;
|
||||
uint32_t m_badImpedanceLimit = 10000;
|
||||
|
||||
signed int* m_pAux = nullptr;
|
||||
signed int* m_eeg = nullptr;
|
||||
uint32_t* m_triggers = nullptr;
|
||||
uint32_t m_size = 0;
|
||||
|
||||
uint32_t m_uiLastTriggers = 0;
|
||||
uint64_t m_lastSampleDate = 0;
|
||||
|
||||
bool m_bMyButtonstate = false;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyActiCHampAPI
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Brain Products Brainamp Series driver for OpenViBE
|
||||
* Copyright (C) 2010 INRIA - Author : Yann Renard
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ovasCConfigurationBrainProductsBrainampSeries.h"
|
||||
|
||||
#include "ovasIHeader.h"
|
||||
|
||||
#if defined TARGET_OS_Windows
|
||||
|
||||
#include "ovasCDriverBrainProductsBrainampSeries.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <windows.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
|
||||
static EParameter toIndex(const uint32_t value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case 1: return DecimationFactor_None;
|
||||
case 2: return DecimationFactor_2;
|
||||
case 4: return DecimationFactor_4;
|
||||
case 5: return DecimationFactor_5;
|
||||
case 8: return DecimationFactor_8;
|
||||
case 10: return DecimationFactor_10;
|
||||
default: return DecimationFactor_None;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t toValue(const EParameter parameter)
|
||||
{
|
||||
switch (parameter)
|
||||
{
|
||||
case DecimationFactor_None: return 1;
|
||||
case DecimationFactor_2: return 2;
|
||||
case DecimationFactor_4: return 4;
|
||||
case DecimationFactor_5: return 5;
|
||||
case DecimationFactor_8: return 8;
|
||||
case DecimationFactor_10: return 10;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void ChannelDetailsPressedCB(GtkButton* /*button*/, void* data)
|
||||
{
|
||||
static_cast<CConfigurationBrainProductsBrainampSeries*>(data)->buttonChannelDetailsPressedCB();
|
||||
}
|
||||
|
||||
static void ComboboxDeviceChangedCB(GtkComboBox* /*comboBox*/, void* data)
|
||||
{
|
||||
static_cast<CConfigurationBrainProductsBrainampSeries*>(data)->comboBoxDeviceChangedCB();
|
||||
}
|
||||
|
||||
CConfigurationBrainProductsBrainampSeries::CConfigurationBrainProductsBrainampSeries(
|
||||
CDriverBrainProductsBrainampSeries& driver, const char* gtkBuilderFileName, uint32_t& usbIdx, uint32_t& decimationFactor,
|
||||
EParameter* channelSelected, EParameter* lowPassFilterFull, EParameter* resolutionFull, EParameter* dcCouplingFull,
|
||||
EParameter& lowPass, EParameter& resolution, EParameter& dcCoupling, EParameter& impedance)
|
||||
: CConfigurationBuilder(gtkBuilderFileName), m_driver(driver), m_usbIdx(usbIdx),
|
||||
m_decimationFactor(decimationFactor), m_channelSelected(channelSelected),
|
||||
m_lowPassFilterFull(lowPassFilterFull), m_resolutionFull(resolutionFull),
|
||||
m_dcCouplingFull(dcCouplingFull), m_lowPass(lowPass), m_resolution(resolution),
|
||||
m_dcCoupling(dcCoupling), m_impedance(impedance) {}
|
||||
|
||||
bool CConfigurationBrainProductsBrainampSeries::preConfigure()
|
||||
{
|
||||
if (!CConfigurationBuilder::preConfigure()) { return false; }
|
||||
|
||||
GtkComboBox* device = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_device"));
|
||||
GtkComboBox* lowPass = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_lowpass_filter"));
|
||||
GtkComboBox* decimation = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_decimation_factor"));
|
||||
GtkComboBox* resolution = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_resolution"));
|
||||
GtkComboBox* dcCoupling = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_dc_coupling"));
|
||||
GtkComboBox* impedance = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_impedance"));
|
||||
|
||||
g_signal_connect(gtk_builder_get_object(m_builder, "button_channel_details"), "pressed", G_CALLBACK(ChannelDetailsPressedCB), this);
|
||||
g_signal_connect(gtk_builder_get_object(m_builder, "combobox_device"), "changed", G_CALLBACK(ComboboxDeviceChangedCB), this);
|
||||
|
||||
char buffer[1024];
|
||||
int count = 0;
|
||||
bool selected = false;
|
||||
|
||||
// autodetection of the connected device(s)
|
||||
for (uint32_t i = 0; i < 16; ++i)
|
||||
{
|
||||
if (m_driver.getDeviceDetails(i, nullptr, nullptr))
|
||||
{
|
||||
sprintf(buffer, "USB port %i", i);
|
||||
gtk_combo_box_append_text(device, buffer);
|
||||
{
|
||||
if (m_usbIdx == i)
|
||||
{
|
||||
gtk_combo_box_set_active(device, count);
|
||||
selected = true;
|
||||
}
|
||||
}
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!selected && count != 0) { gtk_combo_box_set_active(device, 0); }
|
||||
|
||||
gtk_combo_box_set_active(lowPass, m_lowPass);
|
||||
gtk_combo_box_set_active(decimation, toIndex(m_decimationFactor));
|
||||
gtk_combo_box_set_active(resolution, m_resolution);
|
||||
gtk_combo_box_set_active(dcCoupling, m_dcCoupling);
|
||||
gtk_combo_box_set_active(impedance, m_impedance);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CConfigurationBrainProductsBrainampSeries::postConfigure()
|
||||
{
|
||||
GtkComboBox* comboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_device"));
|
||||
|
||||
if (m_applyConfig)
|
||||
{
|
||||
GtkComboBox* device = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_device"));
|
||||
GtkComboBox* lowPass = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_lowpass_filter"));
|
||||
GtkComboBox* decimation = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_decimation_factor"));
|
||||
GtkComboBox* resolution = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_resolution"));
|
||||
GtkComboBox* dcCoupling = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_dc_coupling"));
|
||||
GtkComboBox* impedance = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_impedance"));
|
||||
|
||||
uint32_t usbIdx = 0;
|
||||
const char* usbIndex = gtk_combo_box_get_active_text(device);
|
||||
if (usbIndex) { if (sscanf(usbIndex, "USB port %i", &usbIdx) == 1) { m_usbIdx = usbIdx; } }
|
||||
|
||||
m_lowPass = EParameter(gtk_combo_box_get_active(lowPass));
|
||||
m_decimationFactor = (toValue(EParameter(gtk_combo_box_get_active(decimation))));
|
||||
m_resolution = EParameter(gtk_combo_box_get_active(resolution));
|
||||
m_dcCoupling = EParameter(gtk_combo_box_get_active(dcCoupling));
|
||||
m_impedance = EParameter(gtk_combo_box_get_active(impedance));
|
||||
}
|
||||
|
||||
if (!CConfigurationBuilder::postConfigure()) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
void CConfigurationBrainProductsBrainampSeries::buttonChannelDetailsPressedCB()
|
||||
{
|
||||
GtkBuilder* builder = gtk_builder_new();
|
||||
gtk_builder_add_from_file(builder, m_gtkBuilderFilename.c_str(), nullptr);
|
||||
|
||||
GtkDialog* dialogChannelDetails = GTK_DIALOG(gtk_builder_get_object(builder, "dialog_channel_details"));
|
||||
GtkTable* dialogChannelDetailsTable = GTK_TABLE(gtk_builder_get_object(builder, "table_content"));
|
||||
|
||||
GtkTreeModel* treeModelLowPassFilterFull = GTK_TREE_MODEL(gtk_builder_get_object(builder, "model_lowpass_filter_full"));
|
||||
GtkTreeModel* treeModelResolutionFull = GTK_TREE_MODEL(gtk_builder_get_object(builder, "model_resolution_full"));
|
||||
GtkTreeModel* treeModelDcCouplingFull = GTK_TREE_MODEL(gtk_builder_get_object(builder, "model_dc_coupling_full"));
|
||||
|
||||
gtk_table_resize(dialogChannelDetailsTable, 1, 5);
|
||||
gtk_table_resize(dialogChannelDetailsTable, 1 + m_header->getChannelCount(), 5);
|
||||
for (size_t i = 1; i < 1 + m_header->getChannelCount(); ++i)
|
||||
{
|
||||
GtkLabel* widgetIdx = GTK_LABEL(gtk_label_new((std::to_string(i) + ":").c_str()));
|
||||
gtk_label_set_justify(widgetIdx, GTK_JUSTIFY_LEFT);
|
||||
gtk_widget_show(GTK_WIDGET(widgetIdx));
|
||||
|
||||
GtkLabel* widgetChannelName = GTK_LABEL(gtk_label_new(m_header->getChannelName(i-1)));
|
||||
gtk_label_set_justify(widgetChannelName, GTK_JUSTIFY_LEFT);
|
||||
// We hide this label as the channel name is not accurate
|
||||
// The update of the channel rename configuration (moved to the main application)
|
||||
// has broken this feature in the driver: real channel names are not passed to the driver
|
||||
// but are only kept at the server level. This problem should be adressed in future version.
|
||||
gtk_widget_hide(GTK_WIDGET(widgetChannelName));
|
||||
//::gtk_widget_show(GTK_WIDGET(l_pWidgetChannelName));
|
||||
|
||||
GtkBox* widgetChannel = GTK_BOX(gtk_hbox_new(false, 2));
|
||||
gtk_box_pack_start(widgetChannel, GTK_WIDGET(widgetIdx), true, true, 0);
|
||||
gtk_box_pack_start(widgetChannel, GTK_WIDGET(widgetChannelName), true, true, 0);
|
||||
gtk_widget_show(GTK_WIDGET(widgetChannel));
|
||||
gtk_table_attach_defaults(dialogChannelDetailsTable, GTK_WIDGET(widgetChannel), 0, 1, i, i + 1);
|
||||
|
||||
GtkToggleButton* widgetChannelSelected = GTK_TOGGLE_BUTTON(gtk_check_button_new());
|
||||
gtk_toggle_button_set_active(widgetChannelSelected, m_channelSelected[i - 1] == Channel_Selected);
|
||||
gtk_widget_show(GTK_WIDGET(widgetChannelSelected));
|
||||
gtk_table_attach_defaults(dialogChannelDetailsTable, GTK_WIDGET(widgetChannelSelected), 1, 2, i, i + 1);
|
||||
|
||||
GtkComboBox* widgetLowPassFilterFull = GTK_COMBO_BOX(gtk_combo_box_new_text());
|
||||
gtk_combo_box_set_model(widgetLowPassFilterFull, treeModelLowPassFilterFull);
|
||||
gtk_combo_box_set_active(widgetLowPassFilterFull, gint(m_lowPassFilterFull[i - 1] + 1));
|
||||
gtk_widget_show(GTK_WIDGET(widgetLowPassFilterFull));
|
||||
gtk_table_attach_defaults(dialogChannelDetailsTable, GTK_WIDGET(widgetLowPassFilterFull), 2, 3, i, i + 1);
|
||||
|
||||
GtkComboBox* widgetResolutionFull = GTK_COMBO_BOX(gtk_combo_box_new_text());
|
||||
gtk_combo_box_set_model(widgetResolutionFull, treeModelResolutionFull);
|
||||
gtk_combo_box_set_active(widgetResolutionFull, gint(m_resolutionFull[i - 1] + 1));
|
||||
gtk_widget_show(GTK_WIDGET(widgetResolutionFull));
|
||||
gtk_table_attach_defaults(dialogChannelDetailsTable, GTK_WIDGET(widgetResolutionFull), 3, 4, i, i + 1);
|
||||
|
||||
GtkComboBox* widgetDcCouplingFull = GTK_COMBO_BOX(gtk_combo_box_new_text());
|
||||
gtk_combo_box_set_model(widgetDcCouplingFull, treeModelDcCouplingFull);
|
||||
gtk_combo_box_set_active(widgetDcCouplingFull, gint(m_dcCouplingFull[i - 1] + 1));
|
||||
gtk_widget_show(GTK_WIDGET(widgetDcCouplingFull));
|
||||
gtk_table_attach_defaults(dialogChannelDetailsTable, GTK_WIDGET(widgetDcCouplingFull), 4, 5, i, i + 1);
|
||||
}
|
||||
|
||||
if (gtk_dialog_run(dialogChannelDetails) == GTK_RESPONSE_APPLY)
|
||||
{
|
||||
for (GList* list = dialogChannelDetailsTable->children; list; list = list->next)
|
||||
{
|
||||
GtkTableChild* tableChild = (GtkTableChild*)list->data;
|
||||
if (tableChild)
|
||||
{
|
||||
const gint i = tableChild->top_attach - 1;
|
||||
if (i >= 0)
|
||||
{
|
||||
switch (tableChild->left_attach)
|
||||
{
|
||||
case 1: m_channelSelected[i] = EParameter(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tableChild->widget)));
|
||||
break;
|
||||
case 2: m_lowPassFilterFull[i] = EParameter(gtk_combo_box_get_active(GTK_COMBO_BOX(tableChild->widget)) - 1);
|
||||
break;
|
||||
case 3: m_resolutionFull[i] = EParameter(gtk_combo_box_get_active(GTK_COMBO_BOX(tableChild->widget)) - 1);
|
||||
break;
|
||||
case 4: m_dcCouplingFull[i] = EParameter(gtk_combo_box_get_active(GTK_COMBO_BOX(tableChild->widget)) - 1);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gtk_widget_hide(GTK_WIDGET(dialogChannelDetails));
|
||||
|
||||
g_object_unref(builder);
|
||||
}
|
||||
|
||||
void CConfigurationBrainProductsBrainampSeries::comboBoxDeviceChangedCB()
|
||||
{
|
||||
GtkComboBox* comboBoxDevice = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_device"));
|
||||
const char* usbIndex = gtk_combo_box_get_active_text(comboBoxDevice);
|
||||
if (usbIndex)
|
||||
{
|
||||
uint32_t usbIdx = -1;
|
||||
if (sscanf(usbIndex, "USB port %i", &usbIdx) == 1)
|
||||
{
|
||||
uint32_t amplifierType[4];
|
||||
uint32_t nAmplifier = 0;
|
||||
if (m_driver.getDeviceDetails(usbIdx, &nAmplifier, amplifierType))
|
||||
{
|
||||
bool correctMontage = true;
|
||||
m_header->setChannelCount(nAmplifier * 32);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_nChannels), nAmplifier * 32);
|
||||
|
||||
for (uint32_t i = 0; i < BrainAmp_MaximumAmplifierCount; ++i)
|
||||
{
|
||||
const std::string name = "label_multiamp_montage_" + std::to_string(i + 1);
|
||||
GtkLabel* multiampMontage = GTK_LABEL(gtk_builder_get_object(m_builder, name.c_str()));
|
||||
gtk_label_set_label(multiampMontage, getDeviceType(amplifierType[i]));
|
||||
if (i >= 1 && amplifierType[i - 1] == AmplifierType_None && amplifierType[i] != AmplifierType_None) { correctMontage = false; }
|
||||
}
|
||||
|
||||
GtkWidget* montageTable = GTK_WIDGET(gtk_builder_get_object(m_builder, "table_multiamp_montage"));
|
||||
GtkWidget* montageIcon = GTK_WIDGET(gtk_builder_get_object(m_builder, "image_montage_status_icon"));
|
||||
if (correctMontage)
|
||||
{
|
||||
const char* text = "The multi amplifier montage is correct";
|
||||
gtk_image_set_from_stock(GTK_IMAGE(montageIcon), GTK_STOCK_YES, GTK_ICON_SIZE_BUTTON);
|
||||
gtk_widget_set_tooltip_markup(montageIcon, text);
|
||||
gtk_widget_set_tooltip_markup(montageTable, text);
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* text = "The multi amplifier montage is not correct - avoid empty slots before filled slots";
|
||||
gtk_image_set_from_stock(GTK_IMAGE(montageIcon), GTK_STOCK_NO, GTK_ICON_SIZE_BUTTON);
|
||||
gtk_widget_set_tooltip_markup(montageIcon, text);
|
||||
gtk_widget_set_tooltip_markup(montageTable, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_OS_Windows
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Brain Products Brainamp Series driver for OpenViBE
|
||||
* Copyright (C) 2010 INRIA - Author : Yann Renard
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../ovasCConfigurationBuilder.h"
|
||||
|
||||
#if defined TARGET_OS_Windows
|
||||
|
||||
#include "ovas_defines_brainamp_series.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
class CDriverBrainProductsBrainampSeries;
|
||||
|
||||
class CConfigurationBrainProductsBrainampSeries final : public CConfigurationBuilder
|
||||
{
|
||||
public:
|
||||
|
||||
CConfigurationBrainProductsBrainampSeries(
|
||||
CDriverBrainProductsBrainampSeries& driver, const char* gtkBuilderFileName, uint32_t& usbIdx, uint32_t& decimationFactor,
|
||||
EParameter* channelSelected, EParameter* lowPassFilterFull, EParameter* resolutionFull, EParameter* dcCouplingFull,
|
||||
EParameter& lowPass, EParameter& resolution, EParameter& dcCoupling, EParameter& impedance);
|
||||
|
||||
bool preConfigure() override;
|
||||
bool postConfigure() override;
|
||||
|
||||
void buttonChannelDetailsPressedCB();
|
||||
void comboBoxDeviceChangedCB();
|
||||
|
||||
protected:
|
||||
|
||||
CDriverBrainProductsBrainampSeries& m_driver;
|
||||
uint32_t& m_usbIdx;
|
||||
uint32_t& m_decimationFactor;
|
||||
EParameter* m_channelSelected = nullptr;
|
||||
EParameter* m_lowPassFilterFull = nullptr;
|
||||
EParameter* m_resolutionFull = nullptr;
|
||||
EParameter* m_dcCouplingFull = nullptr;
|
||||
EParameter& m_lowPass;
|
||||
EParameter& m_resolution;
|
||||
EParameter& m_dcCoupling;
|
||||
EParameter& m_impedance;
|
||||
|
||||
// private:
|
||||
// GtkWidget* m_calibrateDialog = nullptr;
|
||||
// bool m_calibrationDone = false;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_OS_Windows
|
||||
+621
@@ -0,0 +1,621 @@
|
||||
/*
|
||||
* Brain Products Brainamp Series driver for OpenViBE
|
||||
* Copyright (C) 2010 INRIA - Author : Yann Renard
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ovasCDriverBrainProductsBrainampSeries.h"
|
||||
|
||||
#if defined TARGET_OS_Windows
|
||||
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <system/ovCTime.h>
|
||||
|
||||
#include <list>
|
||||
|
||||
#include <windows.h>
|
||||
#include <WinIoCtl.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
char* CDriverBrainProductsBrainampSeries::getErrorMessage(const uint32_t error)
|
||||
{
|
||||
const uint32_t code = error & 0xffff;
|
||||
switch (code)
|
||||
{
|
||||
case 0: break;
|
||||
case 1: return
|
||||
"Connection between Brainamp and USB 2 Adapter / PCI is broken.\n"
|
||||
"Please check connectors, switch and battery power. After the\n"
|
||||
"connection is established and if you wish to continue the\n"
|
||||
"recording, please press the Start/Resume Recording button.\n"
|
||||
"If problem still persists, contact us at techsup@brainproducts.com";
|
||||
case 2: return
|
||||
"The voltage in the amplifier is too low!\n"
|
||||
"Check the batteries!";
|
||||
case 3: return
|
||||
"Could not establish communication with the amplifier.\n"
|
||||
"Check the connectors and the battery power!";
|
||||
case 4: return
|
||||
"Out of synch, Barker words missing!";
|
||||
case 5: return
|
||||
"Connection between USB 2 Adapter and Computer is broken.\n"
|
||||
"Monitoring or recording was interrupted. Please check\n"
|
||||
"the USB connectors. If problem still persists, contact\n"
|
||||
"us at techsup@brainproducts.com";
|
||||
default: return "Unknown Amplifier Error\n";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
const Kernel::ELogLevel LogLevel_TraceAPI = Kernel::LogLevel_None;
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
CDriverBrainProductsBrainampSeries::CDriverBrainProductsBrainampSeries(IDriverContext& ctx)
|
||||
: IDriver(ctx), m_settings("AcquisitionServer_Driver_BrainAmpSeries", m_driverCtx.getConfigurationManager()), m_headerAdapter(m_header, m_channelSelected)
|
||||
{
|
||||
// char* channelName[]={"Fp1", "Fp2", "F3", "F4", "C3", "C4", "P3", "P4", "O1", "O2", "F7", "F8", "T7", "T8", "P7", "P8", "Fz", "Cz", "Pz", "FC1", "FC2", "CP1", "CP2", "FC5", "FC6", "CP5", "CP6", "TP9", "TP10", "Eog", "Ekg1", "Ekg2", };
|
||||
//char* channelNameActiCap32 [] = { "Fp1", "Fp2", "F7", "F3", "Fz", "F4", "F8", "FC5", "FC1", "FC2", "FC6", "T7", "C3", "Cz", "C4", "T8", "TP9", "CP5", "CP1", "CP2", "CP6", "TP10", "P7", "P3", "Pz", "P4", "P8", "PO9", "O1", "Oz", "O2", "PO10" };
|
||||
//char* channelNameActiCap64 [] = {
|
||||
// "Fp1", "Fp2", "F7", "F3", "Fz", "F4", "F8", "FC5", "FC1", "FC2", "FC6", "T7", "C3", "Cz", "C4", "T8", "TP9", "CP5", "CP1", "CP2", "CP6", "TP10", "P7", "P3", "Pz", "P4", "P8", "PO9", "O1", "Oz", "O2", "PO10",
|
||||
// "AF7", "AF3", "AF4", "AF8", "F5", "F1", "F2", "F6", "FT9", "FT7", "FC3", "FC4", "FT8", "FT10", "C5", "C1", "C2", "C6", "TP7", "CP3", "CPz", "CP4", "TP8", "P5", "P1", "P2", "P6", "PO7", "PO3", "POz", "PO4", "PO8",
|
||||
//};
|
||||
char* channelNameActiCap128[] = {
|
||||
"Fp1", "Fp2", "F7", "F3", "Fz", "F4", "F8", "FC5", "FC1", "FC2", "FC6", "T7", "C3", "Cz", "C4", "T8", "TP9", "CP5", "CP1", "CP2", "CP6", "TP10", "P7",
|
||||
"P3", "Pz", "P4", "P8", "PO9", "O1", "Oz", "O2", "PO10",
|
||||
"AF7", "AF3", "AF4", "AF8", "F5", "F1", "F2", "F6", "FT9", "FT7", "FC3", "FC4", "FT8", "FT10", "C5", "C1", "C2", "C6", "TP7", "CP3", "CPz", "CP4",
|
||||
"TP8", "P5", "P1", "P2", "P6", "PO7", "PO3", "POz", "PO4", "PO8",
|
||||
"FPz", "F9", "AFF5h", "AFF1h", "AFF2h", "AFF6h", "F10", "FTT9h", "FTT7h", "FCC5h", "FCC3h", "FCC1h", "FCC2h", "FCC4h", "FCC6h", "FTT8h", "FTT10h",
|
||||
"TPP9h", "TPP7h", "CPP5h", "CPP3h", "CPP1h", "CPP2h", "CPP4h", "CPP6h", "TPP8h", "TPP10h", "POO9h", "POO1", "POO2", "POO10h", "Iz",
|
||||
"AFp1", "AFp2", "FFT9h", "FFT7h", "FFC5h", "FFC3h", "FFC1h", "FFC2h", "FFC4h", "FFC6h", "FFT8h", "FFT10h", "TTP7h", "CCP5h", "CCP3h", "CCP1h", "CCP2h",
|
||||
"CCP4h", "CCP6h", "TTP8h", "P9", "PPO9h", "PPO5h", "PPO1h", "PPO2h", "PPO6h", "PPO10h", "P10", "I1", "OI1h", "OI2h", "I2",
|
||||
};
|
||||
char** channelName = channelNameActiCap128;
|
||||
|
||||
uint32_t nAmplifier = 1;
|
||||
this->getDeviceDetails(m_usbIdx, &nAmplifier, nullptr);
|
||||
|
||||
m_header.setSamplingFrequency(500);
|
||||
m_header.setChannelCount(nAmplifier * 32);
|
||||
for (uint32_t i = 0; i < 256; ++i)
|
||||
{
|
||||
m_header.setChannelName(i, (i < sizeof(channelNameActiCap128) / sizeof(void*) ? channelName[i] : ""));
|
||||
m_channelSelected[i] = Channel_Selected;
|
||||
m_lowPassFilterFull[i] = Parameter_Default;
|
||||
m_resolutionFull[i] = Parameter_Default;
|
||||
m_dcCouplingFull[i] = Parameter_Default;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < 256; ++i) { m_header.setChannelUnits(i, OVTK_UNIT_Volts, OVTK_FACTOR_Micro); }
|
||||
|
||||
m_settings.add("Header", &m_header);
|
||||
m_settings.add("USBIndex", &m_usbIdx);
|
||||
m_settings.add("DecimationFactor", &m_decimationFactor);
|
||||
m_settings.add("ChannelSelected", m_channelSelected);
|
||||
m_settings.add("LowPassFilterFull", &m_lowPassFilterFull);
|
||||
m_settings.add("ResolutionFull", &m_resolutionFull);
|
||||
m_settings.add("DCCouplingFull", &m_dcCouplingFull);
|
||||
m_settings.add("LowPass", &m_lowPass);
|
||||
m_settings.add("Resolution", &m_resolution);
|
||||
m_settings.add("DCCoupling", &m_dcCoupling);
|
||||
m_settings.add("Impedance", &m_impedance);
|
||||
m_settings.load();
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverBrainProductsBrainampSeries::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
|
||||
{
|
||||
// #define nSamplePerSentBlock 20*5
|
||||
|
||||
|
||||
if (m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
// --
|
||||
const std::string name = "\\\\.\\BrainAmpUSB" + std::to_string(m_usbIdx);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Preparing device [" << name << "]\n";
|
||||
|
||||
// -- Gets amplifiers type
|
||||
|
||||
uint32_t nAmplifier = BrainAmp_MaximumAmplifierCount;
|
||||
uint32_t types[BrainAmp_MaximumAmplifierCount];
|
||||
if (!this->getDeviceDetails(m_usbIdx, &nAmplifier, types))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not get amplifier(s) type - got error " << size_t(GetLastError()) << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < BrainAmp_MaximumAmplifierCount; ++i)
|
||||
{
|
||||
CString type(getDeviceType(types[i]));
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << " - Amplifier " << i + 1 << " : " << type << "\n";
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Found " << nAmplifier << " amplifier(s)\n";
|
||||
|
||||
// -- Opens device
|
||||
|
||||
m_Device = ::CreateFile(name.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, nullptr);
|
||||
if (m_Device == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not open device [" << name << "]\n";
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Opened device [" << name << "]\n";
|
||||
|
||||
// -- Gets driver version from device
|
||||
|
||||
char driverVersion[1024];
|
||||
uint32_t version = 0;
|
||||
m_driverCtx.getLogManager() << LogLevel_TraceAPI << "BRAINAMP_DRIVERVERSION\n";
|
||||
if (!DeviceIoControl(m_Device, BRAINAMP_DRIVERVERSION, nullptr, 0, &version, sizeof(version), &m_nBytesReturned, nullptr))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not get driver version from device - got error " << size_t(GetLastError()) << "\n";
|
||||
CloseHandle(m_Device);
|
||||
return false;
|
||||
}
|
||||
const uint32_t versionPatch = version % 10000;
|
||||
const uint32_t versionMinor = (version % 1000000) / 10000;
|
||||
const uint32_t versionMajor = (version) / 1000000;
|
||||
sprintf(driverVersion, "%i.%02i.%04i", versionMajor, versionMinor, versionPatch);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Found driver version [" << CString(driverVersion) << "]\n";
|
||||
|
||||
// -- Gets serial number from device
|
||||
|
||||
char deviceSerialNumber[1024];
|
||||
uint32_t serialNumber = 0;
|
||||
m_driverCtx.getLogManager() << LogLevel_TraceAPI << "BRAINAMP_GET_SERIALNUMBER\n";
|
||||
if (!DeviceIoControl(m_Device, BRAINAMP_GET_SERIALNUMBER, nullptr, 0, &serialNumber, sizeof(serialNumber), &m_nBytesReturned,
|
||||
nullptr))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not get serial number from device - got error " << size_t(GetLastError()) << "\n";
|
||||
CloseHandle(m_Device);
|
||||
return false;
|
||||
}
|
||||
if (m_nBytesReturned != 0)
|
||||
{
|
||||
sprintf(deviceSerialNumber, "0x%08x", serialNumber);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Found serial number [" << CString(deviceSerialNumber) << "]\n";
|
||||
}
|
||||
|
||||
// -- Sets callibration settings
|
||||
|
||||
m_deviceCalibrationSettings = new CBrainampCalibrationSettings;
|
||||
// m_deviceCalibrationSettings->waveForm=WaveForm_Ramp;
|
||||
// m_deviceCalibrationSettings->waveForm=WaveForm_Triangle;
|
||||
// m_deviceCalibrationSettings->waveForm=WaveForm_Square;
|
||||
m_deviceCalibrationSettings->waveForm = WaveForm_SineWave;
|
||||
m_deviceCalibrationSettings->frequency = 5;
|
||||
|
||||
m_driverCtx.getLogManager() << LogLevel_TraceAPI << "BRAINAMP_CALIBRATION_SETTINGS\n";
|
||||
if (!DeviceIoControl(m_Device, BRAINAMP_CALIBRATION_SETTINGS, m_deviceCalibrationSettings, sizeof(CBrainampCalibrationSettings), nullptr, 0,
|
||||
&m_nBytesReturned, nullptr))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not set calibration settings - got error " << size_t(GetLastError()) << "\n";
|
||||
CloseHandle(m_Device);
|
||||
return false;
|
||||
}
|
||||
|
||||
// -- Configures device according to what has been enterd in the configuration dialog
|
||||
|
||||
bool lowPassWarning = false;
|
||||
float resolution[] = { 0.1F, 0.5F, 10.0F, 152.6F };
|
||||
|
||||
m_deviceSetup = new CBrainampSetup;
|
||||
memset(m_deviceSetup, 0, sizeof(CBrainampSetup));
|
||||
m_deviceSetup->nChannel = m_headerAdapter.getChannelCount();
|
||||
m_deviceSetup->holdValue = 0x0; // Value without trigger
|
||||
m_deviceSetup->nSamplePerSentBlock = nSamplePerSentBlock * m_decimationFactor;
|
||||
m_deviceSetup->lowImpedance = uint8_t(m_impedance);
|
||||
for (size_t j = 0, i = 0; i < m_header.getChannelCount(); ++i)
|
||||
{
|
||||
if (m_channelSelected[i] == Channel_Selected)
|
||||
{
|
||||
// Should I care about j being greater than
|
||||
m_deviceSetup->channelLookup[j] = i;
|
||||
m_deviceSetup->lowPassFilter[j] =
|
||||
uint8_t(m_lowPassFilterFull[i] == Parameter_Default ? m_lowPass : m_lowPassFilterFull[i]); // 0 - 1000Hz, 1 - 250Hz
|
||||
m_deviceSetup->resolution[j] =
|
||||
uint8_t(m_resolutionFull[i] == Parameter_Default ? m_resolution
|
||||
: m_resolutionFull[i]); // 0 - 100 nV, 1 - 500 nV, 2 - 10 uV, 3 - 152.6 uV
|
||||
m_deviceSetup->dcCoupling[j] = uint8_t(m_dcCouplingFull[i] == Parameter_Default ? m_dcCoupling : m_dcCouplingFull[i]); // 0 - AC, 1 - DC
|
||||
|
||||
m_resolutionFactor[j] = resolution[m_deviceSetup->resolution[j]];
|
||||
|
||||
if (!lowPassWarning && m_decimationFactor != 1 && (m_lowPass == LowPass_1000 || m_lowPassFilterFull[i] == LowPass_1000))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Using 1000 Hz low pass filter...\n";
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Using a decimation factor of " << m_decimationFactor << " (resulting in " <<
|
||||
5000 / m_decimationFactor << " Hz sampling rate)\n";
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "This is probably not safe. Signal quality will suffer.\n";
|
||||
lowPassWarning = true;
|
||||
}
|
||||
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
m_driverCtx.getLogManager() << LogLevel_TraceAPI << "BRAINAMP_SETUP\n";
|
||||
if (!DeviceIoControl(m_Device, BRAINAMP_SETUP, m_deviceSetup, sizeof(CBrainampSetup), nullptr, 0, &m_nBytesReturned, nullptr))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not send setup parameters to device - got error " << size_t(GetLastError()) << "\n";
|
||||
CloseHandle(m_Device);
|
||||
return false;
|
||||
}
|
||||
|
||||
// -- Allocates sample array and intermediate buffer
|
||||
|
||||
m_sample = new float[m_headerAdapter.getChannelCount() * nSamplePerSentBlock];
|
||||
m_buffer = new int16_t[(m_headerAdapter.getChannelCount() + 1) * nSamplePerSentBlock * m_decimationFactor];
|
||||
if (!m_sample || !m_buffer)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not allocate memory for sample array / intermediate buffer\n";
|
||||
delete [] m_sample;
|
||||
delete [] m_buffer;
|
||||
m_sample = nullptr;
|
||||
m_buffer = nullptr;
|
||||
CloseHandle(m_Device);
|
||||
return false;
|
||||
}
|
||||
|
||||
// -- Everything is up and ready to work
|
||||
|
||||
m_callback = &callback;
|
||||
m_nSamplePerSentBlock = nSamplePerSentBlock;
|
||||
m_marker = 0;
|
||||
|
||||
// -- Optionally starts impedance check
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Impedance " << (m_driverCtx.isImpedanceCheckRequested() ? "will" : "won't") << " be checked\n";
|
||||
if (m_driverCtx.isImpedanceCheckRequested()) { if (!this->startImpedanceCheck()) { return false; } }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBrainProductsBrainampSeries::start()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
// -- Optionally stops impedance check
|
||||
if (m_driverCtx.isImpedanceCheckRequested()) { if (!this->stopImpedanceCheck()) { return false; } }
|
||||
|
||||
// -- Configures pull up/down for triggers
|
||||
|
||||
uint16_t pullUp = 0;
|
||||
m_driverCtx.getLogManager() << LogLevel_TraceAPI << "BRAINAMP_DIGITALINPUT_PULL_UP\n";
|
||||
if (!DeviceIoControl(m_Device, BRAINAMP_DIGITALINPUT_PULL_UP, &pullUp, sizeof(pullUp), nullptr, 0, &m_nBytesReturned, nullptr))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not switch pull up\n";
|
||||
CloseHandle(m_Device);
|
||||
return false;
|
||||
}
|
||||
|
||||
// -- Starts acquisition
|
||||
|
||||
uint32_t type = uint32_t(AcquisitionType_EEG);
|
||||
// uint32_t type=uint32_t(AcquisitionType_TestSignal);
|
||||
m_driverCtx.getLogManager() << LogLevel_TraceAPI << "BRAINAMP_START\n";
|
||||
if (!DeviceIoControl(m_Device, BRAINAMP_START, &type, sizeof(uint32_t), nullptr, 0, &m_nBytesReturned, nullptr))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not start acquisition\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBrainProductsBrainampSeries::loop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected()) { return false; }
|
||||
if (!m_driverCtx.isStarted() && !m_driverCtx.isImpedanceCheckRequested()) { return true; }
|
||||
|
||||
|
||||
// -- Gets error code from device
|
||||
|
||||
uint32_t error = 0;
|
||||
m_driverCtx.getLogManager() << LogLevel_TraceAPI << "BRAINAMP_ERROR_STATE\n";
|
||||
if (!DeviceIoControl(m_Device, BRAINAMP_ERROR_STATE, nullptr, 0, &error, sizeof(error), &m_nBytesReturned, nullptr))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not retrieve error state\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// -- Checks error code
|
||||
|
||||
if (error != 0)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Received error state " << error << " from device\n";
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Full error text :\n" << this->getErrorMessage(error) << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// -- Loops until the buffer is read
|
||||
|
||||
do
|
||||
{
|
||||
// -- Reads intermediate buffer from device
|
||||
|
||||
if (!ReadFile(m_Device, m_buffer, (m_headerAdapter.getChannelCount() + 1) * m_nSamplePerSentBlock * m_decimationFactor * sizeof(int16_t),
|
||||
&m_nBytesReturned, nullptr))
|
||||
{
|
||||
const uint32_t lastError = GetLastError();
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Could not read from device - Got error " << lastError << " "
|
||||
<< (lastError == ERROR_MORE_DATA ? "(buffer overflow)" : "") << "\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_nBytesReturned == 0) { System::Time::sleep(2); }
|
||||
} while (m_nBytesReturned == 0);
|
||||
|
||||
#if 0
|
||||
// -- Gets buffer filling state
|
||||
|
||||
static uint32_t lastBufferFillingState=0;
|
||||
static uint32_t bufferFillingState=0;
|
||||
m_driverCtx.getLogManager() << LogLevel_TraceAPI << "BRAINAMP_BUFFERFILLING_STATE\n";
|
||||
if (!::DeviceIoControl(m_Device, BRAINAMP_BUFFERFILLING_STATE, nullptr, 0, &bufferFillingState, sizeof(bufferFillingState), &m_nBytesReturned, nullptr))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not retrieve buffer filling state\n";
|
||||
return false;
|
||||
}
|
||||
if(bufferFillingState!=lastBufferFillingState)
|
||||
{
|
||||
lastBufferFillingState=bufferFillingState;
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Buffer filling state : " << bufferFillingState << "\n";
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!m_driverCtx.isStarted())
|
||||
{
|
||||
// -- Converts the intermediate buffer in instant impedances
|
||||
const double scale = 1000 * (m_impedance == Impedance_High ? 2.1e-3 : 2.13219e-4);
|
||||
for (size_t i = 0; i < m_headerAdapter.getChannelCount(); ++i)
|
||||
{
|
||||
int16_t minValue = 32767;
|
||||
int16_t maxValue = -32767;
|
||||
int16_t* buffer = m_buffer + i;
|
||||
for (size_t j = 0; j < m_nSamplePerSentBlock * m_decimationFactor; ++j)
|
||||
{
|
||||
if (*buffer > maxValue) { maxValue = *buffer; }
|
||||
if (*buffer < minValue) { minValue = *buffer; }
|
||||
buffer += m_headerAdapter.getChannelCount() + 1;
|
||||
}
|
||||
m_impedances[i] = (maxValue - minValue) * scale - 1. + .5;
|
||||
}
|
||||
|
||||
// -- Backups this impedance in the impedance buffer and limit this buffer size
|
||||
|
||||
m_impedanceBuffers.push_back(m_impedances);
|
||||
if (m_impedanceBuffers.size() > m_impedanceCheckSignalFrequency) { m_impedanceBuffers.pop_front(); }
|
||||
|
||||
// -- Averages the impedances over last measures and send them to the acquisition server
|
||||
|
||||
for (size_t i = 0; i < m_headerAdapter.getChannelCount(); ++i)
|
||||
{
|
||||
double average = 0;
|
||||
for (auto it = m_impedanceBuffers.begin(); it != m_impedanceBuffers.end(); ++it) { average += (*it)[i]; }
|
||||
average /= m_impedanceBuffers.size();
|
||||
|
||||
m_driverCtx.updateImpedance(i, average);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// -- Converts the intermediate buffer in acquisition server convenient format
|
||||
|
||||
float* samples = m_sample;
|
||||
int16_t* buffer;
|
||||
for (size_t i = 0; i < m_headerAdapter.getChannelCount(); ++i)
|
||||
{
|
||||
buffer = m_buffer + i;
|
||||
for (size_t j = 0; j < m_nSamplePerSentBlock; ++j)
|
||||
{
|
||||
*samples = float(*buffer) * m_resolutionFactor[i];
|
||||
|
||||
samples += 1;
|
||||
buffer += m_decimationFactor * (m_headerAdapter.getChannelCount() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
CStimulationSet stimSet;
|
||||
buffer = m_buffer + m_headerAdapter.getChannelCount();
|
||||
for (size_t j = 0; j < m_nSamplePerSentBlock * m_decimationFactor; ++j)
|
||||
{
|
||||
uint32_t marker = *buffer;
|
||||
marker ^= m_deviceSetup->holdValue;
|
||||
if (marker != m_marker)
|
||||
{
|
||||
m_marker = marker;
|
||||
stimSet.appendStimulation(OVTK_StimulationId_Label(m_marker), CTime(m_header.getSamplingFrequency() * m_decimationFactor, uint64_t(j)).time(),
|
||||
0);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Got stim code " << m_marker << " at sample " << j << "\n";
|
||||
}
|
||||
buffer += m_headerAdapter.getChannelCount() + 1;
|
||||
}
|
||||
|
||||
// -- Sends data to the acquisition server
|
||||
|
||||
m_callback->setSamples(m_sample, m_nSamplePerSentBlock);
|
||||
m_callback->setStimulationSet(stimSet);
|
||||
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBrainProductsBrainampSeries::stop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
// -- Stops acquisition
|
||||
|
||||
m_driverCtx.getLogManager() << LogLevel_TraceAPI << "BRAINAMP_STOP\n";
|
||||
if (!DeviceIoControl(m_Device, BRAINAMP_STOP, nullptr, 0, nullptr, 0, &m_nBytesReturned, nullptr))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not stop acquisition\n";
|
||||
}
|
||||
|
||||
// -- Optionally starts impedance check
|
||||
|
||||
if (m_driverCtx.isImpedanceCheckRequested()) { if (!this->startImpedanceCheck()) { return false; } }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBrainProductsBrainampSeries::uninitialize()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
// -- Optionally stops impedance check
|
||||
|
||||
if (m_driverCtx.isImpedanceCheckRequested()) { if (!this->stopImpedanceCheck()) { return false; } }
|
||||
|
||||
// -- Deletes device setup object
|
||||
|
||||
delete m_deviceSetup;
|
||||
m_deviceSetup = nullptr;
|
||||
|
||||
// -- Closes handle to device
|
||||
|
||||
CloseHandle(m_Device);
|
||||
m_Device = nullptr;
|
||||
|
||||
// -- Deletes sample array and intermediate buffer
|
||||
|
||||
delete [] m_sample;
|
||||
delete [] m_buffer;
|
||||
m_sample = nullptr;
|
||||
m_buffer = nullptr;
|
||||
m_callback = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverBrainProductsBrainampSeries::startImpedanceCheck()
|
||||
{
|
||||
if (!m_driverCtx.isImpedanceCheckRequested()) { return true; }
|
||||
|
||||
// Configures impedance frequency
|
||||
|
||||
m_impedanceCheckSignalFrequency = 5000 / (m_nSamplePerSentBlock * m_decimationFactor);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Imepdance check sampling frequency set to " << m_impedanceCheckSignalFrequency << " Hz\n";
|
||||
|
||||
m_driverCtx.getLogManager() << LogLevel_TraceAPI << "BRAINAMP_IMPEDANCE_FREQUENCY\n";
|
||||
if (!DeviceIoControl(m_Device, BRAINAMP_IMPEDANCE_FREQUENCY, &m_impedanceCheckSignalFrequency, sizeof(uint32_t), nullptr, 0, &m_nBytesReturned, nullptr))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not set impedance frequency - got error " << size_t(GetLastError()) << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Starts acquiring impedance signal
|
||||
uint32_t type = uint32_t(AcquisitionType_Impedance);
|
||||
m_driverCtx.getLogManager() << LogLevel_TraceAPI << "BRAINAMP_START\n";
|
||||
if (!DeviceIoControl(m_Device, BRAINAMP_START, &type, sizeof(uint32_t), nullptr, 0, &m_nBytesReturned, nullptr))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not start acquisition\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configures impedance group range
|
||||
uint32_t group = (m_impedance == Impedance_High ? 0 : 1);
|
||||
m_driverCtx.getLogManager() << LogLevel_TraceAPI << "BRAINAMP_IMPEDANCE_GROUPRANGE\n";
|
||||
if (!DeviceIoControl(m_Device, BRAINAMP_IMPEDANCE_GROUPRANGE, &group, sizeof(uint32_t), nullptr, 0, &m_nBytesReturned, nullptr))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could set impedance group range\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prepares impedance buffers
|
||||
|
||||
m_impedances.resize(m_headerAdapter.getChannelCount());
|
||||
m_impedanceBuffers.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBrainProductsBrainampSeries::stopImpedanceCheck()
|
||||
{
|
||||
if (!m_driverCtx.isImpedanceCheckRequested()) { return true; }
|
||||
|
||||
// Stops impedance acquisition
|
||||
m_driverCtx.getLogManager() << LogLevel_TraceAPI << "BRAINAMP_STOP\n";
|
||||
if (!DeviceIoControl(m_Device, BRAINAMP_STOP, nullptr, 0, nullptr, 0, &m_nBytesReturned, nullptr))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not stop acquisition\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverBrainProductsBrainampSeries::configure()
|
||||
{
|
||||
CConfigurationBrainProductsBrainampSeries config(
|
||||
*this, Directories::getDataDir() + "/applications/acquisition-server/interface-BrainProducts-BrainampSeries.ui", m_usbIdx, m_decimationFactor,
|
||||
m_channelSelected, m_lowPassFilterFull, m_resolutionFull, m_dcCouplingFull, m_lowPass, m_resolution, m_dcCoupling, m_impedance);
|
||||
|
||||
config.configure(m_header);
|
||||
|
||||
m_settings.save();
|
||||
|
||||
m_header.setSamplingFrequency(5000 / m_decimationFactor);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBrainProductsBrainampSeries::getDeviceDetails(const uint32_t index, uint32_t* nAmplifier, uint32_t* amplifierType)
|
||||
{
|
||||
const char* name = ("\\\\.\\BrainAmpUSB" + std::to_string(index)).c_str();
|
||||
// -- Opens device
|
||||
void* device = ::CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, nullptr);
|
||||
if (device == INVALID_HANDLE_VALUE) { return false; }
|
||||
|
||||
// -- Gets amplifiers type
|
||||
uint16_t type[BrainAmp_MaximumAmplifierCount];
|
||||
m_driverCtx.getLogManager() << LogLevel_TraceAPI << "BRAINAMP_AMPLIFIER_TYPE\n";
|
||||
if (!DeviceIoControl(device, BRAINAMP_AMPLIFIER_TYPE, nullptr, 0, type, sizeof(type), &m_nBytesReturned, nullptr))
|
||||
{
|
||||
CloseHandle(device);
|
||||
return false;
|
||||
}
|
||||
|
||||
// -- Closes device
|
||||
CloseHandle(device);
|
||||
|
||||
// -- Formats result
|
||||
size_t count = BrainAmp_MaximumAmplifierCount;
|
||||
for (size_t i = 0; i < BrainAmp_MaximumAmplifierCount; ++i)
|
||||
{
|
||||
if (amplifierType) { amplifierType[i] = type[i]; }
|
||||
if (count == BrainAmp_MaximumAmplifierCount && type[i] == AmplifierType_None) { count = i; }
|
||||
}
|
||||
if (nAmplifier) { *nAmplifier = count; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_OS_Windows
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Brain Products Brainamp Series driver for OpenViBE
|
||||
* Copyright (C) 2010 INRIA - Author : Yann Renard
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ovasIDriver.h"
|
||||
#include "../ovasCHeader.h"
|
||||
|
||||
#include "../ovasCSettingsHelper.h"
|
||||
#include "../ovasCSettingsHelperOperators.h"
|
||||
|
||||
#if defined TARGET_OS_Windows
|
||||
|
||||
#include "ovas_defines_brainamp_series.h"
|
||||
|
||||
#include "ovasCConfigurationBrainProductsBrainampSeries.h"
|
||||
#include "ovasCHeaderAdapterBrainProductsBrainampSeries.h"
|
||||
|
||||
#include <vector>
|
||||
#include <list>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CDriverBrainProductsBrainampSeries
|
||||
* \author Mensia Technologies
|
||||
*/
|
||||
class CDriverBrainProductsBrainampSeries final : public IDriver
|
||||
{
|
||||
public:
|
||||
|
||||
friend class CConfigurationBrainProductsBrainampSeries;
|
||||
|
||||
static char* getErrorMessage(uint32_t error);
|
||||
|
||||
explicit CDriverBrainProductsBrainampSeries(IDriverContext& ctx);
|
||||
void release() { delete this; }
|
||||
const char* getName() override { return "Brain Products BrainAmp Series"; }
|
||||
|
||||
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
|
||||
bool start() override;
|
||||
bool loop() override;
|
||||
bool stop() override;
|
||||
bool uninitialize() override;
|
||||
|
||||
bool startImpedanceCheck();
|
||||
bool stopImpedanceCheck();
|
||||
|
||||
bool isConfigurable() override { return true; }
|
||||
bool configure() override;
|
||||
const IHeader* getHeader() override { return &m_headerAdapter; }
|
||||
|
||||
protected:
|
||||
|
||||
bool getDeviceDetails(uint32_t index, uint32_t* nAmplifier, uint32_t* amplifierType);
|
||||
|
||||
SettingsHelper m_settings;
|
||||
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
CHeader m_header;
|
||||
CHeaderAdapterBrainProductsBrainampSeries m_headerAdapter;
|
||||
|
||||
void* m_Device = nullptr;
|
||||
CBrainampSetup* m_deviceSetup = nullptr;
|
||||
CBrainampCalibrationSettings* m_deviceCalibrationSettings = nullptr;
|
||||
|
||||
uint32_t m_impedanceCheckSignalFrequency = 0;
|
||||
uint32_t m_decimationFactor = 10;
|
||||
uint32_t m_nSamplePerSentBlock = 0;
|
||||
float* m_sample = nullptr;
|
||||
int16_t* m_buffer = nullptr;
|
||||
uint16_t m_marker = 0;
|
||||
|
||||
std::list<std::vector<double>> m_impedanceBuffers;
|
||||
std::vector<double> m_impedances;
|
||||
|
||||
unsigned long m_nBytesReturned;
|
||||
|
||||
uint32_t m_usbIdx = 1;
|
||||
float m_resolutionFactor[256];
|
||||
EParameter m_channelSelected[256];
|
||||
EParameter m_lowPassFilterFull[256];
|
||||
EParameter m_resolutionFull[256];
|
||||
EParameter m_dcCouplingFull[256];
|
||||
EParameter m_lowPass = LowPass_250;
|
||||
EParameter m_resolution = Resolution_100nV;
|
||||
EParameter m_dcCoupling = DCCouping_AC;
|
||||
EParameter m_impedance = Impedance_Low;
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, const EParameter& var)
|
||||
{
|
||||
out << int(var);
|
||||
return out;
|
||||
}
|
||||
|
||||
inline std::istream& operator>>(std::istream& in, EParameter& var)
|
||||
{
|
||||
int tmp;
|
||||
in >> tmp;
|
||||
var = EParameter(tmp);
|
||||
return in;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& out, const EParameter var[256])
|
||||
{
|
||||
for (int i = 0; i < 256; ++i) { out << int(var[i]) << " "; }
|
||||
return out;
|
||||
}
|
||||
|
||||
inline std::istream& operator>>(std::istream& in, EParameter var[256])
|
||||
{
|
||||
int tmp;
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
in >> tmp;
|
||||
var[i] = EParameter(tmp);
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_OS_Windows
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Brain Products Brainamp Series driver for OpenViBE
|
||||
* Copyright (C) 2010 INRIA - Author : Yann Renard
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ovasCHeaderAdapterBrainProductsBrainampSeries.h"
|
||||
|
||||
#if defined TARGET_OS_Windows
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
size_t CHeaderAdapterBrainProductsBrainampSeries::getChannelCount() const
|
||||
{
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < m_header.getChannelCount(); ++i) { if (m_ChannelSelected[i] == Channel_Selected) { j++; } }
|
||||
return j;
|
||||
}
|
||||
|
||||
const char* CHeaderAdapterBrainProductsBrainampSeries::getChannelName(const size_t index) const
|
||||
{
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
|
||||
{
|
||||
if (m_ChannelSelected[i] == Channel_Selected)
|
||||
{
|
||||
if (j == index) { return m_header.getChannelName(i); }
|
||||
j++;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_OS_Windows
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Brain Products Brainamp Series driver for OpenViBE
|
||||
* Copyright (C) 2010 INRIA - Author : Yann Renard
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../ovasCHeader.h"
|
||||
|
||||
#if defined TARGET_OS_Windows
|
||||
|
||||
#include "ovas_defines_brainamp_series.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
class CHeaderAdapterBrainProductsBrainampSeries final : public CHeaderAdapter
|
||||
{
|
||||
public:
|
||||
|
||||
CHeaderAdapterBrainProductsBrainampSeries(IHeader& adaptedHeader, EParameter* channelSelected)
|
||||
: CHeaderAdapter(adaptedHeader), m_ChannelSelected(channelSelected) {}
|
||||
|
||||
bool setChannelCount(const size_t /*nChannel*/) override { return false; }
|
||||
bool setChannelName(const size_t /*index*/, const char* /*name*/) override { return false; }
|
||||
|
||||
size_t getChannelCount() const override;
|
||||
const char* getChannelName(const size_t index) const override;
|
||||
|
||||
EParameter* m_ChannelSelected = nullptr;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_OS_Windows
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
#pragma once
|
||||
|
||||
// __________________________________________________________________//
|
||||
// //
|
||||
// File created from BrainAmpIoCtl.h in Brain Products low level SDK //
|
||||
// More compliant with OpenViBE coding rules //
|
||||
// __________________________________________________________________//
|
||||
// //
|
||||
|
||||
// Send setup, Parameter [IN]: BA_SETUP struct.
|
||||
#define BRAINAMP_SETUP CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Start acquisition, Parameter [IN]: long nType, 0 = Impedance check, 1 = Data aquisition, 2 = test signal
|
||||
#define BRAINAMP_START CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Stop acquisition
|
||||
#define BRAINAMP_STOP CTL_CODE(FILE_DEVICE_UNKNOWN, 0x803, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Get buffer filling state, Parameter [OUT]: long nState, < 0 = Overflow, 0 - 100 = Filling state in percent.
|
||||
#define BRAINAMP_BUFFERFILLING_STATE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x804, METHOD_BUFFERED, FILE_READ_DATA)
|
||||
|
||||
// Get battery voltage of one unit, Parameter [IN]: USHORT nUnit, Parameter [OUT]: long nVoltage in millivolts
|
||||
#define BRAINAMP_BATTERY_VOLTAGE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x805, METHOD_BUFFERED, FILE_WRITE_DATA | FILE_READ_DATA)
|
||||
|
||||
// Set impedance check sine wave frequency, Parameter [IN]: LONG nFrequency in Hertz
|
||||
#define BRAINAMP_IMPEDANCE_FREQUENCY CTL_CODE(FILE_DEVICE_UNKNOWN, 0x806, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Set impedance test mode, i.e. even in recording mode, the amplifier is still in impedance check mode
|
||||
// Parameter [IN]: long bTestMode, != 0 testmode, 0 normal mode
|
||||
#define BRAINAMP_IMPEDANCE_TESTMODE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x807, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Set impedance group and range
|
||||
// Parameter [IN]: long nGroupRange, 0 = 100kOhm Data (IMP1 | CAL), 1 = 10kOhm Data (IMP1),
|
||||
// 2 = 100 kOhm Reference (IMP2 | CAL), 3 = 10 kOhm Reference (IMP2)
|
||||
// 4 = Ground (IMP2)
|
||||
#define BRAINAMP_IMPEDANCE_GROUPRANGE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x808, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Get error state, Parameter [OUT]: long nState,
|
||||
// Highword (amplifier): Bit 0 - 3: amplifier number(s)
|
||||
// Loword: (error code): 0 = no error, 1 = loss lock, 2 = low power,
|
||||
// 3 = can't establish communication at start.
|
||||
// 4 = synchronisation error
|
||||
#define BRAINAMP_ERROR_STATE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x809, METHOD_BUFFERED, FILE_READ_DATA)
|
||||
|
||||
// Disable DC offset correction for testing purposes.
|
||||
// Parameter [IN]: long bTestMode, != 0 DC offset correction off, 0 normal mode
|
||||
#define BRAINAMP_DCOFFSET_TESTMODE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80A, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Send calibration settings, Parameter [IN]: BA_CALIBRATION_SETTINGS struct.
|
||||
#define BRAINAMP_CALIBRATION_SETTINGS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80B, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Set pullup/pulldown resistors for the digital input (default is pulldown).
|
||||
// This can not be done for each bit, but for 2 groups.
|
||||
// Parameter [IN]: USHORT bPullup, (low byte for bit 0 - 7, high byte for bit 8 - 15)
|
||||
// != 0 pullup, 0 pulldown
|
||||
#define BRAINAMP_DIGITALINPUT_PULL_UP CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80C, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Get digital input value, Parameter [OUT]: USHORT nValue
|
||||
#define BRAINAMP_DIGITALINPUT_VALUE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80D, METHOD_BUFFERED, FILE_READ_DATA)
|
||||
|
||||
// Get driver version, Parameter [OUT]: ULONG nValue
|
||||
// The version is coded as Major.Minor.DLL.
|
||||
// The "DLL" part is used for intermediate versions and contains 4 digits.
|
||||
// The minor part contains 2 digits.
|
||||
// The number 1010041 for example means version 1.01.0041.
|
||||
// If the highest bit (bit 31) is set, it means "test version".
|
||||
#define BRAINAMP_DRIVERVERSION CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80E, METHOD_BUFFERED, FILE_READ_DATA)
|
||||
|
||||
// Send Commands during recording
|
||||
// Parameter [IN]: USHORT nCommand (1 = DC Correction), USHORT nChannel (-1 = all channels)
|
||||
#define BRAINAMP_COMMAND CTL_CODE(FILE_DEVICE_UNKNOWN, 0x80F, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Set Control register directly (WRCON)
|
||||
// Parameter [IN]: USHORT nAddress, ulong[4] channel mask
|
||||
#define BRAINAMP_CONTROLREGISTER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x810, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Set DC offset value for potentiometer A (coarse)
|
||||
// Parameter [IN]: USHORT nChannel, UCHAR nValue
|
||||
#define BRAINAMP_DCOFFSET_COARSE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x811, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Do DC Offset correction for potentiometer A (coarse)
|
||||
#define BRAINAMP_DCOFFSET_CORRECTION_COARSE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x812, METHOD_NEITHER, FILE_WRITE_DATA)
|
||||
|
||||
// Set DC offset value for potentiometer B (fine)
|
||||
// Parameter [IN]: USHORT nChannel, UCHAR nValue
|
||||
#define BRAINAMP_DCOFFSET_FINE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x813, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Do DC Offset correction for potentiometer B (fine)
|
||||
#define BRAINAMP_DCOFFSET_CORRECTION_FINE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x814, METHOD_NEITHER, FILE_WRITE_DATA)
|
||||
|
||||
// Set Cal, Imp1, and Imp2 lines (testing only!)
|
||||
// Parameter [IN]: BYTE Cal, BYTE, Imp1, Byte Imp2
|
||||
#define BRAINAMP_SET_CAL_IMP_LINES CTL_CODE(FILE_DEVICE_UNKNOWN, 0x815, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Get amplifier type of all 4 units,
|
||||
// Parameter [IN]: USHORT nUnit, Parameter [OUT]: USHORT Type[4]: 0 no Amp, 1 BrainAmp, 2 MR, 3 DC
|
||||
#define BRAINAMP_AMPLIFIER_TYPE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x816, METHOD_BUFFERED, FILE_WRITE_DATA | FILE_READ_DATA)
|
||||
|
||||
// Do DC Offset correction, no parameters
|
||||
#define BRAINAMP_DCOFFSET_CORRECTION CTL_CODE(FILE_DEVICE_UNKNOWN, 0x817, METHOD_NEITHER, FILE_WRITE_DATA)
|
||||
|
||||
// Retrieve serial number of device. Parameter [OUT]: ULONG
|
||||
#define BRAINAMP_GET_SERIALNUMBER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x818, METHOD_BUFFERED, FILE_READ_DATA)
|
||||
|
||||
// Retrieve number of supported amplifiers for the device. Parameter [OUT]: USHORT
|
||||
#define BRAINAMP_GET_SUPPORTED_AMPLIFIERS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x819, METHOD_BUFFERED, FILE_READ_DATA)
|
||||
|
||||
// Prestart, used only if more than one device is involved.
|
||||
#define BRAINAMP_PRESTART CTL_CODE(FILE_DEVICE_UNKNOWN, 0x81a, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Poststart, used only if more than one device is involved.
|
||||
#define BRAINAMP_POSTSTART CTL_CODE(FILE_DEVICE_UNKNOWN, 0x81b, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
// Disable busmastering
|
||||
#define BRAINAMP_DISABLE_BUSMASTERING CTL_CODE(FILE_DEVICE_UNKNOWN, 0x81c, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
#pragma pack(1)
|
||||
|
||||
// Brainamp Setup
|
||||
typedef struct
|
||||
{
|
||||
uint32_t nChannel; // Number of channels - was /*long*/
|
||||
uint8_t channelLookup[256]; // Channel lookup table, -1 to -8 means PolyBox channels - was /*CHAR*/
|
||||
uint32_t nSamplePerSentBlock; // Number of points per block - was /*long*/
|
||||
uint16_t holdValue; // Hold value for digital input - was /*USHORT*/
|
||||
|
||||
// Version 1.01.0003 and higher
|
||||
// The following tables are based on logical channel positions
|
||||
uint8_t lowPassFilter[256]; // Low pass 250 Hz (0 = 1000Hz) - was /*UCHAR*/
|
||||
uint8_t resolution[256]; // 0 = 100 nV, 1 = 500 nV, 2 = 10 uV, 3 = 152.6 uV - was /*UCHAR*/
|
||||
uint8_t dcCoupling[256]; // DC coupling (0 = AC) - was /*UCHAR*/
|
||||
uint8_t lowImpedance; // Low impedance i.e. 10 MOhm, (0 = > 100MOhm) - was /*UCHAR*/
|
||||
} CBrainampSetup;
|
||||
|
||||
// Brainamp Calibration Settings (default: square waves, 5 Hz)
|
||||
typedef struct
|
||||
{
|
||||
uint16_t waveForm; // 0 = ramp, 1 = triangle, 2 = square, 3 = sine wave - was /*USHORT*/
|
||||
uint32_t frequency; // Frequency in millihertz - was /*ULONG*/
|
||||
} CBrainampCalibrationSettings;
|
||||
|
||||
#pragma pack()
|
||||
|
||||
// Brainamp Parameter Types
|
||||
typedef enum
|
||||
{
|
||||
Parameter_Default=-1,
|
||||
|
||||
Channel_Unselected=0,
|
||||
Channel_Selected=1,
|
||||
|
||||
DecimationFactor_None=0,
|
||||
DecimationFactor_2=1,
|
||||
DecimationFactor_4=2,
|
||||
DecimationFactor_5=3,
|
||||
DecimationFactor_8=4,
|
||||
DecimationFactor_10=5,
|
||||
|
||||
LowPass_1000=0,
|
||||
LowPass_250=1,
|
||||
|
||||
Resolution_100nV=0,
|
||||
Resolution_500nV=1,
|
||||
Resolution_10uV=2,
|
||||
Resolution_152uV=3,
|
||||
|
||||
DCCouping_AC=0,
|
||||
DCCouping_DC=1,
|
||||
|
||||
Impedance_High=0,
|
||||
Impedance_Low=1,
|
||||
|
||||
AcquisitionType_Impedance=0,
|
||||
AcquisitionType_EEG=1,
|
||||
AcquisitionType_TestSignal=2,
|
||||
|
||||
WaveForm_Ramp=0,
|
||||
WaveForm_Triangle=1,
|
||||
WaveForm_Square=2,
|
||||
WaveForm_SineWave=3,
|
||||
|
||||
AmplifierType_None=0,
|
||||
AmplifierType_BrainAmp=1,
|
||||
AmplifierType_BrainAmpMR=2,
|
||||
AmplifierType_BrainAmpDC=3,
|
||||
} EParameter;
|
||||
|
||||
inline const char* getDeviceType(const uint32_t type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case AmplifierType_None: return "-- Not found --";
|
||||
case AmplifierType_BrainAmp: return "BrainAmp Standard";
|
||||
case AmplifierType_BrainAmpMR: return "BrainAmp MR";
|
||||
case AmplifierType_BrainAmpDC: return "BrainAmp DC / MR Plus";
|
||||
default: return "-- Unknown --";
|
||||
}
|
||||
}
|
||||
|
||||
#define BrainAmp_MaximumAmplifierCount 4
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Brain Products LiveAmp driver for OpenViBE
|
||||
* Copyright (C) 2017 Brain Products - Author : Ratko Petrovic
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
/*********************************************************************
|
||||
* History
|
||||
* [2017-03-29] ver 1.0 - RP
|
||||
* [2017-05-02] ver 1.1 Due to support for LiveAmp 8 and 16 channels,
|
||||
* a new variable "m_nBip" added - RP
|
||||
*
|
||||
*********************************************************************/
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyLiveAmpAPI
|
||||
|
||||
#include "ovasCConfigurationBrainProductsLiveAmp.h"
|
||||
#include <Amplifier_LIB.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
/*_________________________________________________
|
||||
|
||||
Insert callback to specific widget here
|
||||
Example with a button that launch a calibration of the device:
|
||||
|
||||
//Callback connected to a dedicated gtk button:
|
||||
static void button_calibrate_pressed_cb(GtkButton* button, void* data)
|
||||
{
|
||||
CConfigurationBrainProductsLiveAmp* config=static_cast<CConfigurationBrainProductsLiveAmp*>(data);
|
||||
config->buttonCalibratePressedCB();
|
||||
}
|
||||
_________________________________________________*/
|
||||
|
||||
// If you added more reference attribute, initialize them here
|
||||
CConfigurationBrainProductsLiveAmp::CConfigurationBrainProductsLiveAmp(
|
||||
CDriverBrainProductsLiveAmp& ctx, const char* gtkBuilderFilename, uint32_t& physicalSampling,
|
||||
uint32_t& rCountEEG, uint32_t& rCountBip, uint32_t& rCountAUX, uint32_t& rCountACC, bool& rUseAccChannels,
|
||||
uint32_t& goodImpedanceLimit, uint32_t& badImpedanceLimit, std::string& sSerialNumber, bool& rUseBipolarChannels)
|
||||
: CConfigurationBuilder(gtkBuilderFilename), m_driverCtx(ctx), m_physicalSampling(physicalSampling), m_nEEG(rCountEEG)
|
||||
, m_nBip(rCountBip), m_nAUX(rCountAUX), m_nACC(rCountACC), m_useAccChannels(rUseAccChannels), m_useBipolarChannels(rUseBipolarChannels)
|
||||
, m_goodImpedanceLimit(goodImpedanceLimit), m_badImpedanceLimit(badImpedanceLimit), m_nEEGChannels(rCountEEG), m_nAUXChannels(rCountAUX)
|
||||
, m_triggers(badImpedanceLimit), m_sSerialNumber(sSerialNumber) {}
|
||||
|
||||
bool CConfigurationBrainProductsLiveAmp::preConfigure()
|
||||
{
|
||||
if (! CConfigurationBuilder::preConfigure()) { return false; }
|
||||
|
||||
m_tSerialNumber = GTK_ENTRY(gtk_builder_get_object(m_builder, "entrySerialNr"));
|
||||
m_comboBoxPhysicalSampleRate = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_sampling_frequency"));
|
||||
|
||||
m_buttonNEEGChannels = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_number_of_channels"));
|
||||
m_buttonNBipolar = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_number_of_bipolar"));
|
||||
m_buttonNAUXChannels = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spbBtnAUXchn"));
|
||||
|
||||
m_enableACCChannels = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_acc"));
|
||||
m_enableBipolarChannels = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_use_bipolar_channels"));
|
||||
|
||||
if (gtk_builder_get_object(m_builder, "checkbutton_impedance"))
|
||||
{
|
||||
m_impedanceCheck = GTK_WIDGET(gtk_builder_get_object(m_builder, "checkbutton_impedance"));
|
||||
}
|
||||
|
||||
m_buttonGoodImpedanceLimit = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_good_imp"));
|
||||
m_buttonBadImpedanceLimit = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_bad_imp"));
|
||||
|
||||
// Connect here all callbacks
|
||||
// Example:
|
||||
// g_signal_connect(gtk_builder_get_object(m_builder, "button_calibrate"), "pressed", G_CALLBACK(button_calibrate_pressed_cb), this);
|
||||
|
||||
// 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.
|
||||
gtk_entry_set_text(m_tSerialNumber, m_sSerialNumber.c_str());
|
||||
gtk_combo_box_set_active(m_comboBoxPhysicalSampleRate, m_physicalSampling);
|
||||
gtk_spin_button_set_value(m_buttonNEEGChannels, m_nEEG);
|
||||
gtk_spin_button_set_value(m_buttonNBipolar, m_nBip);
|
||||
gtk_spin_button_set_value(m_buttonNAUXChannels, m_nAUX);
|
||||
gtk_spin_button_set_value(m_buttonGoodImpedanceLimit, m_goodImpedanceLimit);
|
||||
gtk_spin_button_set_value(m_buttonBadImpedanceLimit, m_badImpedanceLimit);
|
||||
gtk_toggle_button_set_active(m_enableACCChannels, m_useAccChannels);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CConfigurationBrainProductsLiveAmp::postConfigure()
|
||||
{
|
||||
if (m_applyConfig)
|
||||
{
|
||||
// If the user pressed the "apply" button, you need to save the changes made in the configuration.
|
||||
// For example, you can save the connection ID of the selected device:
|
||||
// m_connectionID = <value-from-gtk-widget>
|
||||
gtk_spin_button_update(GTK_SPIN_BUTTON(m_buttonGoodImpedanceLimit));
|
||||
gtk_spin_button_update(GTK_SPIN_BUTTON(m_buttonGoodImpedanceLimit));
|
||||
gtk_spin_button_update(GTK_SPIN_BUTTON(m_buttonNEEGChannels));
|
||||
gtk_spin_button_update(GTK_SPIN_BUTTON(m_buttonNBipolar));
|
||||
gtk_spin_button_update(GTK_SPIN_BUTTON(m_buttonNAUXChannels));
|
||||
|
||||
const char* serialNumber = gtk_entry_get_text(m_tSerialNumber);
|
||||
m_sSerialNumber.assign(serialNumber, strlen(serialNumber));
|
||||
|
||||
|
||||
m_physicalSampling = gtk_combo_box_get_active(m_comboBoxPhysicalSampleRate);
|
||||
GtkTreeModel* widget = gtk_combo_box_get_model(m_comboBoxPhysicalSampleRate);
|
||||
|
||||
m_nEEG = uint32_t(gtk_spin_button_get_value(m_buttonNEEGChannels));
|
||||
m_nBip = uint32_t(gtk_spin_button_get_value(m_buttonNBipolar));
|
||||
m_nAUX = uint32_t(gtk_spin_button_get_value(m_buttonNAUXChannels));
|
||||
|
||||
m_useAccChannels = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_enableACCChannels)) != 0;
|
||||
|
||||
if (m_useAccChannels) { m_nACC = 3; } // can be 3 or 6
|
||||
else { m_nACC = 0; }
|
||||
|
||||
m_goodImpedanceLimit = uint32_t(gtk_spin_button_get_value(m_buttonGoodImpedanceLimit));
|
||||
m_badImpedanceLimit = uint32_t(gtk_spin_button_get_value(m_buttonBadImpedanceLimit));
|
||||
}
|
||||
|
||||
// normal header is filled (Subject ID, Age, Gender, channels, sampling frequency), ressources are realesed
|
||||
if (! CConfigurationBuilder::postConfigure()) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_HAS_ThirdPartyLiveAmpAPI
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef TARGET_HAS_ThirdPartyLiveAmpAPI
|
||||
|
||||
#include "../ovasCConfigurationBuilder.h"
|
||||
#include "ovasCDriverBrainProductsLiveAmp.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CConfigurationBrainProductsLiveAmp
|
||||
* \author Ratko Petrovic (Brain Products GmbH)
|
||||
* \date Mon Nov 21 14:57:37 2016
|
||||
* \brief The CConfigurationBrainProductsLiveAmp handles the configuration dialog specific to the Brain Products LiveAmp device.
|
||||
*
|
||||
* TODO: details
|
||||
*
|
||||
* \sa CDriverBrainProductsLiveAmp
|
||||
*/
|
||||
class CConfigurationBrainProductsLiveAmp final : public CConfigurationBuilder
|
||||
{
|
||||
public:
|
||||
// you may have to add to your constructor some reference parameters
|
||||
// for example, a connection ID:
|
||||
CConfigurationBrainProductsLiveAmp(
|
||||
CDriverBrainProductsLiveAmp& ctx, const char* gtkBuilderFilename, uint32_t& physicalSampling,
|
||||
uint32_t& rCountEEG, uint32_t& rCountBip, uint32_t& rCountAUX, uint32_t& rCountACC, bool& rUseAccChannels,
|
||||
uint32_t& goodImpedanceLimit, uint32_t& badImpedanceLimit, std::string& sSerialNumber, bool& rUseBipolarChannels);
|
||||
|
||||
bool preConfigure() override;
|
||||
bool postConfigure() override;
|
||||
|
||||
protected:
|
||||
CDriverBrainProductsLiveAmp& m_driverCtx;
|
||||
|
||||
uint32_t& m_physicalSampling;
|
||||
uint32_t& m_nEEG;
|
||||
uint32_t& m_nBip;
|
||||
uint32_t& m_nAUX;
|
||||
uint32_t& m_nACC;
|
||||
bool& m_useAccChannels;
|
||||
bool& m_useBipolarChannels;
|
||||
|
||||
uint32_t& m_goodImpedanceLimit;
|
||||
uint32_t& m_badImpedanceLimit;
|
||||
uint32_t& m_nEEGChannels;
|
||||
uint32_t& m_nAUXChannels;
|
||||
uint32_t& m_triggers;
|
||||
std::string& m_sSerialNumber;
|
||||
|
||||
GtkWidget* m_imageErrorIcon = nullptr;
|
||||
GtkLabel* m_labelErrorMessage = nullptr;
|
||||
GtkButton* m_buttonErrorDLLLink = nullptr;
|
||||
GtkSpinButton* m_buttonDeviceId = nullptr;
|
||||
GtkComboBox* m_comboBoxMode = nullptr;
|
||||
GtkComboBox* m_comboBoxPhysicalSampleRate = nullptr;
|
||||
GtkComboBox* m_comboBoxADCDataFilter = nullptr;
|
||||
GtkComboBox* m_comboBoxADCDataDecimation = nullptr;
|
||||
GtkSpinButton* m_buttonActiveShieldGain = nullptr;
|
||||
GtkToggleButton* m_buttonUseAuxChannels = nullptr;
|
||||
GtkSpinButton* m_buttonGoodImpedanceLimit = nullptr;
|
||||
GtkSpinButton* m_buttonBadImpedanceLimit = nullptr;
|
||||
|
||||
GtkSpinButton* m_buttonNEEGChannels = nullptr;
|
||||
GtkSpinButton* m_buttonNBipolar = nullptr;
|
||||
GtkSpinButton* m_buttonNAUXChannels = nullptr;
|
||||
GtkToggleButton* m_enableACCChannels = nullptr;
|
||||
GtkToggleButton* m_enableBipolarChannels = nullptr;
|
||||
GtkEntry* m_tSerialNumber = nullptr;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyLiveAmpAPI
|
||||
+1055
File diff suppressed because it is too large
Load Diff
+171
@@ -0,0 +1,171 @@
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyLiveAmpAPI
|
||||
|
||||
#include "ovasIDriver.h"
|
||||
#include "../ovasCHeader.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
|
||||
#include "../ovasCSettingsHelper.h"
|
||||
#include "../ovasCSettingsHelperOperators.h"
|
||||
|
||||
#include <deque>
|
||||
|
||||
#ifndef BYTE
|
||||
typedef unsigned char BYTE;
|
||||
#endif
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CDriverBrainProductsLiveAmp
|
||||
* \author Ratko Petrovic (Brain Products GmbH)
|
||||
* \date Mon Nov 21 14:57:37 2016
|
||||
* \brief The CDriverBrainProductsLiveAmp allows the acquisition server to acquire data from a Brain Products LiveAmp device.
|
||||
*
|
||||
* TODO: details
|
||||
*
|
||||
* \sa CConfigurationBrainProductsLiveAmp
|
||||
*/
|
||||
class CDriverBrainProductsLiveAmp final : public IDriver
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CDriverBrainProductsLiveAmp(IDriverContext& ctx);
|
||||
~CDriverBrainProductsLiveAmp() override { uninitialize(); }
|
||||
|
||||
const char* getName() override { return "Brain Products LiveAmp"; }
|
||||
|
||||
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
|
||||
{
|
||||
if (flag == EDriverFlag::IsUnstable) { return false; } // switch to "Stable" on 3.5.2017 - RP
|
||||
return false; // No flags are set
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
SettingsHelper m_settings;
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
void* m_handle = nullptr;
|
||||
|
||||
CHeader m_header;
|
||||
CStimulationSet m_stimSet;
|
||||
uint32_t m_nSamplePerSentBlock = 0;
|
||||
uint8_t* m_sampleBuffer = nullptr;
|
||||
|
||||
uint32_t m_bufferSize = 0;
|
||||
uint32_t m_sampleSize = 0;
|
||||
|
||||
uint32_t m_physicalSampling = 250;
|
||||
bool m_useAccChannels = false;
|
||||
bool m_useBipolarChannels = false;
|
||||
uint32_t m_impedanceChannels = 0;
|
||||
uint32_t m_nUsedChannels = 0;
|
||||
uint32_t m_nEnabledChannels = 0;
|
||||
|
||||
uint32_t m_nChannel = 0;
|
||||
uint32_t m_nEEG = 32;
|
||||
uint32_t m_nAux = 0;
|
||||
uint32_t m_nACC = 0;
|
||||
uint32_t m_nBipolar = 0;
|
||||
uint32_t m_nTriggersIn = 0;
|
||||
uint32_t m_goodImpedanceLimit = 5000;
|
||||
uint32_t m_badImpedanceLimit = 10000;
|
||||
|
||||
std::string m_sSerialNumber = "05203-0077";
|
||||
int m_iRecordingMode = -1;
|
||||
|
||||
std::vector<float> m_samples;
|
||||
std::deque<std::vector<float>> m_sampleCaches;
|
||||
std::vector<std::vector<float>> m_sendBuffers;
|
||||
std::vector<size_t> m_samplings;
|
||||
std::vector<uint16_t> m_triggerIndices;
|
||||
std::vector<uint16_t> m_lastTriggerStates;
|
||||
std::vector<uint16_t> m_dataTypes;
|
||||
std::vector<float> m_resolutions;
|
||||
|
||||
bool m_bSimulationMode = false;
|
||||
/**
|
||||
* \function initializeLiveAmp
|
||||
* \return True if LiveAmp is successful initialized.
|
||||
* \brief Establish BT connection with the LiveAmp amplifier, identified with the serial number.
|
||||
*/
|
||||
bool initializeLiveAmp();
|
||||
|
||||
/**
|
||||
* \function configureLiveAmp
|
||||
* \return True if LiveAmp is successful configured.
|
||||
* \brief Configure LiveAmp, sampling rate and mode.
|
||||
*/
|
||||
bool configureLiveAmp();
|
||||
|
||||
/**
|
||||
* \function checkAvailableChannels
|
||||
* \return True if checking is successful.
|
||||
* \brief Check how many available channels has LiveAmp. Get count of EEG,AUX and ACC channels.
|
||||
Compares the count with the amplifier/driver settings.
|
||||
*/
|
||||
bool checkAvailableChannels();
|
||||
|
||||
/**
|
||||
* \function disableAllAvailableChannels
|
||||
* \return True if function is successful.
|
||||
* \brief
|
||||
*/
|
||||
bool disableAllAvailableChannels();
|
||||
|
||||
/**
|
||||
* \function getChannelIndices
|
||||
* \return True if function is successful.
|
||||
* \brief Get indexes of the channel that will be used for acquisition.
|
||||
*/
|
||||
bool getChannelIndices();
|
||||
|
||||
/**
|
||||
* \function impedanceMessureInit
|
||||
* \return True if function is successful.
|
||||
* \brief Configures impedance measurement.
|
||||
*/
|
||||
bool configureImpedanceMessure();
|
||||
|
||||
/**
|
||||
* \function getLiveAmpSampleSize
|
||||
* \return Size of 'one' acquired sample.
|
||||
* \brief Calcualtes the size of 'one' sample that LiveAmp delivers. It is actually a sum of the one sample per each channel that will be acquired.
|
||||
This information is needed to read data from buffer, in order to access to each sample of each channel.
|
||||
*/
|
||||
uint32_t getLiveAmpSampleSize();
|
||||
|
||||
/**
|
||||
* \function liveAmpExtractData
|
||||
* \param samplesRead[in] : count of samples read (obtained) from the amplifier
|
||||
* \param extractData[out] : extract data from the buffer
|
||||
* \return
|
||||
* \brief Extracts acquired data from the buffer, and puts them into the double vector 'extractData' , in the appropriate format.
|
||||
*/
|
||||
void liveAmpExtractData(int samplesRead, std::vector<std::vector<float>>& extractData);
|
||||
|
||||
/**
|
||||
* \function liveAmpExtractImpedanceData
|
||||
* \param samplesRead[in] : count of acquired samples from the amplifier
|
||||
* \param extractData[out] : extract data from the buffer
|
||||
* \return
|
||||
* \brief Extracts acquired impedance measurements from the buffer, and puts them into the double vector 'extractData', in the appropriate format.
|
||||
*/
|
||||
void liveAmpExtractImpedanceData(int samplesRead, std::vector<std::vector<float>>& extractData);
|
||||
};
|
||||
} //namespace AcquisitionServer
|
||||
} //namespace OpenViBE
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyLiveAmpAPI
|
||||
+519
@@ -0,0 +1,519 @@
|
||||
#include "ovasCConfigurationBrainProductsVAmp.h"
|
||||
#include "ovasCHeaderBrainProductsVAmp.h"
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyUSBFirstAmpAPI
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
gboolean idle_check_service(gpointer data)
|
||||
{
|
||||
GtkBuilder* builder = static_cast<GtkBuilder*>(data);
|
||||
|
||||
SC_HANDLE scm = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
||||
SC_HANDLE service = nullptr;
|
||||
if (scm != nullptr && scm != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
service = OpenService(scm, "VampService", SERVICE_ALL_ACCESS);
|
||||
if (service != nullptr)
|
||||
{
|
||||
SERVICE_STATUS status;
|
||||
QueryServiceStatus(service, &status);
|
||||
|
||||
if (status.dwCurrentState == SERVICE_RUNNING)
|
||||
{
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(builder, "button_start_service")), false);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(builder, "button_stop_service")), true);
|
||||
gtk_label_set(GTK_LABEL(gtk_builder_get_object(builder, "label_service")), "VampService is Enabled");
|
||||
gtk_image_set_from_stock(GTK_IMAGE(gtk_builder_get_object(builder, "image_service")),GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_BUTTON);
|
||||
CloseServiceHandle(scm);
|
||||
CloseServiceHandle(service);
|
||||
return true;
|
||||
}
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(builder, "button_start_service")), true);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(builder, "button_stop_service")), false);
|
||||
gtk_label_set(GTK_LABEL(gtk_builder_get_object(builder, "label_service")), "VampService is Disabled");
|
||||
gtk_image_set_from_stock(GTK_IMAGE(gtk_builder_get_object(builder, "image_service")),GTK_STOCK_APPLY, GTK_ICON_SIZE_BUTTON);
|
||||
CloseServiceHandle(scm);
|
||||
CloseServiceHandle(service);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(builder, "button_start_service")), false);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(builder, "button_stop_service")), false);
|
||||
gtk_label_set(GTK_LABEL(gtk_builder_get_object(builder, "label_service")), (scm != nullptr && scm != INVALID_HANDLE_VALUE)
|
||||
? "VampService has not been Detected"
|
||||
: "Service Manager not Available (you must be administrator)");
|
||||
gtk_image_set_from_stock(GTK_IMAGE(gtk_builder_get_object(builder, "image_service")),GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_BUTTON);
|
||||
return false;
|
||||
}
|
||||
//____________________________________________________________________________________
|
||||
//
|
||||
static void StartServiceCB(GtkButton* /*button*/, void* data) { static_cast<CConfigurationBrainProductsVAmp*>(data)->buttonStartServiceCB(); }
|
||||
static void StopServiceCB(GtkButton* /*button*/, void* data) { static_cast<CConfigurationBrainProductsVAmp*>(data)->buttonStopServiceCB(); }
|
||||
static void FastModeSettingsCB(GtkButton* /*button*/, void* data) { static_cast<CConfigurationBrainProductsVAmp*>(data)->buttonFastModeSettingsCB(); }
|
||||
static void ChannelCountChangedCB(GtkComboBox* /*box*/, void* data) { static_cast<CConfigurationBrainProductsVAmp*>(data)->channelCountChangedCB(); }
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
//____________________________________________________________________________________
|
||||
|
||||
// Inits the combo box, and sets the active value
|
||||
// Warning : active value must be 7 8 9 10 or -1.
|
||||
void initFastModeSettingsComboBox(GtkWidget* comboBox, const uint32_t value, const bool initComboBox = true)
|
||||
{
|
||||
if (initComboBox)
|
||||
{
|
||||
//-1
|
||||
gtk_combo_box_append_text(GTK_COMBO_BOX(comboBox), "-1");
|
||||
//7-10
|
||||
for (size_t i = 7; i < 11; ++i)
|
||||
{
|
||||
const std::string tmp = std::to_string(i);
|
||||
gtk_combo_box_append_text(GTK_COMBO_BOX(comboBox), tmp.c_str());
|
||||
}
|
||||
}
|
||||
const std::string tmp = std::to_string(value);
|
||||
GTKComboBoxSetActiveText(GTK_COMBO_BOX(comboBox), tmp.c_str());
|
||||
}
|
||||
|
||||
//____________________________________________________________________________________
|
||||
|
||||
CConfigurationBrainProductsVAmp::CConfigurationBrainProductsVAmp(IDriverContext& ctx, const char* gtkBuilderFilename,
|
||||
CHeaderBrainProductsVAmp* headerBrainProductsVAmp, bool& acquireAuxiliaryAsEEG,
|
||||
bool& acquireTriggerAsEEG)
|
||||
: CConfigurationBuilder(gtkBuilderFilename), m_driverCtx(ctx), m_headerBrainProductsVAmp(headerBrainProductsVAmp),
|
||||
m_rAcquireAuxiliaryAsEEG(acquireAuxiliaryAsEEG), m_rAcquireTriggerAsEEG(acquireTriggerAsEEG) { m_giIdleID = 0; }
|
||||
|
||||
bool CConfigurationBrainProductsVAmp::preConfigure()
|
||||
{
|
||||
if (!CConfigurationBuilder::preConfigure()) { return false; }
|
||||
|
||||
// Finds all the widgets
|
||||
m_dialogFastModeSettings = GTK_WIDGET(gtk_builder_get_object(m_builder, "dialog_fast_mode_settings"));
|
||||
|
||||
// the acquisition mode combo box in the main interface
|
||||
m_acquisitionMode = GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_acquisition_mode"));
|
||||
|
||||
// the device combo box autofilled with all connected device
|
||||
m_Device = GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_device"));
|
||||
|
||||
// the toggle button for aux channels and triggers
|
||||
m_auxiliaryChannels = GTK_WIDGET(gtk_builder_get_object(m_builder, "checkbutton_aux"));
|
||||
m_triggerChannels = GTK_WIDGET(gtk_builder_get_object(m_builder, "checkbutton_trigger"));
|
||||
|
||||
// the 8 spin buttons for the settings in the "fast mode settings" interface
|
||||
m_pair1PositiveInputs = GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_pair1_positive_input"));
|
||||
m_pair1NegativeInputs = GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_pair1_negative_input"));
|
||||
|
||||
m_pair2PositiveInputs = GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_pair2_positive_input"));
|
||||
m_pair2NegativeInputs = GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_pair2_negative_input"));
|
||||
|
||||
m_pair3PositiveInputs = GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_pair3_positive_input"));
|
||||
m_pair3NegativeInputs = GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_pair3_negative_input"));
|
||||
|
||||
m_pair4PositiveInputs = GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_pair4_positive_input"));
|
||||
m_pair4NegativeInputs = GTK_WIDGET(gtk_builder_get_object(m_builder, "combobox_pair4_negative_input"));
|
||||
|
||||
// hide the Change Channel Names button (for compatibility with original contributor (Mensia Technologies) base code)
|
||||
#if defined TARGET_Is_Mensia_Acquisition_Server
|
||||
gtk_widget_set_visible(GTK_WIDGET(gtk_builder_get_object(m_builder, "button_change_channel_names")), false);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(m_builder, "button_change_channel_names")), false);
|
||||
#endif
|
||||
|
||||
// connects callbacks to buttons
|
||||
g_signal_connect(gtk_builder_get_object(m_builder,"button_fast_mode_settings"), "pressed", G_CALLBACK(FastModeSettingsCB), this);
|
||||
g_signal_connect(gtk_builder_get_object(m_builder,"combobox_acquisition_mode"), "changed", G_CALLBACK(ChannelCountChangedCB), this);
|
||||
g_signal_connect(gtk_builder_get_object(m_builder,"checkbutton_aux"), "toggled", G_CALLBACK(ChannelCountChangedCB), this);
|
||||
g_signal_connect(gtk_builder_get_object(m_builder,"checkbutton_trigger"), "toggled", G_CALLBACK(ChannelCountChangedCB), this);
|
||||
g_signal_connect(gtk_builder_get_object(m_builder,"button_start_service"), "pressed", G_CALLBACK(StartServiceCB), this);
|
||||
g_signal_connect(gtk_builder_get_object(m_builder,"button_stop_service"), "pressed", G_CALLBACK(StopServiceCB), this);
|
||||
|
||||
// start the idle function that checks the VampService
|
||||
m_giIdleID = g_idle_add(idle_check_service, m_builder);
|
||||
|
||||
// Configures interface with given values
|
||||
//Data mode
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(m_acquisitionMode), m_headerBrainProductsVAmp->getAcquisitionMode());
|
||||
|
||||
//Device(s)
|
||||
uint32_t nDevice = 0;
|
||||
uint32_t nRetries = 0;
|
||||
|
||||
// We try to get the last opened device, (max 5 tries, 500ms sleep between tries)
|
||||
while (nRetries++ < 5)
|
||||
{
|
||||
nDevice = faGetCount(); // Get the last opened Device id.
|
||||
if (nDevice < 1) { Sleep(500); }
|
||||
else { break; }
|
||||
}
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "VAmp Configuration: " << nDevice << " device(s) connected.\n";
|
||||
|
||||
if (nDevice != 0)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "VAmp Configuration: Device(s) information :\n";
|
||||
|
||||
t_faInformation info;
|
||||
GtkComboBox* comboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_device"));
|
||||
|
||||
for (uint32_t i = 0; i < nDevice; ++i)
|
||||
{
|
||||
int deviceId = faGetId(i);
|
||||
faGetInformation(deviceId, &info);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << " device#" << i << " > Model(" << (info.Model == 1 ? "VAmp 16" : "VAmp 8") << ") | SN(" << info
|
||||
.SerialNumber << ")\n";
|
||||
const std::string buffer = "device#" + std::to_string(i);
|
||||
gtk_combo_box_append_text(comboBox, buffer.c_str());
|
||||
}
|
||||
// active = the last opened device
|
||||
gtk_combo_box_set_active(comboBox, nDevice - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "[INIT] VAmp Driver: faGetCount() returned [" << faGetCount() << "]. No device detected.\n";
|
||||
gtk_widget_set_sensitive(m_dialogFastModeSettings, false);
|
||||
gtk_widget_set_sensitive(m_acquisitionMode, false);
|
||||
gtk_widget_set_sensitive(m_Device, false);
|
||||
gtk_widget_set_sensitive(m_auxiliaryChannels, false);
|
||||
gtk_widget_set_sensitive(m_triggerChannels, false);
|
||||
gtk_widget_set_sensitive(m_pair1PositiveInputs, false);
|
||||
gtk_widget_set_sensitive(m_pair1NegativeInputs, false);
|
||||
gtk_widget_set_sensitive(m_pair2PositiveInputs, false);
|
||||
gtk_widget_set_sensitive(m_pair2NegativeInputs, false);
|
||||
gtk_widget_set_sensitive(m_pair3PositiveInputs, false);
|
||||
gtk_widget_set_sensitive(m_pair3NegativeInputs, false);
|
||||
gtk_widget_set_sensitive(m_pair4PositiveInputs, false);
|
||||
gtk_widget_set_sensitive(m_pair4NegativeInputs, false);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(m_builder, "button_change_channel_names")), false);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(m_builder, "checkbutton_impedance")), false);
|
||||
}
|
||||
|
||||
m_iDeviceCount = nDevice;
|
||||
|
||||
// acquisition mode
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(m_acquisitionMode), m_headerBrainProductsVAmp->getAcquisitionMode());
|
||||
|
||||
// aux channels and triggers
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_auxiliaryChannels), m_rAcquireAuxiliaryAsEEG);
|
||||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_triggerChannels), m_rAcquireTriggerAsEEG);
|
||||
|
||||
// channel count (widget is hidden but is read when clicnking on "channel names..."
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_nChannels), m_header->getChannelCount());
|
||||
|
||||
//SETTINGS:
|
||||
//Adding all possible settings : 7/8/9/10 and -1
|
||||
// and setting active text
|
||||
//_______________________________________________________
|
||||
|
||||
initFastModeSettingsComboBox(m_pair1PositiveInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsPos[0]);
|
||||
initFastModeSettingsComboBox(m_pair1NegativeInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsNeg[0]);
|
||||
initFastModeSettingsComboBox(m_pair2PositiveInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsPos[1]);
|
||||
initFastModeSettingsComboBox(m_pair2NegativeInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsNeg[1]);
|
||||
initFastModeSettingsComboBox(m_pair3PositiveInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsPos[2]);
|
||||
initFastModeSettingsComboBox(m_pair3NegativeInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsNeg[2]);
|
||||
initFastModeSettingsComboBox(m_pair4PositiveInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsPos[3]);
|
||||
initFastModeSettingsComboBox(m_pair4NegativeInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsNeg[3]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CConfigurationBrainProductsVAmp::postConfigure()
|
||||
{
|
||||
if (m_applyConfig)
|
||||
{
|
||||
m_headerBrainProductsVAmp->setAcquisitionMode(static_cast<enum EAcquisitionModes>(gtk_combo_box_get_active(GTK_COMBO_BOX(m_acquisitionMode))));
|
||||
int usbIdx = 0;
|
||||
|
||||
// Device number
|
||||
if (m_iDeviceCount != 0)
|
||||
{
|
||||
int number;
|
||||
GtkComboBox* comboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_device"));
|
||||
if (sscanf(gtk_combo_box_get_active_text(comboBox), "device#%i", &number) == 1)
|
||||
{
|
||||
m_headerBrainProductsVAmp->setDeviceId(faGetId(uint32_t(number)));
|
||||
}
|
||||
}
|
||||
|
||||
// aux and triggers: update the references
|
||||
m_rAcquireAuxiliaryAsEEG = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_auxiliaryChannels)) != 0;
|
||||
m_rAcquireTriggerAsEEG = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_triggerChannels)) != 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// Code from CConfigurationBuilder::postConfigure() in order to have the channel names before making the pairs !
|
||||
if(m_applyConfig)
|
||||
{
|
||||
m_header->setChannelCount(gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(m_nChannels)));
|
||||
|
||||
for (size_t i=0; i!=m_header->getChannelCount(); ++i)
|
||||
{
|
||||
if(m_channelNames[i]!="")
|
||||
{
|
||||
m_header->setChannelName(i, m_channelNames[i].c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//releasing ressources as we dont need it anymore
|
||||
gtk_widget_hide(m_dialogFastModeSettings);
|
||||
|
||||
// making the pairs names
|
||||
if (m_headerBrainProductsVAmp->getAcquisitionMode() == VAmp4Fast)
|
||||
{
|
||||
size_t nPair = 0;
|
||||
std::vector<size_t> pairIdx; // if the user has n<4 pairs but not in the n first settings, we need the indexes
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
{
|
||||
if (m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsPos[i] != -1
|
||||
|| m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsNeg[i] != -1)
|
||||
{
|
||||
nPair++;
|
||||
pairIdx.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
m_headerBrainProductsVAmp->setPairCount(nPair);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << nPair << " channel pairs used.\n";
|
||||
|
||||
// we need to rename the 4 first channels, in order to match the pairs
|
||||
std::vector<std::string> pairNames;
|
||||
|
||||
for (size_t i = 0; i < nPair; ++i)
|
||||
{
|
||||
std::string positiveChannelName, negativeChannelName, pairName;
|
||||
bool positiveChannelSet = false;
|
||||
bool negativeChannelSet = false;
|
||||
|
||||
if (m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsPos[pairIdx[i]] != -1)
|
||||
{
|
||||
positiveChannelName = m_header->getChannelName(m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsPos[pairIdx[i]]);
|
||||
if (positiveChannelName == "")
|
||||
{
|
||||
int val = m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsPos[pairIdx[i]];
|
||||
positiveChannelName = std::to_string(val);
|
||||
}
|
||||
positiveChannelSet = true;
|
||||
}
|
||||
|
||||
if (m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsNeg[pairIdx[i]] != -1)
|
||||
{
|
||||
negativeChannelName = m_header->getChannelName(m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsNeg[pairIdx[i]]);
|
||||
if (negativeChannelName == "")
|
||||
{
|
||||
int val = m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsNeg[pairIdx[i]];
|
||||
negativeChannelName = std::to_string(val);
|
||||
}
|
||||
negativeChannelSet = true;
|
||||
}
|
||||
|
||||
if (negativeChannelSet && positiveChannelSet) { pairName = "Differential (" + positiveChannelName + " - " + negativeChannelName + ")"; }
|
||||
|
||||
if (!negativeChannelSet && positiveChannelSet) { pairName = "Monopolar(+) " + positiveChannelName; }
|
||||
if (negativeChannelSet && !positiveChannelSet) { pairName = "Monopolar(-) " + negativeChannelName; }
|
||||
|
||||
pairNames.push_back(pairName);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < nPair; ++i) { m_headerBrainProductsVAmp->setPairName(i, pairNames[i].c_str()); }
|
||||
}
|
||||
|
||||
//remove the idle function for the service check
|
||||
if (m_giIdleID != 0) g_source_remove(m_giIdleID);
|
||||
|
||||
if (!CConfigurationBuilder::postConfigure()) { return false; } // normal header is filled, ressources are realesed
|
||||
|
||||
// Force sampling frequency
|
||||
#if 0
|
||||
m_headerBrainProductsVAmp->setSamplingFrequency(m_headerBrainProductsVAmp->getAcquisitionMode()==EAcquisitionModes::VAmp4Fast?20000:2000);
|
||||
#else
|
||||
// Signal is now decimated down to 512Hz whatever input sampling frequency was acquired
|
||||
m_headerBrainProductsVAmp->setSamplingFrequency(512);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//____________________________________________________________________________________//
|
||||
|
||||
void CConfigurationBrainProductsVAmp::buttonFastModeSettingsCB()
|
||||
{
|
||||
GtkDialog* dialog = GTK_DIALOG(gtk_builder_get_object(m_builder, "dialog_fast_mode_settings"));
|
||||
int response;
|
||||
do
|
||||
{
|
||||
response = gtk_dialog_run(dialog);
|
||||
switch (response)
|
||||
{
|
||||
case GTK_RESPONSE_APPLY:
|
||||
{
|
||||
t_faDataModeSettings settings;
|
||||
settings.Mode20kHz4Channels.ChannelsPos[0] = atoi(gtk_combo_box_get_active_text(GTK_COMBO_BOX(m_pair1PositiveInputs)));
|
||||
settings.Mode20kHz4Channels.ChannelsNeg[0] = atoi(gtk_combo_box_get_active_text(GTK_COMBO_BOX(m_pair1NegativeInputs)));
|
||||
|
||||
settings.Mode20kHz4Channels.ChannelsPos[1] = atoi(gtk_combo_box_get_active_text(GTK_COMBO_BOX(m_pair2PositiveInputs)));
|
||||
settings.Mode20kHz4Channels.ChannelsNeg[1] = atoi(gtk_combo_box_get_active_text(GTK_COMBO_BOX(m_pair2NegativeInputs)));
|
||||
|
||||
settings.Mode20kHz4Channels.ChannelsPos[2] = atoi(gtk_combo_box_get_active_text(GTK_COMBO_BOX(m_pair3PositiveInputs)));
|
||||
settings.Mode20kHz4Channels.ChannelsNeg[2] = atoi(gtk_combo_box_get_active_text(GTK_COMBO_BOX(m_pair3NegativeInputs)));
|
||||
|
||||
settings.Mode20kHz4Channels.ChannelsPos[3] = atoi(gtk_combo_box_get_active_text(GTK_COMBO_BOX(m_pair4PositiveInputs)));
|
||||
settings.Mode20kHz4Channels.ChannelsNeg[3] = atoi(gtk_combo_box_get_active_text(GTK_COMBO_BOX(m_pair4NegativeInputs)));
|
||||
|
||||
m_headerBrainProductsVAmp->setFastModeSettings(settings);
|
||||
GTKComboBoxSetActiveText(GTK_COMBO_BOX(m_acquisitionMode), "Fast");
|
||||
|
||||
break;
|
||||
}
|
||||
case GTK_RESPONSE_CANCEL:
|
||||
{
|
||||
//Data mode
|
||||
gtk_combo_box_set_active(GTK_COMBO_BOX(m_acquisitionMode), m_headerBrainProductsVAmp->getAcquisitionMode());
|
||||
|
||||
//Settings
|
||||
initFastModeSettingsComboBox(m_pair1PositiveInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsPos[0], false);
|
||||
initFastModeSettingsComboBox(m_pair1NegativeInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsNeg[0], false);
|
||||
initFastModeSettingsComboBox(m_pair2PositiveInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsPos[1], false);
|
||||
initFastModeSettingsComboBox(m_pair2NegativeInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsNeg[1], false);
|
||||
initFastModeSettingsComboBox(m_pair3PositiveInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsPos[2], false);
|
||||
initFastModeSettingsComboBox(m_pair3NegativeInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsNeg[2], false);
|
||||
initFastModeSettingsComboBox(m_pair4PositiveInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsPos[3], false);
|
||||
initFastModeSettingsComboBox(m_pair4NegativeInputs, m_headerBrainProductsVAmp->getFastModeSettings().Mode20kHz4Channels.ChannelsNeg[3], false);
|
||||
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
} while (response != GTK_RESPONSE_APPLY && response != GTK_RESPONSE_CANCEL);
|
||||
|
||||
gtk_widget_hide(GTK_WIDGET(dialog));
|
||||
}
|
||||
|
||||
void CConfigurationBrainProductsVAmp::channelCountChangedCB()
|
||||
{
|
||||
const bool acquireAuxiliaryAsEEG = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_auxiliaryChannels)) != 0;
|
||||
const bool acquireTriggerAsEEG = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_triggerChannels)) != 0;
|
||||
const gint acquisitionMode = gtk_combo_box_get_active(GTK_COMBO_BOX(m_acquisitionMode));
|
||||
|
||||
uint32_t nChannel = m_headerBrainProductsVAmp->getEEGChannelCount(acquisitionMode);
|
||||
nChannel += (acquireTriggerAsEEG ? m_headerBrainProductsVAmp->getTriggerChannelCount(acquisitionMode) : 0);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_nChannels), nChannel);
|
||||
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(m_builder, "button_change_channel_names")), acquisitionMode != VAmp4Fast);
|
||||
gtk_widget_set_sensitive(GTK_WIDGET(gtk_builder_get_object(m_builder, "button_fast_mode_settings")), acquisitionMode == VAmp4Fast);
|
||||
}
|
||||
|
||||
bool CConfigurationBrainProductsVAmp::controlVampService(bool start)
|
||||
{
|
||||
SC_HANDLE scm = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
||||
SC_HANDLE service = nullptr;
|
||||
//bool bReturn = false;
|
||||
//DWORD dwReturn = NO_ERROR;
|
||||
|
||||
if (scm == nullptr || scm == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << " [VampService] The driver was unable to create the handler to the SCManager (err code "
|
||||
<< uint32_t(GetLastError()) << ".\n";
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
service = OpenService(scm, "VampService", SERVICE_ALL_ACCESS);
|
||||
if (service != nullptr)
|
||||
{
|
||||
SERVICE_STATUS status;
|
||||
QueryServiceStatus(service, &status);
|
||||
if (start)
|
||||
{
|
||||
if (status.dwCurrentState != SERVICE_RUNNING)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << " [VampService] Starting VampService...\n";
|
||||
if (!StartService(service, 0, nullptr)) // handle to service, number of arguments, no arguments
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << " [VampService] The driver was unable to restart the service (err code " <<
|
||||
size_t(GetLastError()) << ".\n";
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << " [VampService] VampService started successfully.\n";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else // try to stop service
|
||||
{
|
||||
if (status.dwCurrentState != SERVICE_STOPPED)
|
||||
{
|
||||
ControlService(service, SERVICE_CONTROL_STOP, &status);
|
||||
bool stopped = false;
|
||||
for (int i = 0; i < 5 && !stopped; ++i) // about 5 seconds
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << " [VampService] Checking the VampService...\n";
|
||||
Sleep(1000);
|
||||
ControlService(service, SERVICE_CONTROL_INTERROGATE, &status);
|
||||
if (status.dwCurrentState == SERVICE_STOPPED)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << " [VampService] VampService stopped successfully.\n";
|
||||
stopped = true;
|
||||
}
|
||||
}
|
||||
if (!stopped) m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << " [VampService] After 5 seconds check, VampService did not stop.\n";
|
||||
}
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << " [VampService] Closing handlers.\n";
|
||||
CloseServiceHandle(service);
|
||||
service = nullptr;
|
||||
CloseServiceHandle(scm);
|
||||
scm = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << " [VampService] The driver was unable to create the handler to VampService (err code "
|
||||
<< uint32_t(GetLastError()) << ".\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
if (service != nullptr)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << " [VampService] An error occurred with the VampService handler (err code "
|
||||
<< uint32_t(GetLastError()) << ".\n";
|
||||
CloseServiceHandle(service);
|
||||
service = nullptr;
|
||||
}
|
||||
if (scm != nullptr)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << " [VampService] An error occurred with the SCManager handler (err code "
|
||||
<< uint32_t(GetLastError()) << ".\n";
|
||||
CloseServiceHandle(scm);
|
||||
scm = nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_HAS_ThirdPartyUSBFirstAmpAPI
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyUSBFirstAmpAPI
|
||||
|
||||
#include "../ovasCConfigurationBuilder.h"
|
||||
#include "ovasIDriver.h"
|
||||
#include "ovasCHeaderBrainProductsVAmp.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <FirstAmp.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
/**
|
||||
* \class CConfigurationBrainProductsVAmp
|
||||
* \author Laurent Bonnet (INRIA)
|
||||
* \date 16 nov 2009
|
||||
* \erief The CConfigurationBrainProductsVAmp handles the configuration dialog specific to the VAmp device.
|
||||
*
|
||||
* User can configure the acquisition mode (normal/fast) and the fast mode settings (monopolar or differential pair).
|
||||
*
|
||||
* \sa CDriverBrainProductsVAmp
|
||||
*/
|
||||
class CConfigurationBrainProductsVAmp final : public CConfigurationBuilder
|
||||
{
|
||||
public:
|
||||
|
||||
CConfigurationBrainProductsVAmp(IDriverContext& ctx, const char* gtkBuilderFilename, CHeaderBrainProductsVAmp* headerBrainProductsVAmp,
|
||||
bool& acquireAuxiliaryAsEEG, bool& acquireTriggerAsEEG);
|
||||
|
||||
bool preConfigure() override;
|
||||
bool postConfigure() override;
|
||||
|
||||
void buttonFastModeSettingsCB();
|
||||
void buttonStartServiceCB() { controlVampService(true); }
|
||||
void buttonStopServiceCB() { controlVampService(false); }
|
||||
void channelCountChangedCB();
|
||||
|
||||
protected:
|
||||
|
||||
IDriverContext& m_driverCtx;
|
||||
int m_iDeviceCount = 0;
|
||||
|
||||
CHeaderBrainProductsVAmp* m_headerBrainProductsVAmp = nullptr;
|
||||
|
||||
bool& m_rAcquireAuxiliaryAsEEG;
|
||||
bool& m_rAcquireTriggerAsEEG;
|
||||
|
||||
//widgets
|
||||
GtkWidget* m_dialogFastModeSettings = nullptr;
|
||||
GtkWidget* m_Device = nullptr;
|
||||
GtkWidget* m_acquisitionMode = nullptr;
|
||||
GtkWidget* m_auxiliaryChannels = nullptr;
|
||||
GtkWidget* m_triggerChannels = nullptr;
|
||||
GtkWidget* m_pair1PositiveInputs = nullptr;
|
||||
GtkWidget* m_pair1NegativeInputs = nullptr;
|
||||
GtkWidget* m_pair2PositiveInputs = nullptr;
|
||||
GtkWidget* m_pair2NegativeInputs = nullptr;
|
||||
GtkWidget* m_pair3PositiveInputs = nullptr;
|
||||
GtkWidget* m_pair3NegativeInputs = nullptr;
|
||||
GtkWidget* m_pair4PositiveInputs = nullptr;
|
||||
GtkWidget* m_pair4NegativeInputs = nullptr;
|
||||
|
||||
private:
|
||||
bool controlVampService(bool state);
|
||||
//DWORD startWindowsService(SC_HANDLE hService);
|
||||
//gboolean idleCheckVampService(gpointer data);
|
||||
gint m_giIdleID = 0;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyUSBFirstAmpAPI
|
||||
+705
@@ -0,0 +1,705 @@
|
||||
#if defined TARGET_HAS_ThirdPartyUSBFirstAmpAPI
|
||||
|
||||
#include "ovasCDriverBrainProductsVAmp.h"
|
||||
#include "ovasCConfigurationBrainProductsVAmp.h"
|
||||
#include "ovasCHeaderBrainProductsVAmp.h"
|
||||
|
||||
#include <system/ovCTime.h>
|
||||
#include <windows.h>
|
||||
#include <FirstAmp.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
#define OVAS_Driver_VAmp_ImpedanceMask_BaseComponent 127
|
||||
|
||||
namespace {
|
||||
// Low pass FIR filters before downsampling
|
||||
//
|
||||
// Computed with python and scipy
|
||||
// References :
|
||||
// http://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.tofile.html
|
||||
// http://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.firwin.html
|
||||
//
|
||||
// The following filters have roughly 50ms delay as described in "2.1.4 What is the
|
||||
// delay of a linear-phase FIR?" at http://www.dspguru.com/book/export/html/3 :
|
||||
// n = 200, Fs = 2000, delay = (n-1)/(2*Fs) = 0.04975
|
||||
// n = 2000, Fs = 20000, delay = (n-1)/(2*Fs) = 0.049975
|
||||
//
|
||||
// In order to correct this delay, filtering should be done as a two steps process with a forward filter
|
||||
// followed by a backward filter. However, this leads to an n square complexity where a linear complexity is
|
||||
// sufficient in forward only filtering (using 100kHz input signal, n=10000 taps !)
|
||||
//
|
||||
// To avoid such complexity, it is chosen to antedate acquired samples by 50ms cheating the drift correction
|
||||
// process. Indeed, the acquisition server application monitors the drifting of the acquisition process and
|
||||
// corrects this drift upon demand. It is up to the driver to require this correction and it can be chosen not
|
||||
// to fit the 0 drift, but to fit an arbitrary fixed drift instead.
|
||||
//
|
||||
// The offset for this correction is stored in the m_nDriftOffsetSample variable
|
||||
|
||||
/* ---------------------------------------------------------------------------------------------------------------
|
||||
from scipy import signal
|
||||
|
||||
N = 200
|
||||
signal.firwin(N, cutoff= 256./(0.5*2000.), window='hamming').tofile("f64_2k_512.bin")
|
||||
signal.firwin(N, cutoff= 128./(0.5*2000.), window='hamming').tofile("f64_2k_256.bin")
|
||||
signal.firwin(N, cutoff= 64./(0.5*2000.), window='hamming').tofile("f64_2k_128.bin")
|
||||
|
||||
N = 2000
|
||||
signal.firwin(N, cutoff=2048./(0.5*20000.), window='hamming').tofile("f64_20k_4096.bin")
|
||||
signal.firwin(N, cutoff=1024./(0.5*20000.), window='hamming').tofile("f64_20k_2048.bin")
|
||||
signal.firwin(N, cutoff= 512./(0.5*20000.), window='hamming').tofile("f64_20k_1024.bin")
|
||||
signal.firwin(N, cutoff= 256./(0.5*20000.), window='hamming').tofile("f64_20k_512.bin")
|
||||
signal.firwin(N, cutoff= 128./(0.5*20000.), window='hamming').tofile("f64_20k_256.bin")
|
||||
signal.firwin(N, cutoff= 64./(0.5*20000.), window='hamming').tofile("f64_20k_128.bin")
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------- */
|
||||
|
||||
bool loadFilter(const char* filename, std::vector<double>& filters)
|
||||
{
|
||||
FILE* file = fopen(filename, "rb");
|
||||
if (!file)
|
||||
{
|
||||
filters.clear();
|
||||
filters.push_back(1);
|
||||
return false;
|
||||
}
|
||||
fseek(file, 0, SEEK_END);
|
||||
const size_t len = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
filters.resize(len / sizeof(double));
|
||||
fread(&filters[0], len, 1, file);
|
||||
fclose(file);
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
CDriverBrainProductsVAmp::CDriverBrainProductsVAmp(IDriverContext& ctx)
|
||||
: IDriver(ctx), m_settings("AcquisitionServer_Driver_BrainProducts-VAmp", m_driverCtx.getConfigurationManager())
|
||||
{
|
||||
// default mode is VAmp 16, aux and trigger depending on the config tokens
|
||||
m_header.setAcquisitionMode(VAmp16);
|
||||
|
||||
m_nEEGChannel = m_header.getEEGChannelCount(VAmp16);
|
||||
m_nAuxiliaryChannel = (m_acquireAuxiliaryAsEEG ? m_header.getAuxiliaryChannelCount(VAmp16) : 0);
|
||||
m_nTriggerChannel = (m_acquireTriggerAsEEG ? m_header.getTriggerChannelCount(VAmp16) : 0);
|
||||
m_header.setChannelCount(m_nEEGChannel + m_nAuxiliaryChannel + m_nTriggerChannel);
|
||||
|
||||
if (m_acquireAuxiliaryAsEEG)
|
||||
{
|
||||
m_header.setChannelName(m_nEEGChannel, "Aux 1");
|
||||
m_header.setChannelName(m_nEEGChannel + 1, "Aux 2");
|
||||
}
|
||||
if (m_acquireTriggerAsEEG) { m_header.setChannelName(m_nEEGChannel + m_nAuxiliaryChannel, "Trigger line"); }
|
||||
|
||||
m_header.setSamplingFrequency(512);
|
||||
|
||||
t_faDataModeSettings settings;
|
||||
settings.Mode20kHz4Channels.ChannelsPos[0] = 7;
|
||||
settings.Mode20kHz4Channels.ChannelsNeg[0] = -1;
|
||||
settings.Mode20kHz4Channels.ChannelsPos[1] = 8;
|
||||
settings.Mode20kHz4Channels.ChannelsNeg[1] = -1;
|
||||
settings.Mode20kHz4Channels.ChannelsPos[2] = 9;
|
||||
settings.Mode20kHz4Channels.ChannelsNeg[2] = -1;
|
||||
settings.Mode20kHz4Channels.ChannelsPos[3] = 10;
|
||||
settings.Mode20kHz4Channels.ChannelsNeg[3] = -1;
|
||||
|
||||
m_header.setFastModeSettings(settings);
|
||||
m_header.setDeviceId(FA_ID_INVALID);
|
||||
|
||||
// @note m_header is CHeaderBrainProductsVAmp, whereas the current interface supports only IHeader. Thus, some info may not be loaded/saved.
|
||||
m_settings.add("Header", &m_header);
|
||||
m_settings.add("AcquireAuxiliaryAsEEG", &m_acquireAuxiliaryAsEEG);
|
||||
m_settings.add("AcquireTriggerAsEEG", &m_acquireTriggerAsEEG);
|
||||
m_settings.load();
|
||||
}
|
||||
|
||||
CDriverBrainProductsVAmp::~CDriverBrainProductsVAmp() {}
|
||||
|
||||
const char* CDriverBrainProductsVAmp::getName() { return "Brain Products V-Amp / First-Amp"; }
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverBrainProductsVAmp::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "INIT called.\n";
|
||||
if (m_driverCtx.isConnected())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[INIT] VAmp Driver: Driver already initialized.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_acquireAuxiliaryAsEEG) m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "[INIT] VAmp Driver: will acquire aux as EEG\n";
|
||||
else m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "[INIT] VAmp Driver: will NOT acquire aux as EEG\n";
|
||||
if (m_acquireTriggerAsEEG) m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "[INIT] VAmp Driver: will acquire trigger as EEG\n";
|
||||
else m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "[INIT] VAmp Driver: will NOT acquire trigger as EEG\n";
|
||||
|
||||
m_acquisitionMode = m_header.getAcquisitionMode();
|
||||
m_nEEGChannel = m_header.getEEGChannelCount(m_acquisitionMode);
|
||||
m_nAuxiliaryChannel = (m_acquireAuxiliaryAsEEG ? m_header.getAuxiliaryChannelCount(m_acquisitionMode) : 0);
|
||||
m_nTriggerChannel = (m_acquireTriggerAsEEG ? m_header.getTriggerChannelCount(m_acquisitionMode) : 0);
|
||||
|
||||
m_header.setChannelCount(m_nEEGChannel + m_nAuxiliaryChannel + m_nTriggerChannel);
|
||||
|
||||
if (m_acquireAuxiliaryAsEEG)
|
||||
{
|
||||
if (strlen(m_header.getChannelName(m_nEEGChannel)) == 0 || strcmp(m_header.getChannelName(m_nEEGChannel), "Trigger line") == 0)
|
||||
{
|
||||
m_header.setChannelName(m_nEEGChannel, "Aux 1");
|
||||
}
|
||||
if (strlen(m_header.getChannelName(m_nEEGChannel + 1)) == 0) { m_header.setChannelName(m_nEEGChannel + 1, "Aux 2"); }
|
||||
}
|
||||
if (m_acquireTriggerAsEEG) { m_header.setChannelName(m_nEEGChannel + m_nAuxiliaryChannel, "Trigger line"); }
|
||||
|
||||
if (!m_header.isChannelCountSet() || !m_header.isSamplingFrequencySet())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[INIT] VAmp Driver: Channel count or frequency not set.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Builds up physical sampling rate
|
||||
switch (m_acquisitionMode)
|
||||
{
|
||||
case VAmp16: m_physicalSamplingHz = 2000;
|
||||
break;
|
||||
case VAmp8: m_physicalSamplingHz = 2000;
|
||||
break;
|
||||
case VAmp4Fast: m_physicalSamplingHz = 20000;
|
||||
break;
|
||||
default: m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[INIT] Vamp Driver: Unsupported acquisition mode [" << m_acquisitionMode << "].\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Loading low pass filter for decimation
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "[INIT] Vamp Driver: Setting up the FIR filter for signal decimation (physical rate " <<
|
||||
m_physicalSamplingHz << " > driver rate " << m_header.getSamplingFrequency() << ").\n";
|
||||
switch (m_physicalSamplingHz)
|
||||
{
|
||||
case 2000: loadFilter(Directories::getDataDir() + "applications/acquisition-server/filters/f64_2k_512.bin", m_filters);
|
||||
break;
|
||||
case 20000: loadFilter(Directories::getDataDir() + "applications/acquisition-server/filters/f64_20k_512.bin", m_filters);
|
||||
break;
|
||||
default: m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[INIT] Vamp Driver: Unsupported physical sampling rate [" << m_physicalSamplingHz << "].\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Builds up a buffer to store acquired samples. This buffer will be sent to the acquisition server later.
|
||||
m_samples.clear();
|
||||
m_samples.resize(m_header.getChannelCount());
|
||||
m_resolutions.clear();
|
||||
m_resolutions.resize(m_header.getChannelCount());
|
||||
|
||||
// Prepares cache for filtering
|
||||
m_sampleCaches.clear();
|
||||
for (size_t i = 0; i < m_filters.size(); ++i) { m_sampleCaches.push_back(m_samples); }
|
||||
|
||||
// Setting the inner latency as described in the beginning of the file
|
||||
m_nDriftOffsetSample = int64_t(m_header.getSamplingFrequency() * 50) / 1000;
|
||||
m_driverCtx.setInnerLatencySampleCount(-m_nDriftOffsetSample);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Driver inner latency set to 50ms to compensate FIR filtering.\n";
|
||||
|
||||
// Prepares downsampling
|
||||
m_counter = 0;
|
||||
m_counterStep = (uint64_t(m_header.getSamplingFrequency()) << 32) / m_physicalSamplingHz;
|
||||
|
||||
// Gets device Id
|
||||
int deviceId = m_header.getDeviceId();
|
||||
|
||||
//__________________________________
|
||||
// Hardware initialization
|
||||
|
||||
// if no device selected with the properties dialog
|
||||
// we take the last device connected
|
||||
if (deviceId == FA_ID_INVALID)
|
||||
{
|
||||
// We try to get the last opened device,
|
||||
uint32_t lastOpenedDeviceID = faGetCount(); // Get the last opened Device id.
|
||||
|
||||
if (lastOpenedDeviceID == FA_ID_INVALID) // failed
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[INIT] VAmp Driver: faGetCount failed to get last opened device.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
deviceId = faGetId(lastOpenedDeviceID - 1);
|
||||
m_header.setDeviceId(deviceId);
|
||||
}
|
||||
|
||||
if (deviceId != FA_ID_INVALID)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "[INIT] VAmp Driver: Active device ID(" << m_header.getDeviceId() << ").\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[INIT] VAmp Driver: No device connected !\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open the device.
|
||||
int openReturn = faOpen(deviceId);
|
||||
if (openReturn != FA_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[INIT] VAmp Driver: faOpen(" << deviceId << ") FAILED(" << openReturn << ").\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_acquisitionMode == VAmp4Fast) { faSetDataMode(deviceId, dm20kHz4Channels, &(m_header.getFastModeSettings())); }
|
||||
else { faSetDataMode(deviceId, dmNormal, nullptr); }
|
||||
|
||||
if (m_driverCtx.isImpedanceCheckRequested())
|
||||
{
|
||||
faStart(deviceId);
|
||||
faStartImpedance(deviceId);
|
||||
}
|
||||
|
||||
if (!m_driverCtx.isImpedanceCheckRequested())
|
||||
{
|
||||
HBITMAP bitmap = HBITMAP(LoadImage(nullptr, Directories::getDataDir() + "/applications/acquisition-server/vamp-standby.bmp",IMAGE_BITMAP, 0, 0,
|
||||
LR_LOADFROMFILE));
|
||||
if (bitmap == nullptr || faSetBitmap(m_header.getDeviceId(), bitmap) != FA_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "[LOOP] VAmp Driver: BMP load failed.\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Gets properties
|
||||
t_faProperty properties;
|
||||
if (faGetProperty(m_header.getDeviceId(), &properties))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not get properties - Got error \n";
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < m_nEEGChannel; i++, j++) m_resolutions[j] = m_header.getChannelGain(i) * properties.ResolutionEeg * 1E6f; // converts to �V
|
||||
for (size_t i = 0; i < m_nAuxiliaryChannel; i++, j++) m_resolutions[j] = properties.ResolutionAux * 1E6f; // converts to �V
|
||||
|
||||
for (size_t i = 0; i < m_nEEGChannel; i++, j++) { m_header.setChannelUnits(i, OVTK_UNIT_Volts, OVTK_FACTOR_Micro); }
|
||||
for (size_t i = 0; i < m_nAuxiliaryChannel; i++, j++)
|
||||
{
|
||||
m_header.setChannelUnits(i, OVTK_UNIT_Volts, OVTK_FACTOR_Micro); // converts to �V
|
||||
}
|
||||
|
||||
//__________________________________
|
||||
// Saves parameters
|
||||
m_callback = &callback;
|
||||
m_nSamplePerSentBlock = nSamplePerSentBlock;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBrainProductsVAmp::start()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "START called.\n";
|
||||
if (!m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
if (m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
m_firstStart = true;
|
||||
uint32_t error = FA_ERR_OK;
|
||||
int deviceId = m_header.getDeviceId();
|
||||
|
||||
if (m_driverCtx.isImpedanceCheckRequested())
|
||||
{
|
||||
faStopImpedance(deviceId);
|
||||
// stops the impedance mode, but not the acquisition
|
||||
}
|
||||
else
|
||||
{
|
||||
// we did not start the acquisition yet, let's do it now.
|
||||
error = faStart(deviceId);
|
||||
}
|
||||
|
||||
if (error != FA_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[START] VAmp Driver: faStart FAILED(" << error << "). Closing device.\n";
|
||||
faClose(deviceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
//The bonus...
|
||||
HBITMAP bitmap = HBITMAP(LoadImage(nullptr, Directories::getDataDir() + "/applications/acquisition-server/vamp-acquiring.bmp",IMAGE_BITMAP, 0, 0,
|
||||
LR_LOADFROMFILE));
|
||||
if (bitmap == nullptr || faSetBitmap(m_header.getDeviceId(), bitmap) != FA_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "[START] VAmp Driver: BMP load failed.\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBrainProductsVAmp::loop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
t_faDataModel16 bufferVAmp16; // buffer for the next block in normal mode
|
||||
uint32_t lengthVAmp16 = sizeof(t_faDataModel16);
|
||||
|
||||
t_faDataModel8 bufferVAmp8; // buffer for the next block in normal mode
|
||||
uint32_t lengthVAmp8 = sizeof(t_faDataModel8);
|
||||
|
||||
t_faDataFormatMode20kHz bufferVamp4Fast; // buffer for fast mode acquisition
|
||||
uint32_t lengthVAmp4Fast = sizeof(t_faDataFormatMode20kHz);
|
||||
|
||||
uint32_t status = 0;
|
||||
|
||||
int deviceId = m_header.getDeviceId();
|
||||
|
||||
if (m_driverCtx.isStarted())
|
||||
{
|
||||
uint32_t nReceivedSamples = 0;
|
||||
#if DEBUG
|
||||
uint32_t nReadError = 0;
|
||||
uint32_t nReadSuccess = 0;
|
||||
uint32_t nReadZero = 0;
|
||||
#endif
|
||||
if (m_firstStart)
|
||||
{
|
||||
//empty buffer
|
||||
switch (m_acquisitionMode)
|
||||
{
|
||||
case VAmp16: while (faGetData(deviceId, &bufferVAmp16, lengthVAmp16) > 0);
|
||||
status = bufferVAmp16.Status;
|
||||
break;
|
||||
case VAmp8: while (faGetData(deviceId, &bufferVAmp8, lengthVAmp8) > 0);
|
||||
status = bufferVAmp8.Status;
|
||||
break;
|
||||
case VAmp4Fast: while (faGetData(deviceId, &bufferVamp4Fast, lengthVAmp4Fast) > 0);
|
||||
status = bufferVamp4Fast.Status;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
// Trigger: Digital inputs (bits 0 - 8) + output (bit 9) state + 22 MSB reserved bits
|
||||
// The trigger value received is in [0-255]
|
||||
status &= 0x000000ff;
|
||||
m_lastTrigger = status;
|
||||
m_firstStart = false;
|
||||
}
|
||||
|
||||
bool finished = false;
|
||||
while (!finished)
|
||||
{
|
||||
// we need to "getData" with the right output structure according to acquisition mode
|
||||
|
||||
int returnLength;
|
||||
signed int* eegArray;
|
||||
signed int* auxiliaryArray = nullptr;
|
||||
status = 0;
|
||||
switch (m_acquisitionMode)
|
||||
{
|
||||
case VAmp16: returnLength = faGetData(deviceId, &bufferVAmp16, lengthVAmp16);
|
||||
eegArray = bufferVAmp16.Main;
|
||||
auxiliaryArray = bufferVAmp16.Aux;
|
||||
status = bufferVAmp16.Status;
|
||||
break;
|
||||
|
||||
case VAmp8: returnLength = faGetData(deviceId, &bufferVAmp8, lengthVAmp8);
|
||||
eegArray = bufferVAmp8.Main;
|
||||
auxiliaryArray = bufferVAmp8.Aux;
|
||||
status = bufferVAmp8.Status;
|
||||
break;
|
||||
|
||||
case VAmp4Fast: returnLength = faGetData(deviceId, &bufferVamp4Fast, lengthVAmp4Fast);
|
||||
eegArray = bufferVamp4Fast.Main;
|
||||
// auxiliaryArray = bufferVamp4Fast.Aux;
|
||||
status = bufferVamp4Fast.Status;
|
||||
break;
|
||||
|
||||
default: m_driverCtx.getLogManager() << Kernel::LogLevel_ImportantWarning << "[LOOP] VAmp Driver: unsupported acquisition mode, this should never happen\n";
|
||||
return false;
|
||||
}
|
||||
// Trigger: Digital inputs (bits 0 - 8) + output (bit 9) state + 22 MSB reserved bits
|
||||
// The trigger value received is in [0-255]
|
||||
status &= 0x000000ff;
|
||||
|
||||
if (returnLength > 0)
|
||||
{
|
||||
#if DEBUG
|
||||
nReadSuccess++;
|
||||
#endif
|
||||
|
||||
// Stores acquired sample
|
||||
for (size_t i = 0; i < m_nEEGChannel; ++i) { m_samples[i] = float((eegArray[i] - eegArray[m_nEEGChannel]) * m_resolutions[i]); }
|
||||
for (size_t i = 0; i < m_nAuxiliaryChannel; ++i) { m_samples[m_nEEGChannel + i] = float(auxiliaryArray[i] * m_resolutions[i]); }
|
||||
for (size_t i = 0; i < m_nTriggerChannel; ++i) { m_samples[m_nEEGChannel + m_nAuxiliaryChannel + i] = float(status); }
|
||||
|
||||
// Updates cache
|
||||
//m_sampleCaches.erase(m_sampleCaches.begin());
|
||||
m_sampleCaches.pop_front();
|
||||
m_sampleCaches.push_back(m_samples);
|
||||
|
||||
// Every time that a trigger has changed, we send a stimulation.
|
||||
if (status != m_lastTrigger)
|
||||
{
|
||||
// The date is relative to the last buffer start time (cf the setSamples before setStimulationSet)
|
||||
const uint64_t date = CTime(m_physicalSamplingHz, m_nTotalSample).time();
|
||||
// Code of stimulation = OVTK_StimulationId_LabelStart + value of the trigger bytes.
|
||||
m_stimSet.appendStimulation(OVTK_StimulationId_Label(status), 0, 0);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "[LOOP] VAmp Driver: Send stimulation: " << status << " at date: " << date << ".\n";
|
||||
m_lastTrigger = status;
|
||||
}
|
||||
|
||||
m_counter += m_counterStep;
|
||||
if (m_counter >= (1LL << 32))
|
||||
{
|
||||
m_counter -= (1LL << 32);
|
||||
|
||||
// Filters last samples
|
||||
for (size_t i = 0; i < m_samples.size(); ++i)
|
||||
{
|
||||
m_samples[i] = 0;
|
||||
auto it = m_sampleCaches.begin();
|
||||
for (size_t j = 0; j < m_filters.size(); ++j)
|
||||
{
|
||||
//m_samples[i]+=m_filters[j]*m_sampleCaches[j][i];
|
||||
m_samples[i] += float(m_filters[j] * (*it)[i]);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
m_callback->setSamples(&m_samples[0], 1);
|
||||
m_callback->setStimulationSet(m_stimSet);
|
||||
m_stimSet.clear();
|
||||
}
|
||||
nReceivedSamples++;
|
||||
}
|
||||
else if (returnLength == 0)
|
||||
{
|
||||
finished = true;
|
||||
System::Time::sleep(2);
|
||||
}
|
||||
#if DEBUG
|
||||
if(returnLength < 0)
|
||||
{
|
||||
nReadError++;
|
||||
finished = true;
|
||||
}
|
||||
if(returnLength == 0)
|
||||
{
|
||||
nReadZero++;
|
||||
finished = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#if DEBUG
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "[LOOP] VAmp Driver: stats for the current block : Success="<<l_uint32ReadSuccessCount<<" Error="<<l_uint32ReadErrorCount<<" Zero="<<nReadZero<<"\n";
|
||||
#endif
|
||||
//____________________________
|
||||
|
||||
// As described in at the begining of this file, the drift
|
||||
// correction process is altered to consider the ~ 50ms delay
|
||||
// of the single way filtering process
|
||||
// Inner latency was set accordingly.
|
||||
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
|
||||
}
|
||||
else
|
||||
{
|
||||
// we drop any data in internal buffer
|
||||
switch (m_acquisitionMode)
|
||||
{
|
||||
case VAmp16: while (faGetData(deviceId, &bufferVAmp16, lengthVAmp16) > 0);
|
||||
break;
|
||||
case VAmp8: while (faGetData(deviceId, &bufferVAmp8, lengthVAmp8) > 0);
|
||||
break;
|
||||
case VAmp4Fast: while (faGetData(deviceId, &bufferVamp4Fast, lengthVAmp4Fast) > 0);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (m_driverCtx.isImpedanceCheckRequested())
|
||||
{
|
||||
// Reads impedances
|
||||
std::vector<uint32_t> buffer;
|
||||
buffer.resize(20, 0); // all possible channels + ground electrode
|
||||
uint32_t error = faGetImpedance(deviceId, &buffer[0], 20 * sizeof(uint32_t));
|
||||
if (error != FA_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[LOOP] Can not read impedances on device id " << deviceId << " - faGetImpedance FAILED(" <<
|
||||
error << ")\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Updates impedances
|
||||
|
||||
const uint64_t goodImpedanceLimit = m_driverCtx.getConfigurationManager().expandAsUInteger("${AcquisitionServer_DefaultImpedanceLimit}", 5000);
|
||||
// as with the default acticap settings (values provided by Brain Products) :
|
||||
const uint64_t badImpedanceLimit = 2 * goodImpedanceLimit;
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Impedances are [ ";
|
||||
for (uint32_t j = 0; j < m_nEEGChannel; ++j)//we do not update the last impedance (ground)
|
||||
{
|
||||
m_driverCtx.updateImpedance(j, buffer[j]);
|
||||
m_driverCtx.getLogManager() << buffer[j] << " ";
|
||||
}
|
||||
m_driverCtx.getLogManager() << "]\n";
|
||||
|
||||
//print impedances on the LCD screen
|
||||
HBITMAP bitmapHandler = HBITMAP(LoadImage(nullptr, Directories::getDataDir() + "/applications/acquisition-server/vamp-impedance-mask.bmp",
|
||||
IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION));
|
||||
if (bitmapHandler)
|
||||
{
|
||||
BITMAP bitmap;
|
||||
GetObject(bitmapHandler, sizeof(bitmap), &bitmap);
|
||||
//uint32_t width = bitmap.bmWidth;
|
||||
const uint32_t height = bitmap.bmHeight;
|
||||
BYTE* bytePtr = static_cast<BYTE*>(bitmap.bmBits); // 3 bytes per pixel for RGB values
|
||||
|
||||
//printf("H x W : %u x %u with %u per scan\n", height, width, bitmap.bmWidthBytes);
|
||||
|
||||
for (uint32_t y = 0; y < height; ++y) // scan lines
|
||||
{
|
||||
uint32_t x = 0;
|
||||
for (uint32_t byteIdx = 0; byteIdx < uint32_t(bitmap.bmWidthBytes); byteIdx += 3)
|
||||
{
|
||||
x++;
|
||||
unsigned char* b = (bytePtr + bitmap.bmWidthBytes * y + byteIdx);
|
||||
unsigned char* g = (bytePtr + bitmap.bmWidthBytes * y + byteIdx + 1);
|
||||
unsigned char* r = (bytePtr + bitmap.bmWidthBytes * y + byteIdx + 2);
|
||||
// The impedance mask is a BLACK and WHITE bitmap with grey squares for the channels.
|
||||
// For each channel, the grey RGB components are equal to OVAS_Driver_VAmp_ImpedanceMask_BaseComponent - channel index - 1
|
||||
// e.g. with 16 channels and base at 127, the 5th channel square as RGB components to 127 - 5 = 122
|
||||
// Every pixel in the grey range is a channel square and needs repaint according to impedance.
|
||||
if (*r < OVAS_Driver_VAmp_ImpedanceMask_BaseComponent && *r >= OVAS_Driver_VAmp_ImpedanceMask_BaseComponent - m_nEEGChannel
|
||||
&& *g < OVAS_Driver_VAmp_ImpedanceMask_BaseComponent && *g >= OVAS_Driver_VAmp_ImpedanceMask_BaseComponent - m_nEEGChannel
|
||||
&& *b < OVAS_Driver_VAmp_ImpedanceMask_BaseComponent && *b >= OVAS_Driver_VAmp_ImpedanceMask_BaseComponent - m_nEEGChannel)
|
||||
{
|
||||
const uint32_t idx = OVAS_Driver_VAmp_ImpedanceMask_BaseComponent - *r - 1; //0-based
|
||||
if (buffer[idx] <= goodImpedanceLimit)
|
||||
{
|
||||
// good impedance: green
|
||||
*r = 0x00;
|
||||
*g = 0xff;
|
||||
*b = 0x00;
|
||||
}
|
||||
else if (buffer[idx] <= badImpedanceLimit)
|
||||
{
|
||||
// unsufficient impedance: yellow
|
||||
*r = 0xff;
|
||||
*g = 0xff;
|
||||
*b = 0x00;
|
||||
}
|
||||
else
|
||||
{
|
||||
// bad impedance: red
|
||||
*r = 0xff;
|
||||
*g = 0x00;
|
||||
*b = 0x00;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bitmapHandler == nullptr || faSetBitmap(m_header.getDeviceId(), bitmapHandler) != FA_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "[LOOP] VAmp Driver: BMP load failed.\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBrainProductsVAmp::stop()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "STOP called.\n";
|
||||
if (!m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
if (!m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
if (m_driverCtx.isImpedanceCheckRequested()) { faStartImpedance(m_header.getDeviceId()); }
|
||||
|
||||
m_firstStart = false;
|
||||
if (!m_driverCtx.isImpedanceCheckRequested())
|
||||
{
|
||||
HBITMAP bitmap = (HBITMAP)LoadImage(nullptr, Directories::getDataDir() + "/applications/acquisition-server/vamp-standby.bmp",IMAGE_BITMAP, 0, 0,
|
||||
LR_LOADFROMFILE);
|
||||
if (bitmap == nullptr || faSetBitmap(m_header.getDeviceId(), bitmap) != FA_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "[STOP] VAmp Driver: BMP load failed.\n";
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverBrainProductsVAmp::uninitialize()
|
||||
{
|
||||
if (!m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
if (m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
uint32_t error = faStop(m_header.getDeviceId());
|
||||
if (error != FA_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[UINIT] VAmp Driver: faStop FAILED(" << error << ").\n";
|
||||
faClose(m_header.getDeviceId());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Uninitialize called. Closing the device.\n";
|
||||
|
||||
|
||||
// for a black bitmap use :
|
||||
//HDC hDC = CreateCompatibleDC(NULL);
|
||||
//HBITMAP bitmap = CreateCompatibleBitmap(l_hDC, 320, 240);
|
||||
// Default bitmap : BP image (provided by N. Soldati)
|
||||
HBITMAP bitmap = HBITMAP(LoadImage(nullptr, Directories::getDataDir() + "/applications/acquisition-server/vamp-default.bmp",IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE));
|
||||
if (faSetBitmap(m_header.getDeviceId(), bitmap) != FA_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "[UINIT] VAmp Driver: BMP load failed.\n";
|
||||
}
|
||||
|
||||
|
||||
error = faClose(m_header.getDeviceId());
|
||||
if (error != FA_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[UINIT] VAmp Driver: faClose FAILED(" << error << ").\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_callback = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverBrainProductsVAmp::configure()
|
||||
{
|
||||
// the specific header is passed into the specific configuration
|
||||
CConfigurationBrainProductsVAmp config(m_driverCtx, Directories::getDataDir() + "/applications/acquisition-server/interface-BrainProducts-VAmp.ui",
|
||||
&m_header, m_acquireAuxiliaryAsEEG, m_acquireTriggerAsEEG);
|
||||
|
||||
if (!config.configure(*(m_header.getBasicHeader()))) { return false; } // the basic configure will use the basic header
|
||||
|
||||
m_settings.save();
|
||||
|
||||
if (m_acquisitionMode == VAmp4Fast)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Pair names :\n";
|
||||
for (uint32_t i = 0; i < m_header.getPairCount(); ++i)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << " Pair " << i << " > " << m_header.getPairName(i) << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_HAS_ThirdPartyUSBFirstAmpAPI
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyUSBFirstAmpAPI
|
||||
|
||||
#include "ovasIDriver.h"
|
||||
#include "ovasCHeaderBrainProductsVAmp.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
|
||||
#include "../ovasCSettingsHelper.h"
|
||||
#include "../ovasCSettingsHelperOperators.h"
|
||||
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CDriverBrainProductsVAmp
|
||||
* \author Laurent Bonnet (INRIA)
|
||||
* \date 16 nov 2009
|
||||
* \erief The CDriverBrainProductsVAmp allows the acquisition server to acquire data from a USB-VAmp-16 amplifier (BrainProducts GmbH).
|
||||
*
|
||||
* The driver allows 2 different acquisition modes: normal (2kHz sampling frequency - max 16 electrodes)
|
||||
* or fast (20kHz sampling frequency, 4 monopolar or differential channels).
|
||||
* The driver uses a dedicated Header.
|
||||
*
|
||||
* \sa CHeaderBrainProductsVAmp
|
||||
*/
|
||||
class CDriverBrainProductsVAmp final : public IDriver
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CDriverBrainProductsVAmp(IDriverContext& ctx);
|
||||
~CDriverBrainProductsVAmp() 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 { return true; }
|
||||
bool configure() override;
|
||||
const IHeader* getHeader() override { return &m_header; }
|
||||
|
||||
protected:
|
||||
|
||||
SettingsHelper m_settings;
|
||||
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
|
||||
bool m_acquireAuxiliaryAsEEG = false;
|
||||
bool m_acquireTriggerAsEEG = false;
|
||||
|
||||
CHeaderBrainProductsVAmp m_header;
|
||||
|
||||
EAcquisitionModes m_acquisitionMode = VAmp16;
|
||||
uint32_t m_nSamplePerSentBlock = 0;
|
||||
uint32_t m_nTotalSample = 0;
|
||||
uint32_t m_nEEGChannel = 0;
|
||||
uint32_t m_nAuxiliaryChannel = 0;
|
||||
uint32_t m_nTriggerChannel = 0;
|
||||
|
||||
std::vector<size_t> m_stimulationIDs;
|
||||
std::vector<size_t> m_stimulationDates;
|
||||
std::vector<size_t> m_stimulationSamples;
|
||||
CStimulationSet m_stimSet;
|
||||
uint32_t m_lastTrigger = 0;
|
||||
|
||||
std::deque<std::vector<float>> m_sampleCaches;
|
||||
std::vector<float> m_samples;
|
||||
std::vector<double> m_filters;
|
||||
std::vector<float> m_resolutions;
|
||||
|
||||
int64_t m_nDriftOffsetSample = 0;
|
||||
uint32_t m_physicalSamplingHz = 0;
|
||||
uint64_t m_counterStep = 0;
|
||||
uint64_t m_counter = 0;
|
||||
|
||||
private:
|
||||
|
||||
bool m_firstStart = false;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyUSBFirstAmpAPI
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
#include "ovasCHeaderBrainProductsVAmp.h"
|
||||
#include "ovasCConfigurationBrainProductsVAmp.h"
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyUSBFirstAmpAPI
|
||||
|
||||
#include <windows.h>
|
||||
#include <FirstAmp.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#define _NoValueI_ 0xffffffff
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
static size_t eegChannelCounts[] = { 16, 8, 4 };
|
||||
static size_t auxiliaryChannelCounts[] = { 2, 2, 0 };
|
||||
static size_t triggerChannelCounts[] = { 1, 1, 1 };
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
CHeaderBrainProductsVAmp::CHeaderBrainProductsVAmp()
|
||||
{
|
||||
m_basicHeader = new CHeader();
|
||||
|
||||
// additional information
|
||||
m_deviceID = -1;
|
||||
m_acquisitionMode = VAmp16;
|
||||
|
||||
// Pair information
|
||||
m_nPair = 0;
|
||||
}
|
||||
|
||||
void CHeaderBrainProductsVAmp::reset()
|
||||
{
|
||||
m_basicHeader->reset();
|
||||
m_deviceID = FA_ID_INVALID;
|
||||
|
||||
// Pair information
|
||||
m_nPair = 0;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
size_t CHeaderBrainProductsVAmp::getEEGChannelCount(const size_t acquisitionMode) { return eegChannelCounts[acquisitionMode]; }
|
||||
size_t CHeaderBrainProductsVAmp::getAuxiliaryChannelCount(const size_t acquisitionMode) { return auxiliaryChannelCounts[acquisitionMode]; }
|
||||
size_t CHeaderBrainProductsVAmp::getTriggerChannelCount(const size_t acquisitionMode) { return triggerChannelCounts[acquisitionMode]; }
|
||||
|
||||
// Pair information
|
||||
|
||||
bool CHeaderBrainProductsVAmp::setPairCount(const size_t count)
|
||||
{
|
||||
m_nPair = count;
|
||||
m_names.clear();
|
||||
m_gains.clear();
|
||||
return m_nPair != size_t(-1);
|
||||
}
|
||||
|
||||
bool CHeaderBrainProductsVAmp::setPairName(const size_t index, const char* name)
|
||||
{
|
||||
m_names[index] = name;
|
||||
return index < m_nPair;
|
||||
}
|
||||
|
||||
bool CHeaderBrainProductsVAmp::setPairGain(const size_t index, const float gain)
|
||||
{
|
||||
m_gains[index] = gain;
|
||||
return index < m_nPair;
|
||||
}
|
||||
|
||||
bool CHeaderBrainProductsVAmp::setPairUnits(const size_t index, const size_t unit, const size_t factor)
|
||||
{
|
||||
m_units[index] = std::pair<size_t, size_t>(unit, factor);
|
||||
return index < m_nPair;
|
||||
}
|
||||
|
||||
bool CHeaderBrainProductsVAmp::setFastModeSettings(t_faDataModeSettings settings)
|
||||
{
|
||||
m_tFastModeSettings = settings;
|
||||
return isFastModeSettingsSet();
|
||||
}
|
||||
|
||||
const char* CHeaderBrainProductsVAmp::getPairName(const size_t index) const
|
||||
{
|
||||
const auto i = m_names.find(index);
|
||||
if (i == m_names.end()) { return ""; }
|
||||
return i->second.c_str();
|
||||
}
|
||||
|
||||
float CHeaderBrainProductsVAmp::getPairGain(const size_t index) const
|
||||
{
|
||||
const auto i = m_gains.find(index);
|
||||
if (i == m_gains.end()) { return (index < m_nPair ? 1.0f : 0.0f); }
|
||||
return i->second;
|
||||
}
|
||||
|
||||
bool CHeaderBrainProductsVAmp::getPairUnits(const size_t index, size_t& unit, size_t& factor) const
|
||||
{
|
||||
const auto i = m_units.find(index);
|
||||
if (i == m_units.end())
|
||||
{
|
||||
unit = OVTK_UNIT_Unspecified;
|
||||
factor = OVTK_FACTOR_Base;
|
||||
|
||||
return false;
|
||||
}
|
||||
unit = (i->second).first;
|
||||
factor = (i->second).second;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CHeaderBrainProductsVAmp::setDeviceId(const int id)
|
||||
{
|
||||
m_deviceID = id;
|
||||
return m_deviceID != _NoValueI_;
|
||||
}
|
||||
|
||||
bool CHeaderBrainProductsVAmp::isDeviceIdSet() const { return m_deviceID != _NoValueI_; }
|
||||
|
||||
bool CHeaderBrainProductsVAmp::isFastModeSettingsSet() const
|
||||
{
|
||||
return (m_tFastModeSettings.Mode20kHz4Channels.ChannelsPos[0] == _NoValueI_
|
||||
|| m_tFastModeSettings.Mode20kHz4Channels.ChannelsNeg[0] == _NoValueI_
|
||||
|| m_tFastModeSettings.Mode20kHz4Channels.ChannelsPos[1] == _NoValueI_
|
||||
|| m_tFastModeSettings.Mode20kHz4Channels.ChannelsNeg[1] == _NoValueI_
|
||||
|| m_tFastModeSettings.Mode20kHz4Channels.ChannelsPos[2] == _NoValueI_
|
||||
|| m_tFastModeSettings.Mode20kHz4Channels.ChannelsNeg[2] == _NoValueI_
|
||||
|| m_tFastModeSettings.Mode20kHz4Channels.ChannelsPos[3] == _NoValueI_
|
||||
|| m_tFastModeSettings.Mode20kHz4Channels.ChannelsNeg[3] == _NoValueI_);
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
// Channel information
|
||||
|
||||
bool CHeaderBrainProductsVAmp::setChannelCount(const size_t nChannel)
|
||||
{
|
||||
if (m_acquisitionMode == VAmp4Fast)
|
||||
{
|
||||
// in fast mode the channel count is the pair count (to display in the designer as a "channel")
|
||||
return this->setPairCount(nChannel);
|
||||
}
|
||||
return m_basicHeader->setChannelCount(nChannel);
|
||||
}
|
||||
|
||||
bool CHeaderBrainProductsVAmp::setChannelName(const size_t index, const char* name)
|
||||
{
|
||||
if (m_acquisitionMode == VAmp4Fast)
|
||||
{
|
||||
// in fast mode the channel count is the pair count (to display in the designer as a "channel")
|
||||
return this->setPairName(index, name);
|
||||
}
|
||||
return m_basicHeader->setChannelName(index, name);
|
||||
}
|
||||
|
||||
bool CHeaderBrainProductsVAmp::setChannelGain(const size_t index, const float gain)
|
||||
{
|
||||
if (m_acquisitionMode == VAmp4Fast)
|
||||
{
|
||||
// in fast mode the channel count is the pair count (to display in the designer as a "channel")
|
||||
return this->setPairGain(index, gain);
|
||||
}
|
||||
return m_basicHeader->setChannelGain(index, gain);
|
||||
}
|
||||
|
||||
bool CHeaderBrainProductsVAmp::setChannelUnits(const size_t index, const size_t unit, const size_t factor)
|
||||
{
|
||||
if (m_acquisitionMode == VAmp4Fast)
|
||||
{
|
||||
// in fast mode the channel count is the pair count (to display in the designer as a "channel")
|
||||
return this->setPairUnits(index, unit, factor);
|
||||
}
|
||||
return m_basicHeader->setChannelUnits(index, unit, factor);
|
||||
}
|
||||
|
||||
size_t CHeaderBrainProductsVAmp::getChannelCount() const
|
||||
{
|
||||
if (m_acquisitionMode == VAmp4Fast)
|
||||
{
|
||||
// in fast mode the channel count is the pair count (to display in the designer as a "channel")
|
||||
return this->getPairCount();
|
||||
}
|
||||
return m_basicHeader->getChannelCount();
|
||||
}
|
||||
|
||||
const char* CHeaderBrainProductsVAmp::getChannelName(const size_t index) const
|
||||
{
|
||||
if (m_acquisitionMode == VAmp4Fast)
|
||||
{
|
||||
// in fast mode the channel count is the pair count (to display in the designer as a "channel")
|
||||
return this->getPairName(index);
|
||||
}
|
||||
return m_basicHeader->getChannelName(index);
|
||||
}
|
||||
|
||||
float CHeaderBrainProductsVAmp::getChannelGain(const size_t index) const
|
||||
{
|
||||
if (m_acquisitionMode == VAmp4Fast)
|
||||
{
|
||||
// in fast mode the channel count is the pair count (to display in the designer as a "channel")
|
||||
return this->getPairGain(index);
|
||||
}
|
||||
return m_basicHeader->getChannelGain(index);
|
||||
}
|
||||
|
||||
bool CHeaderBrainProductsVAmp::getChannelUnits(const size_t index, size_t& unit, size_t& factor) const
|
||||
{
|
||||
if (m_acquisitionMode == VAmp4Fast) { return this->getPairUnits(index, unit, factor); }
|
||||
return m_basicHeader->getChannelUnits(index, unit, factor);
|
||||
}
|
||||
|
||||
bool CHeaderBrainProductsVAmp::isChannelCountSet() const
|
||||
{
|
||||
if (m_acquisitionMode == VAmp4Fast)
|
||||
{
|
||||
// in fast mode the channel count is the pair count (to display in the designer as a "channel")
|
||||
return this->isPairCountSet();
|
||||
}
|
||||
return m_basicHeader->isChannelCountSet();
|
||||
}
|
||||
|
||||
bool CHeaderBrainProductsVAmp::isChannelNameSet() const
|
||||
{
|
||||
if (m_acquisitionMode == VAmp4Fast)
|
||||
{
|
||||
// in fast mode the channel count is the pair count (to display in the designer as a "channel")
|
||||
return this->isPairNameSet();
|
||||
}
|
||||
return m_basicHeader->isChannelNameSet();
|
||||
}
|
||||
|
||||
bool CHeaderBrainProductsVAmp::isChannelGainSet() const
|
||||
{
|
||||
if (m_acquisitionMode == VAmp4Fast)
|
||||
{
|
||||
// in fast mode the channel count is the pair count (to display in the designer as a "channel")
|
||||
return this->isPairGainSet();
|
||||
}
|
||||
return m_basicHeader->isChannelGainSet();
|
||||
}
|
||||
|
||||
bool CHeaderBrainProductsVAmp::isChannelUnitSet() const
|
||||
{
|
||||
if (m_acquisitionMode == VAmp4Fast)
|
||||
{
|
||||
// in fast mode the channel count is the pair count (to display in the designer as a "channel")
|
||||
return this->isPairUnitSet();
|
||||
}
|
||||
return m_basicHeader->isChannelUnitSet();
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_HAS_ThirdPartyUSBFirstAmpAPI
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyUSBFirstAmpAPI
|
||||
|
||||
#include "../ovasCHeader.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <FirstAmp.h>
|
||||
|
||||
#include<map>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
enum EAcquisitionModes { VAmp16 = 0, VAmp8 = 1, VAmp4Fast = 2 };
|
||||
|
||||
/**
|
||||
* \class CHeaderBrainProductsVAmp
|
||||
* \author Laurent Bonnet (INRIA)
|
||||
* \date 16 nov 2009
|
||||
* \erief The CHeaderBrainProductsVAmp is an Adaptator for the VAmp device.
|
||||
*
|
||||
* It contains basic functions using the basic header behaviour, and a set of specific functions to handle the Fast Mode data.
|
||||
*
|
||||
* \sa CDriverBrainProductsVAmp
|
||||
*/
|
||||
class CHeaderBrainProductsVAmp final : public IHeader
|
||||
{
|
||||
public:
|
||||
|
||||
CHeaderBrainProductsVAmp();
|
||||
~CHeaderBrainProductsVAmp() override { delete m_basicHeader; }
|
||||
void reset() override;
|
||||
|
||||
// Experiment information
|
||||
bool setExperimentID(const size_t experimentID) override { return m_basicHeader->setExperimentID(experimentID); }
|
||||
bool setSubjectAge(const size_t subjectAge) override { return m_basicHeader->setSubjectAge(subjectAge); }
|
||||
bool setSubjectGender(const size_t subjectGender) override { return m_basicHeader->setSubjectGender(subjectGender); }
|
||||
|
||||
size_t getExperimentID() const override { return m_basicHeader->getExperimentID(); }
|
||||
size_t getSubjectAge() const override { return m_basicHeader->getSubjectAge(); }
|
||||
size_t getSubjectGender() const override { return m_basicHeader->getSubjectGender(); }
|
||||
|
||||
bool isExperimentIDSet() const override { return m_basicHeader->isExperimentIDSet(); }
|
||||
bool isSubjectAgeSet() const override { return m_basicHeader->isSubjectAgeSet(); }
|
||||
bool isSubjectGenderSet() const override { return m_basicHeader->isSubjectGenderSet(); }
|
||||
|
||||
void setImpedanceCheckRequested(const bool state) override { m_basicHeader->setImpedanceCheckRequested(state); }
|
||||
bool isImpedanceCheckRequested() const override { return m_basicHeader->isImpedanceCheckRequested(); }
|
||||
size_t getImpedanceLimit() const override { return m_impedanceLimit; }
|
||||
|
||||
void setImpedanceLimit(const size_t limit) override { m_impedanceLimit = limit; }
|
||||
|
||||
// Channel information
|
||||
bool setChannelCount(const size_t nChannel) override;
|
||||
bool setChannelName(const size_t index, const char* name) override;
|
||||
bool setChannelGain(const size_t index, const float gain) override;
|
||||
bool setChannelUnits(const size_t index, const size_t unit, const size_t factor) override;
|
||||
|
||||
size_t getChannelCount() const override;
|
||||
const char* getChannelName(const size_t index) const override;
|
||||
float getChannelGain(const size_t index) const override;
|
||||
bool getChannelUnits(const size_t index, size_t& unit, size_t& factor) const override;
|
||||
|
||||
bool isChannelCountSet() const override;
|
||||
bool isChannelNameSet() const override;
|
||||
bool isChannelGainSet() const override;
|
||||
bool isChannelUnitSet() const override;
|
||||
|
||||
// Samples information
|
||||
bool setSamplingFrequency(const size_t sampling) override { return m_basicHeader->setSamplingFrequency(sampling); }
|
||||
size_t getSamplingFrequency() const override { return m_basicHeader->getSamplingFrequency(); }
|
||||
bool isSamplingFrequencySet() const override { return m_basicHeader->isSamplingFrequencySet(); }
|
||||
|
||||
//------------- SPECIFIC FUNCTIONS -------------
|
||||
|
||||
EAcquisitionModes getAcquisitionMode() const { return m_acquisitionMode; }
|
||||
void setAcquisitionMode(const EAcquisitionModes mode) { m_acquisitionMode = mode; }
|
||||
|
||||
static size_t getEEGChannelCount(size_t acquisitionMode);
|
||||
static size_t getAuxiliaryChannelCount(size_t acquisitionMode);
|
||||
static size_t getTriggerChannelCount(size_t acquisitionMode);
|
||||
|
||||
// Pair information
|
||||
bool setPairCount(const size_t count);
|
||||
bool setPairName(const size_t index, const char* name);
|
||||
bool setPairGain(const size_t index, const float gain);
|
||||
bool setPairUnits(const size_t index, const size_t unit, const size_t factor);
|
||||
bool setDeviceId(int id);
|
||||
bool setFastModeSettings(t_faDataModeSettings settings);
|
||||
|
||||
size_t getPairCount() const { return m_nPair; }
|
||||
const char* getPairName(const size_t index) const;
|
||||
float getPairGain(const size_t index) const;
|
||||
bool getPairUnits(const size_t index, size_t& unit, size_t& factor) const;
|
||||
int getDeviceId() const { return m_deviceID; }
|
||||
t_faDataModeSettings getFastModeSettings() const { return m_tFastModeSettings; }
|
||||
|
||||
bool isPairCountSet() const { return m_nPair != size_t(-1) && m_nPair != 0; }
|
||||
bool isPairNameSet() const { return isPairCountSet(); }
|
||||
bool isPairGainSet() const { return isPairCountSet(); }
|
||||
bool isPairUnitSet() const { return isPairCountSet(); }
|
||||
bool isDeviceIdSet() const;
|
||||
bool isFastModeSettingsSet() const;
|
||||
|
||||
CHeader* getBasicHeader() const { return m_basicHeader; }
|
||||
|
||||
protected:
|
||||
|
||||
CHeader* m_basicHeader = nullptr; // the basic header
|
||||
|
||||
// additional information
|
||||
int m_deviceID = 0;
|
||||
EAcquisitionModes m_acquisitionMode = VAmp16;
|
||||
t_faDataModeSettings m_tFastModeSettings;
|
||||
|
||||
// Pair information
|
||||
size_t m_nPair = 0;
|
||||
std::map<size_t, std::string> m_names;
|
||||
std::map<size_t, float> m_gains;
|
||||
std::map<size_t, std::pair<size_t, size_t>> m_units;
|
||||
|
||||
size_t m_impedanceLimit = 5000; // 5kOhm
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyGUSBampCAPI
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
#include "ovasCConfigurationEGIAmpServer.h"
|
||||
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
bool CConfigurationEGIAmpServer::preConfigure()
|
||||
{
|
||||
const bool res = CConfigurationBuilder::preConfigure();
|
||||
|
||||
m_pHostName = GTK_WIDGET(gtk_builder_get_object(m_builder, "entry_host_name"));
|
||||
m_pCommandPort = GTK_WIDGET(gtk_builder_get_object(m_builder, "spinbutton_command_port"));
|
||||
m_pStreamPort = GTK_WIDGET(gtk_builder_get_object(m_builder, "spinbutton_stream_port"));
|
||||
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_pCommandPort), m_commandPort);
|
||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_pStreamPort), m_streamPort);
|
||||
|
||||
gtk_entry_set_text(GTK_ENTRY(m_pHostName), m_hostName.toASCIIString());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool CConfigurationEGIAmpServer::postConfigure()
|
||||
{
|
||||
if (m_applyConfig)
|
||||
{
|
||||
gtk_spin_button_update(GTK_SPIN_BUTTON(m_pStreamPort));
|
||||
gtk_spin_button_update(GTK_SPIN_BUTTON(m_pCommandPort));
|
||||
|
||||
m_streamPort = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(m_pStreamPort));
|
||||
m_commandPort = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(m_pCommandPort));
|
||||
m_hostName = gtk_entry_get_text(GTK_ENTRY(m_pHostName));
|
||||
}
|
||||
|
||||
return CConfigurationBuilder::postConfigure();
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovasCConfigurationBuilder.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
class CConfigurationEGIAmpServer final : public CConfigurationBuilder
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CConfigurationEGIAmpServer(const char* gtkBuilderFilename) : CConfigurationBuilder(gtkBuilderFilename) { }
|
||||
~CConfigurationEGIAmpServer() override { }
|
||||
|
||||
void setHostName(const CString& hostName) { m_hostName = hostName; }
|
||||
void setCommandPort(const uint32_t commandPort) { m_commandPort = commandPort; }
|
||||
void setStreamPort(const uint32_t streamPort) { m_streamPort = streamPort; }
|
||||
|
||||
CString getHostName() const { return m_hostName; }
|
||||
uint32_t getCommandPort() const { return m_commandPort; }
|
||||
uint32_t getStreamPort() const { return m_streamPort; }
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
bool preConfigure() override;
|
||||
bool postConfigure() override;
|
||||
|
||||
private:
|
||||
|
||||
CConfigurationEGIAmpServer() = delete;
|
||||
|
||||
protected:
|
||||
|
||||
GtkWidget* m_pHostName = nullptr;
|
||||
GtkWidget* m_pCommandPort = nullptr;
|
||||
GtkWidget* m_pStreamPort = nullptr;
|
||||
CString m_hostName = "localhost";
|
||||
uint32_t m_commandPort = 9877;
|
||||
uint32_t m_streamPort = 9879;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
#include "ovasCDriverEGIAmpServer.h"
|
||||
#include "ovasCConfigurationEGIAmpServer.h"
|
||||
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <system/ovCMemory.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
CDriverEGIAmpServer::CDriverEGIAmpServer(IDriverContext& ctx)
|
||||
: IDriver(ctx), m_settings("AcquisitionServer_Driver_EGIAmpServer", m_driverCtx.getConfigurationManager())
|
||||
{
|
||||
m_serverHostName = "localhost";
|
||||
m_commandPort = 9877;
|
||||
m_streamPort = 9879;
|
||||
|
||||
m_header.setSamplingFrequency(1000);
|
||||
m_header.setChannelCount(280);
|
||||
|
||||
m_settings.add("Header", &m_header);
|
||||
m_settings.add("AmpServerHostName", &m_serverHostName);
|
||||
m_settings.add("CommandPort", &m_commandPort);
|
||||
m_settings.add("StreamPort", &m_streamPort);
|
||||
m_settings.load();
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
#define COMMAND_SIZE 4096
|
||||
|
||||
class CCommandConnectionHandler
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CCommandConnectionHandler(CDriverEGIAmpServer& driver, Socket::IConnection* connection = nullptr)
|
||||
: m_Driver(driver), m_Connection(connection ? *connection : *driver.m_command), m_ListenToResponse(connection == nullptr) { }
|
||||
|
||||
~CCommandConnectionHandler() { }
|
||||
|
||||
bool send(const char* cmd) const
|
||||
{
|
||||
m_Driver.m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Sending command >> [" << CString(cmd) << "]\n";
|
||||
m_Connection.sendBufferBlocking(cmd, strlen(cmd));
|
||||
m_Connection.sendBufferBlocking("\n", 1);
|
||||
if (m_ListenToResponse)
|
||||
{
|
||||
size_t i, l = 0;
|
||||
char buffer[COMMAND_SIZE];
|
||||
do
|
||||
{
|
||||
i = m_Connection.receiveBuffer(const_cast<char*>(buffer + l), 1);
|
||||
if (i > 0) { l += i; }
|
||||
} while (i == 0 || (i > 0 && buffer[l - 1] != '\0' && buffer[l - 1] != '\n'));
|
||||
buffer[l > 1 ? l - 1 : l] = '\0';
|
||||
m_Driver.m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Received answer << [" << CString(buffer) << "]\n";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
CDriverEGIAmpServer& m_Driver;
|
||||
Socket::IConnection& m_Connection;
|
||||
bool m_ListenToResponse;
|
||||
};
|
||||
|
||||
#pragma pack(1)
|
||||
typedef struct
|
||||
{
|
||||
int64_t amplifierID;
|
||||
uint64_t size;
|
||||
} SAmpServerPacketHeader;
|
||||
#pragma pack()
|
||||
|
||||
// cmdHandler.send("(sendCommand cmd_NumberOfAmps -1 -1 -1)");
|
||||
// cmdHandler.send("(sendCommand cmd_SetPower 0 -1 1)");
|
||||
// cmdHandler.send("(sendCommand cmd_GetAmpDetails 0 -1 -1)");
|
||||
// cmdHandler.send("(sendCommand cmd_GetAmpStatus 0 -1 -1)");
|
||||
// cmdHandler.send("(sendCommand cmd_DefaultAcquisitionState 0 -1 -1)");
|
||||
// cmdHandler.send("(sendCommand cmd_DefaultSignalGeneration 0 -1 -1)");
|
||||
// cmdHandler.send("(sendCommand cmd_SetWaveShape 0 -1 1)");
|
||||
// cmdHandler.send("(sendCommand cmd_SetCalibrationSignalRange 0 -1 -1)");
|
||||
// cmdHandler.send("(sendCommand cmd_SetCalibrationSignalAmplitude 0 -1 -1)");
|
||||
// cmdHandler.send("(sendCommand cmd_SetCalibrationSignalFreq 0 -1 10)");
|
||||
// cmdHandler.send("(sendCommand cmd_TurnAllDriveSignals 0 -1 1)");
|
||||
// streamHandler.send("(sendCommand cmd_GetStatus -1 -1 -1)");
|
||||
// streamHandler.send("(sendCommand cmd_InstallEGINA300TestAmp -1 -1 -1)");
|
||||
|
||||
// cmdHandler.send("(sendCommand cmd_SetPower 0 -1 0)");
|
||||
// cmdHandler.send("(sendCommand cmd_Exit -1 -1 -1)");
|
||||
|
||||
bool CDriverEGIAmpServer::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
|
||||
{
|
||||
if (m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
m_command = Socket::createConnectionClient();
|
||||
if (!m_command->connect(m_serverHostName.toASCIIString(), m_commandPort))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not connect to AmpServer command port [" << m_serverHostName << ":" << m_commandPort << "]\n";
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Please check the driver configuration and the firewall configuration\n";
|
||||
m_command->close();
|
||||
m_command->release();
|
||||
m_command = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_stream = Socket::createConnectionClient();
|
||||
if (!m_stream->connect(m_serverHostName.toASCIIString(), m_streamPort))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not connect to AmpServer stream port [" << m_serverHostName << ":" << m_streamPort << "]\n";
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Please check the driver configuration and the firewall configuration\n";
|
||||
m_stream->close();
|
||||
m_stream->release();
|
||||
m_stream = nullptr;
|
||||
m_command->close();
|
||||
m_command->release();
|
||||
m_command = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_buffer = new float[m_header.getChannelCount() * nSamplePerSentBlock];
|
||||
if (!m_buffer)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Memory allocation error.\n";
|
||||
m_stream->close();
|
||||
m_stream->release();
|
||||
m_stream = nullptr;
|
||||
m_command->close();
|
||||
m_command->release();
|
||||
m_command = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
this->execute(Directories::getDataDir() + "/applications/acquisition-server/scripts/egi-default-initialize.script");
|
||||
|
||||
const CCommandConnectionHandler cmdHandler(*this);
|
||||
cmdHandler.send("(sendCommand cmd_Start 0 -1 -1)");
|
||||
|
||||
const CCommandConnectionHandler streamHandler(*this, m_stream);
|
||||
streamHandler.send("(sendCommand cmd_ListenToAmp 0 -1 -1)");
|
||||
|
||||
m_callback = &callback;
|
||||
m_nSamplePerSentBlock = nSamplePerSentBlock;
|
||||
m_sampleIdx = 0;
|
||||
m_nChannel = m_header.getChannelCount();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverEGIAmpServer::start()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
this->execute(Directories::getDataDir() + "/applications/acquisition-server/scripts/egi-default-start.script");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverEGIAmpServer::loop()
|
||||
{
|
||||
bool completedBuffer = false;
|
||||
|
||||
if (!m_driverCtx.isConnected()) { return false; }
|
||||
// if(!m_driverCtx.isStarted()) { return true; }
|
||||
|
||||
SAmpServerPacketHeader header;
|
||||
SAmpServerPacketHeader headerSwap;
|
||||
|
||||
while (m_stream->isReadyToReceive() && !completedBuffer)
|
||||
{
|
||||
m_stream->receiveBufferBlocking(reinterpret_cast<char*>(&headerSwap), sizeof(headerSwap));
|
||||
System::Memory::bigEndianToHost(reinterpret_cast<const uint8_t*>(&headerSwap.amplifierID), &header.amplifierID);
|
||||
System::Memory::bigEndianToHost(reinterpret_cast<const uint8_t*>(&headerSwap.size), &header.size);
|
||||
|
||||
if (header.size)
|
||||
{
|
||||
float* bufferSwap = new float[size_t(header.size / sizeof(float))];
|
||||
m_stream->receiveBufferBlocking(reinterpret_cast<char*>(bufferSwap), uint32_t(header.size));
|
||||
|
||||
if (m_driverCtx.isStarted())
|
||||
{
|
||||
const uint32_t nSample = uint32_t(header.size / 1152);
|
||||
for (uint32_t i = 0; i < nSample; ++i)
|
||||
{
|
||||
for (uint32_t j = 0; j < m_nChannel; ++j)
|
||||
{
|
||||
System::Memory::bigEndianToHost(reinterpret_cast<const uint8_t*>(bufferSwap + 8 + i * 288 + j),
|
||||
m_buffer + j * m_nSamplePerSentBlock + m_sampleIdx);
|
||||
}
|
||||
|
||||
m_sampleIdx++;
|
||||
if (m_sampleIdx == m_nSamplePerSentBlock)
|
||||
{
|
||||
completedBuffer = true;
|
||||
m_sampleIdx = 0;
|
||||
m_callback->setSamples(m_buffer);
|
||||
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete [] bufferSwap;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverEGIAmpServer::stop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
this->execute(Directories::getDataDir() + "/applications/acquisition-server/scripts/egi-default-stop.script");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverEGIAmpServer::uninitialize()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
if (m_buffer)
|
||||
{
|
||||
delete [] m_buffer;
|
||||
m_buffer = nullptr;
|
||||
}
|
||||
|
||||
if (m_stream)
|
||||
{
|
||||
const CCommandConnectionHandler handler(*this, m_stream);
|
||||
handler.send("(sendCommand cmd_StopListeningToAmp 0 -1 -1)");
|
||||
|
||||
m_stream->close();
|
||||
m_stream->release();
|
||||
m_stream = nullptr;
|
||||
}
|
||||
|
||||
if (m_command)
|
||||
{
|
||||
const CCommandConnectionHandler handler(*this);
|
||||
handler.send("(sendCommand cmd_Stop 0 -1 -1)");
|
||||
|
||||
this->execute(Directories::getDataDir() + "/applications/acquisition-server/scripts/egi-default-uninitialize.script");
|
||||
|
||||
m_command->close();
|
||||
m_command->release();
|
||||
m_command = nullptr;
|
||||
}
|
||||
|
||||
m_callback = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverEGIAmpServer::configure()
|
||||
{
|
||||
CConfigurationEGIAmpServer config(Directories::getDataDir() + "/applications/acquisition-server/interface-egi-ampserver.ui");
|
||||
|
||||
config.setHostName(m_serverHostName);
|
||||
config.setCommandPort(m_commandPort);
|
||||
config.setStreamPort(m_streamPort);
|
||||
|
||||
if (config.configure(m_header))
|
||||
{
|
||||
m_serverHostName = config.getHostName();
|
||||
m_commandPort = config.getCommandPort();
|
||||
m_streamPort = config.getStreamPort();
|
||||
m_settings.save();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverEGIAmpServer::execute(const char* sScriptFilename)
|
||||
{
|
||||
FILE* file = fopen(sScriptFilename, "rb");
|
||||
if (!file)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Could not find script [" << CString(sScriptFilename) << "]\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
const CCommandConnectionHandler cmdHandler(*this);
|
||||
while (!feof(file))
|
||||
{
|
||||
char buffer[4096];
|
||||
char* line = fgets(buffer, sizeof(buffer), file);
|
||||
if (line)
|
||||
{
|
||||
line[strlen(line) - 1] = '\0'; // replaces \n with \0 (\n is automatically added by the command handler)
|
||||
cmdHandler.send(line);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "ovasIDriver.h"
|
||||
#include "../ovasCHeader.h"
|
||||
|
||||
#include "../ovasCSettingsHelper.h"
|
||||
#include "../ovasCSettingsHelperOperators.h"
|
||||
|
||||
#include <socket/IConnectionClient.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
class CCommandConnectionHandler;
|
||||
|
||||
/**
|
||||
* \class CDriverEGIAmpServer
|
||||
* \author Yann Renard (Inria)
|
||||
*/
|
||||
class CDriverEGIAmpServer final : public IDriver
|
||||
{
|
||||
public:
|
||||
|
||||
friend class CCommandConnectionHandler;
|
||||
|
||||
explicit CDriverEGIAmpServer(IDriverContext& ctx);
|
||||
void release() { delete this; }
|
||||
const char* getName() override { return "EGI Net Amps 300 (through AmpServer)"; }
|
||||
|
||||
bool isFlagSet(const EDriverFlag flag) const override { return flag == EDriverFlag::IsUnstable; }
|
||||
|
||||
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
|
||||
bool uninitialize() override;
|
||||
|
||||
bool start() override;
|
||||
bool stop() override;
|
||||
bool loop() override;
|
||||
|
||||
bool isConfigurable() override { return true; }
|
||||
bool configure() override;
|
||||
const IHeader* getHeader() override { return &m_header; }
|
||||
|
||||
protected:
|
||||
|
||||
bool execute(const char* sScriptFilename);
|
||||
|
||||
SettingsHelper m_settings;
|
||||
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
CHeader m_header;
|
||||
|
||||
Socket::IConnectionClient* m_command = nullptr;
|
||||
Socket::IConnectionClient* m_stream = nullptr;
|
||||
uint32_t m_nSamplePerSentBlock = 0;
|
||||
uint32_t m_sampleIdx = 0;
|
||||
uint32_t m_nChannel = 0;
|
||||
float* m_buffer = nullptr;
|
||||
|
||||
CString m_serverHostName = "localhost";
|
||||
uint32_t m_commandPort = 9877;
|
||||
uint32_t m_streamPort = 9879;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
#if defined TARGET_HAS_ThirdPartyEmotivAPI
|
||||
|
||||
#include "ovasCConfigurationEmotivEPOC.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
CConfigurationEmotivEPOC::CConfigurationEmotivEPOC(IDriverContext& ctx, const char* gtkBuilderFilename, bool& rUseGyroscope, CString& rPathToEmotivResearchSDK, uint32_t& rUserID)
|
||||
: CConfigurationBuilder(gtkBuilderFilename), m_driverCtx(ctx), m_useGyroscope(rUseGyroscope), m_rPathToEmotivResearchSDK(rPathToEmotivResearchSDK), m_userID(rUserID) {}
|
||||
|
||||
bool CConfigurationEmotivEPOC::preConfigure()
|
||||
{
|
||||
if (! CConfigurationBuilder::preConfigure()) { return false; }
|
||||
|
||||
GtkToggleButton* gyro = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_gyro"));
|
||||
gtk_toggle_button_set_active(gyro, m_useGyroscope);
|
||||
|
||||
GtkFileChooser* fileChooser = GTK_FILE_CHOOSER(gtk_builder_get_object(m_builder, "filechooserbutton"));
|
||||
gtk_file_chooser_set_current_folder(fileChooser, m_rPathToEmotivResearchSDK.toASCIIString());
|
||||
|
||||
GtkSpinButton* userID = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_userid"));
|
||||
gtk_spin_button_set_value(userID, m_userID);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CConfigurationEmotivEPOC::postConfigure()
|
||||
{
|
||||
if (m_applyConfig)
|
||||
{
|
||||
GtkToggleButton* gyro = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_gyro"));
|
||||
m_useGyroscope = gtk_toggle_button_get_active(gyro) ? true : false;
|
||||
|
||||
GtkSpinButton* userID = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_userid"));
|
||||
gtk_spin_button_update(userID);
|
||||
m_userID = uint32_t(gtk_spin_button_get_value(userID));
|
||||
|
||||
GtkFileChooser* fileChooser = GTK_FILE_CHOOSER(gtk_builder_get_object(m_builder, "filechooserbutton"));
|
||||
gchar* dir = gtk_file_chooser_get_filename(fileChooser);
|
||||
std::string tmpDstDir(dir);
|
||||
for (auto it = tmpDstDir.begin(); it < tmpDstDir.end(); ++it) { if ((*it) == '\\') { tmpDstDir.replace(it, it + 1, 1, '/'); } }
|
||||
tmpDstDir.push_back('/');
|
||||
m_rPathToEmotivResearchSDK = tmpDstDir.c_str();
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Path to Emotiv Research SDK is set to [" << m_rPathToEmotivResearchSDK.toASCIIString() << "]\n";
|
||||
}
|
||||
|
||||
if (! CConfigurationBuilder::postConfigure()) { return false; } // normal header is filled, ressources are realesed
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_HAS_ThirdPartyEmotivAPI
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyEmotivAPI
|
||||
|
||||
#include "../ovasCConfigurationBuilder.h"
|
||||
#include "ovasIDriver.h"
|
||||
#include "../ovasCHeader.h"
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#if defined TARGET_OS_Windows
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CConfigurationEmotivEPOC
|
||||
* \author Laurent Bonnet (INRIA)
|
||||
* \date 21 july 2010
|
||||
* \erief The CConfigurationEmotivEPOC handles the configuration dialog specific to the Emotiv EPOC headset.
|
||||
*
|
||||
* \sa CDriverEmotivEPOC
|
||||
*/
|
||||
class CConfigurationEmotivEPOC final : public CConfigurationBuilder
|
||||
{
|
||||
public:
|
||||
|
||||
CConfigurationEmotivEPOC(IDriverContext& ctx, const char* gtkBuilderFilename, bool& rUseGyroscope, CString& rPathToEmotivResearchSDK, uint32_t& rUserID);
|
||||
|
||||
bool preConfigure() override;
|
||||
bool postConfigure() override;
|
||||
|
||||
protected:
|
||||
|
||||
IDriverContext& m_driverCtx;
|
||||
bool& m_useGyroscope;
|
||||
CString& m_rPathToEmotivResearchSDK;
|
||||
uint32_t& m_userID;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyEmotivAPI
|
||||
+422
@@ -0,0 +1,422 @@
|
||||
#if defined TARGET_HAS_ThirdPartyEmotivAPI
|
||||
|
||||
#include "ovasCDriverEmotivEPOC.h"
|
||||
#include "ovasCConfigurationEmotivEPOC.h"
|
||||
|
||||
#include <system/ovCTime.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#if defined(TARGET_OS_Windows)
|
||||
#include <delayimp.h>
|
||||
#endif
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
|
||||
#if defined(TARGET_HAS_ThirdPartyEmotivResearchAPI3x)
|
||||
static const IEE_DataChannel_t CHANNEL_LIST[] =
|
||||
{
|
||||
IED_AF3, IED_F7, IED_F3, IED_FC5, IED_T7, IED_P7, IED_O1, IED_O2, IED_P8, IED_T8, IED_FC6, IED_F4, IED_F8, IED_AF4,
|
||||
IED_GYROX, IED_GYROY,
|
||||
IED_COUNTER,
|
||||
IED_TIMESTAMP,
|
||||
IED_FUNC_ID, IED_FUNC_VALUE,
|
||||
IED_MARKER,
|
||||
IED_SYNC_SIGNAL
|
||||
};
|
||||
#else
|
||||
// Old API
|
||||
static const EE_DataChannel_t CHANNEL_LIST[] =
|
||||
{
|
||||
ED_AF3, ED_F7, ED_F3, ED_FC5, ED_T7, ED_P7, ED_O1, ED_O2, ED_P8, ED_T8, ED_FC6, ED_F4, ED_F8, ED_AF4,
|
||||
ED_GYROX, ED_GYROY,
|
||||
ED_COUNTER,
|
||||
ED_TIMESTAMP,
|
||||
ED_FUNC_ID, ED_FUNC_VALUE,
|
||||
ED_MARKER,
|
||||
ED_SYNC_SIGNAL
|
||||
};
|
||||
#endif
|
||||
|
||||
CDriverEmotivEPOC::CDriverEmotivEPOC(IDriverContext& ctx)
|
||||
: IDriver(ctx), m_settings("AcquisitionServer_Driver_EmotivEPOC", m_driverCtx.getConfigurationManager())
|
||||
{
|
||||
m_useGyroscope = false;
|
||||
m_pathToEmotivSDK = "";
|
||||
m_cmdForPathModification = "";
|
||||
|
||||
m_userID = 0;
|
||||
m_readyToCollect = false;
|
||||
m_firstStart = true;
|
||||
|
||||
m_settings.add("Header", &m_header);
|
||||
m_settings.add("UseGyroscope", &m_useGyroscope);
|
||||
m_settings.add("PathToEmotivSDK", &m_pathToEmotivSDK);
|
||||
m_settings.add("UserID", &m_userID);
|
||||
m_settings.load();
|
||||
}
|
||||
|
||||
CDriverEmotivEPOC::~CDriverEmotivEPOC() {}
|
||||
|
||||
const char* CDriverEmotivEPOC::getName() { return "Emotiv EPOC"; }
|
||||
|
||||
bool CDriverEmotivEPOC::restoreState()
|
||||
{
|
||||
// Restore the previous path
|
||||
if (m_oldPath.length() > 0) { _putenv_s("PATH", m_oldPath); }
|
||||
|
||||
#if defined TARGET_OS_Windows
|
||||
__FUnloadDelayLoadedDLL2("edk.dll");
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverEmotivEPOC::buildPath()
|
||||
{
|
||||
char* path = getenv("PATH");
|
||||
if (path == nullptr) { return false; }
|
||||
|
||||
m_oldPath = path;
|
||||
|
||||
const std::string str = std::string(path);
|
||||
const size_t found = str.find(m_pathToEmotivSDK.toASCIIString());
|
||||
|
||||
if (found == std::string::npos)
|
||||
{
|
||||
// If the emotiv component is not part of the path, we add it.
|
||||
m_cmdForPathModification = path + CString(";") + m_pathToEmotivSDK;
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "[INIT] Emotiv Driver: Building new Windows PATH.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we found the emotiv component in path already, keep the path as-is.
|
||||
m_cmdForPathModification = CString(path);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "[INIT] Emotiv Driver: Using the existing PATH.\n";
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
bool CDriverEmotivEPOC::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
|
||||
{
|
||||
m_eventHandle = nullptr;
|
||||
|
||||
//we need to add the path to Emotiv SDK to PATH: done in external function
|
||||
//because SEH (__try/__except) does not allow the use of local variables with destructor.
|
||||
if (!this->buildPath())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[INIT] Emotiv Driver: Failed to get the ENV variable PATH.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined TARGET_OS_Windows
|
||||
// m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "[INIT] Emotiv Driver: Setting PATH as " << m_cmdForPathModification << "\n";
|
||||
|
||||
if (_putenv_s("PATH", m_cmdForPathModification) != 0)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[INIT] Emotiv Driver: Failed to modify the environment PATH with the Emotiv SDK path.\n";
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
m_header.setChannelCount(14);
|
||||
if (m_useGyroscope)
|
||||
{
|
||||
m_header.setChannelCount(16); // 14 + 2 REF + 2 Gyro
|
||||
}
|
||||
|
||||
m_header.setChannelName(0, "AF3");
|
||||
m_header.setChannelName(1, "F7");
|
||||
m_header.setChannelName(2, "F3");
|
||||
m_header.setChannelName(3, "FC5");
|
||||
m_header.setChannelName(4, "T7");
|
||||
m_header.setChannelName(5, "P7");
|
||||
m_header.setChannelName(6, "O1");
|
||||
m_header.setChannelName(7, "O2");
|
||||
m_header.setChannelName(8, "P8");
|
||||
m_header.setChannelName(9, "T8");
|
||||
m_header.setChannelName(10, "FC6");
|
||||
m_header.setChannelName(11, "F4");
|
||||
m_header.setChannelName(12, "F8");
|
||||
m_header.setChannelName(13, "AF4");
|
||||
|
||||
if (m_useGyroscope)
|
||||
{
|
||||
m_header.setChannelName(14, "Gyro-X");
|
||||
m_header.setChannelName(15, "Gyro-Y");
|
||||
}
|
||||
|
||||
m_header.setSamplingFrequency(128); // let's hope so...
|
||||
|
||||
// Set channel units
|
||||
// Various sources (e.g. http://emotiv.com/forum/forum15/topic879/messages/?PAGEN_1=3
|
||||
// and http://www.bci2000.org/wiki/index.php/Contributions:Emotiv ) suggested
|
||||
// that the units from the device are in microvolts, but with a typical DC offset around 4000.
|
||||
// Hard to find official source.
|
||||
for (uint32_t c = 0; c < 14; ++c) { m_header.setChannelUnits(c, OVTK_UNIT_Volts, OVTK_FACTOR_Micro); }
|
||||
if (m_useGyroscope)
|
||||
{
|
||||
// Even less sure about the units of these, leaving as unspecified.
|
||||
m_header.setChannelUnits(14, OVTK_UNIT_Unspecified, OVTK_FACTOR_Base);
|
||||
m_header.setChannelUnits(15, OVTK_UNIT_Unspecified, OVTK_FACTOR_Base);
|
||||
}
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "INIT called.\n";
|
||||
if (m_driverCtx.isConnected())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[INIT] Emotiv Driver: Driver already initialized.\n";
|
||||
restoreState();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_header.isChannelCountSet() || !m_header.isSamplingFrequencySet())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[INIT] Emotiv Driver: Channel count or frequency not set.\n";
|
||||
restoreState();
|
||||
return false;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// Builds up a buffer to store acquired samples. This buffer will be sent to the acquisition server later.
|
||||
|
||||
m_sample = new float[m_header.getChannelCount()];
|
||||
//m_Buffer=new double[m_header.getChannelCount()*nSamplePerSentBlock];
|
||||
if (!m_sample /*|| !m_Buffer*/)
|
||||
{
|
||||
delete [] m_sample;
|
||||
//delete [] m_Buffer;
|
||||
m_sample = nullptr;
|
||||
//m_Buffer= nullptr;
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[INIT] Emotiv Driver: Samples allocation failed.\n";
|
||||
restoreState();
|
||||
return false;
|
||||
}
|
||||
|
||||
//__________________________________
|
||||
// Hardware initialization
|
||||
|
||||
m_readyToCollect = false;
|
||||
#if defined TARGET_OS_Windows
|
||||
// First call to a function from EDK.DLL: guard the call with __try/__except clauses.
|
||||
__try
|
||||
#elif defined TARGET_OS_Linux
|
||||
try
|
||||
#endif
|
||||
{
|
||||
m_eventHandle = IEE_EmoEngineEventCreate();
|
||||
m_lastErrorCode = IEE_EngineConnect();
|
||||
}
|
||||
#if defined TARGET_OS_Windows
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
#elif defined TARGET_OS_Linux
|
||||
catch(...)
|
||||
#endif
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[INIT] Emotiv Driver: First call to 'edk.dll' failed.\n"
|
||||
<< "\tThis driver needs Emotiv SDK Research Edition (or better)\n"
|
||||
<< "\tinstalled on your computer.\n"
|
||||
<< "\tYou have configured the driver to use path [" << m_pathToEmotivSDK << "].\n"
|
||||
<< "\tIf there is an 'edk.dll' there, its version may be incompatible.\n"
|
||||
<< "\tThis driver was built against 32-bit (x86) Emotiv SDK v "
|
||||
#if defined(TARGET_HAS_ThirdPartyEmotivResearchAPI3x)
|
||||
<< "3.x.x.\n";
|
||||
#else
|
||||
<< "1.x.x.\n";
|
||||
#endif
|
||||
|
||||
restoreState();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_lastErrorCode != EDK_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[INIT] Emotiv Driver: Can't connect to EmoEngine. EDK Error Code [" << m_lastErrorCode << "]\n";
|
||||
restoreState();
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "[INIT] Emotiv Driver: Connection to EmoEngine successful.\n";
|
||||
|
||||
#if defined(TARGET_HAS_ThirdPartyEmotivResearchAPI)
|
||||
unsigned long hwVersion = 0;
|
||||
unsigned long buildNum = 0;
|
||||
char version[16];
|
||||
|
||||
IEE_HardwareGetVersion(m_userID, &hwVersion);
|
||||
IEE_SoftwareGetVersion(version, 16, &buildNum);
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Emotiv Driver: "
|
||||
<< "headset version " << uint64_t(hwVersion && 0xFFFF) << " / " << uint64_t(hwVersion >> 16)
|
||||
<< ", software " << version << ", build " << uint64_t(buildNum) << "\n";
|
||||
#endif
|
||||
|
||||
//__________________________________
|
||||
// Saves parameters
|
||||
|
||||
m_callback = &callback;
|
||||
m_nSamplePerSentBlock = nSamplePerSentBlock;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverEmotivEPOC::start()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "START called.\n";
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
m_dataHandle = IEE_DataCreate();
|
||||
//float sec = (float)m_nSamplePerSentBlock/(float)m_header.getSamplingFrequency();
|
||||
float sec = 1;
|
||||
m_lastErrorCode = IEE_DataSetBufferSizeInSec(sec);
|
||||
if (m_lastErrorCode != EDK_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[START] Emotiv Driver: Set buffer size to [" << sec << "] sec failed. EDK Error Code [" << m_lastErrorCode << "]\n";
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "[START] Emotiv Driver: Data Handle created. Buffer size set to [" << sec << "] sec.\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverEmotivEPOC::loop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
if (m_driverCtx.isStarted())
|
||||
{
|
||||
// we enable the acquisiton for every new headset (user) ever added
|
||||
if (IEE_EngineGetNextEvent(m_eventHandle) == EDK_OK)
|
||||
{
|
||||
IEE_Event_t type = IEE_EmoEngineEventGetType(m_eventHandle);
|
||||
|
||||
|
||||
if (type == IEE_UserAdded)
|
||||
{
|
||||
uint32_t id;
|
||||
IEE_EmoEngineEventGetUserId(m_eventHandle, &id);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "[LOOP] Emotiv Driver: User #" << id << " registered.\n";
|
||||
m_lastErrorCode = IEE_DataAcquisitionEnable(id, true);
|
||||
if (m_lastErrorCode != EDK_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[LOOP] Emotiv Driver: Enabling acquisition failed. EDK Error Code [" << m_lastErrorCode << "]\n";
|
||||
return false;
|
||||
}
|
||||
// but we are ready to acquire the samples only if the requested headset is detected
|
||||
m_readyToCollect = m_readyToCollect || (m_userID == id);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_readyToCollect)
|
||||
{
|
||||
uint32_t nSamplesTaken = 0;
|
||||
m_lastErrorCode = IEE_DataUpdateHandle(m_userID, m_dataHandle);
|
||||
if (m_lastErrorCode != EDK_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[LOOP] Emotiv Driver: An error occurred while updating the DataHandle. EDK Error Code [" << m_lastErrorCode << "]\n";
|
||||
return false;
|
||||
}
|
||||
m_lastErrorCode = IEE_DataGetNumberOfSample(m_dataHandle, &nSamplesTaken);
|
||||
if (m_lastErrorCode != EDK_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[LOOP] Emotiv Driver: An error occurred while getting new samples from device. EDK Error Code [" << m_lastErrorCode << "]\n";
|
||||
return false;
|
||||
}
|
||||
// warning : if you connect/disconnect then reconnect, the internal buffer may be full of samples, thus maybe nSamplesTaken > m_nSamplePerSentBlock
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "EMOTIV EPOC >>> received [" << nSamplesTaken << "] samples per channel from device with user #" << m_userID << ".\n";
|
||||
|
||||
/*
|
||||
for (size_t i=0; i<m_header.getChannelCount(); ++i)
|
||||
{
|
||||
for (uint32_t s=0;s<nSamplesTaken;s++)
|
||||
{
|
||||
double* buffer = new double[nSamplesTaken];
|
||||
m_lastErrorCode = IEE_DataGet(m_dataHandle, CHANNEL_LIST[i], buffer, nSamplesTaken);
|
||||
if(m_lastErrorCode != EDK_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[LOOP] Emotiv Driver: An error occurred while getting new samples from device. EDK Error Code [" << m_lastErrorCode << "]\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "EMOTIV EPOC >>> adding sample with value ["<< buffer[s] <<"]\n";
|
||||
m_sample[i*m_nSamplePerSentBlock + s] = (float)buffer[s];
|
||||
delete [] buffer;
|
||||
}
|
||||
m_callback->setSamples(m_sample);
|
||||
}
|
||||
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
|
||||
*/
|
||||
|
||||
double* buffer = new double[nSamplesTaken];
|
||||
for (uint32_t s = 0; s < nSamplesTaken; ++s)
|
||||
{
|
||||
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
|
||||
{
|
||||
m_lastErrorCode = IEE_DataGet(m_dataHandle, CHANNEL_LIST[i], buffer, nSamplesTaken);
|
||||
if (m_lastErrorCode != EDK_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "[LOOP] Emotiv Driver: An error occurred while getting new samples from device. EDK Error Code [" << m_lastErrorCode << "]\n";
|
||||
return false;
|
||||
}
|
||||
m_sample[i] = float(buffer[s]);
|
||||
if (s == 0) m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "EMOTIV EPOC >>> sample received has value [" << buffer[s] << "]\n";
|
||||
//m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "EMOTIV EPOC >>> sample stored has value ["<< m_sample[i] <<"]\n";
|
||||
}
|
||||
m_callback->setSamples(m_sample, 1);
|
||||
}
|
||||
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
|
||||
delete [] buffer;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverEmotivEPOC::stop()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "STOP called.\n";
|
||||
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
IEE_DataFree(m_dataHandle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverEmotivEPOC::uninitialize()
|
||||
{
|
||||
IEE_EngineDisconnect();
|
||||
|
||||
if (m_eventHandle)
|
||||
{
|
||||
IEE_EmoEngineEventFree(m_eventHandle);
|
||||
m_eventHandle = nullptr;
|
||||
}
|
||||
|
||||
restoreState();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverEmotivEPOC::configure()
|
||||
{
|
||||
CConfigurationEmotivEPOC config(m_driverCtx, Directories::getDataDir() + "/applications/acquisition-server/interface-Emotiv-EPOC.ui", m_useGyroscope, m_pathToEmotivSDK, m_userID);
|
||||
|
||||
if (!config.configure(m_header)) { return false; }
|
||||
m_settings.save();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_HAS_ThirdPartyEmotivAPI
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyEmotivAPI
|
||||
|
||||
#include "ovasIDriver.h"
|
||||
#include "../ovasCHeader.h"
|
||||
|
||||
#include "../ovasCSettingsHelper.h"
|
||||
#include "../ovasCSettingsHelperOperators.h"
|
||||
|
||||
#include <openvibe/ov_all.h>
|
||||
|
||||
#if defined(TARGET_HAS_ThirdPartyEmotivResearchAPI3x)
|
||||
#include "IEmoStateDLL.h"
|
||||
#include "Iedk.h"
|
||||
#include "IedkErrorCode.h"
|
||||
#include "IEegData.h"
|
||||
#else
|
||||
// Old API
|
||||
#include "EmoStateDLL.h"
|
||||
#include "edk.h"
|
||||
#include "edkErrorCode.h"
|
||||
|
||||
#define IEE_DataAcquisitionEnable EE_DataAcquisitionEnable
|
||||
#define IEE_DataCreate EE_DataCreate
|
||||
#define IEE_DataFree EE_DataFree
|
||||
#define IEE_DataGet EE_DataGet
|
||||
#define IEE_DataGetNumberOfSample EE_DataGetNumberOfSample
|
||||
#define IEE_DataSetBufferSizeInSec EE_DataSetBufferSizeInSec
|
||||
#define IEE_DataUpdateHandle EE_DataUpdateHandle
|
||||
#define IEE_EmoEngineEventCreate EE_EmoEngineEventCreate
|
||||
#define IEE_EmoEngineEventFree EE_EmoEngineEventFree
|
||||
#define IEE_EmoEngineEventGetType EE_EmoEngineEventGetType
|
||||
#define IEE_EmoEngineEventGetUserId EE_EmoEngineEventGetUserId
|
||||
#define IEE_EngineConnect EE_EngineConnect
|
||||
#define IEE_EngineDisconnect EE_EngineDisconnect
|
||||
#define IEE_EngineGetNextEvent EE_EngineGetNextEvent
|
||||
#define IEE_Event_t EE_Event_t
|
||||
#define IEE_UserAdded EE_UserAdded
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
#if defined TARGET_OS_Windows
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CDriverEmotivEPOC
|
||||
* \author Laurent Bonnet (INRIA)
|
||||
* \date 21 july 2010
|
||||
* \erief The CDriverEmotivEPOC allows the acquisition server to acquire data from a Emotiv EPOC amplifier, Research Edition or above.
|
||||
*
|
||||
*/
|
||||
class CDriverEmotivEPOC final : public IDriver
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CDriverEmotivEPOC(IDriverContext& ctx);
|
||||
~CDriverEmotivEPOC() 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 { return true; }
|
||||
bool configure() override;
|
||||
const IHeader* getHeader() override { return &m_header; }
|
||||
|
||||
protected:
|
||||
|
||||
SettingsHelper m_settings;
|
||||
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
|
||||
CHeader m_header;
|
||||
|
||||
uint32_t m_nSamplePerSentBlock = 0;
|
||||
uint32_t m_nTotalSample = 0;
|
||||
float* m_sample = nullptr;
|
||||
//double* m_Buffer;
|
||||
|
||||
private:
|
||||
bool buildPath();
|
||||
bool restoreState();
|
||||
|
||||
uint32_t m_lastErrorCode = 0;
|
||||
|
||||
EmoEngineEventHandle m_eventHandle;
|
||||
uint32_t m_userID = 0;
|
||||
bool m_readyToCollect = false;
|
||||
|
||||
DataHandle m_dataHandle;
|
||||
bool m_firstStart = false;
|
||||
|
||||
bool m_useGyroscope = false;
|
||||
CString m_pathToEmotivSDK;
|
||||
CString m_cmdForPathModification;
|
||||
CString m_oldPath;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyEmotivAPI
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+43
@@ -0,0 +1,43 @@
|
||||
#include "ovasCConfigurationDriverGenericOscillator.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
CConfigurationDriverGenericOscillator::CConfigurationDriverGenericOscillator(IDriverContext& ctx, const char* gtkBuilderFilename,
|
||||
bool& sendPeriodicStimulations, double& stimulationInterval)
|
||||
: CConfigurationBuilder(gtkBuilderFilename), m_driverCtx(ctx), m_sendPeriodicStimulations(sendPeriodicStimulations),
|
||||
m_stimulationInterval(stimulationInterval) {}
|
||||
|
||||
bool CConfigurationDriverGenericOscillator::preConfigure()
|
||||
{
|
||||
if (!CConfigurationBuilder::preConfigure()) { return false; }
|
||||
|
||||
GtkToggleButton* sendPeriodicStims = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_send_periodic_stimulations"));
|
||||
GtkSpinButton* stimulationInterval = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_stimulation_interval"));
|
||||
|
||||
gtk_toggle_button_set_active(sendPeriodicStims, m_sendPeriodicStimulations);
|
||||
gtk_spin_button_set_value(stimulationInterval, m_stimulationInterval);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CConfigurationDriverGenericOscillator::postConfigure()
|
||||
{
|
||||
if (m_applyConfig)
|
||||
{
|
||||
GtkToggleButton* sendPeriodicStims = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_send_periodic_stimulations"));
|
||||
GtkSpinButton* stimulationInterval = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_stimulation_interval"));
|
||||
|
||||
m_sendPeriodicStimulations = (gtk_toggle_button_get_active(sendPeriodicStims) > 0);
|
||||
|
||||
gtk_spin_button_update(stimulationInterval);
|
||||
m_stimulationInterval = gtk_spin_button_get_value(stimulationInterval);
|
||||
}
|
||||
|
||||
if (!CConfigurationBuilder::postConfigure()) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovasCConfigurationBuilder.h"
|
||||
#include "ovasIDriver.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CConfigurationDriverGenericOscillator
|
||||
* \author Jozef Legeny (Inria)
|
||||
* \date 28 jan 2013
|
||||
* \brief The CConfigurationDriverGenericOscillator handles the configuration dialog specific to the Generic Oscillator driver
|
||||
*
|
||||
* \sa CDriverGenericOscillator
|
||||
*/
|
||||
|
||||
class CConfigurationDriverGenericOscillator final : public CConfigurationBuilder
|
||||
{
|
||||
public:
|
||||
CConfigurationDriverGenericOscillator(IDriverContext& ctx, const char* gtkBuilderFilename, bool& sendPeriodicStimulations, double& stimulationInterval);
|
||||
|
||||
bool preConfigure() override;
|
||||
bool postConfigure() override;
|
||||
|
||||
protected:
|
||||
|
||||
IDriverContext& m_driverCtx;
|
||||
|
||||
bool& m_sendPeriodicStimulations;
|
||||
double& m_stimulationInterval;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
#include "ovasCDriverGenericOscillator.h"
|
||||
#include "ovasCConfigurationDriverGenericOscillator.h"
|
||||
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <system/ovCTime.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
CDriverGenericOscillator::CDriverGenericOscillator(IDriverContext& ctx)
|
||||
: IDriver(ctx), m_settings("AcquisitionServer_Driver_GenericOscillator", m_driverCtx.getConfigurationManager()), m_stimulationInterval(1.0)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericOscillator::CDriverGenericOscillator\n";
|
||||
|
||||
m_header.setSamplingFrequency(512);
|
||||
m_header.setChannelCount(4);
|
||||
|
||||
m_settings.add("Header", &m_header);
|
||||
m_settings.add("SendPeriodicStimulations", &m_sendPeriodicStimulations);
|
||||
m_settings.add("StimulationInterval", &m_stimulationInterval);
|
||||
m_settings.load();
|
||||
}
|
||||
|
||||
void CDriverGenericOscillator::release()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericOscillator::release\n";
|
||||
delete this;
|
||||
}
|
||||
|
||||
const char* CDriverGenericOscillator::getName()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericOscillator::getName\n";
|
||||
return "Generic Oscillator";
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverGenericOscillator::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericOscillator::initialize\n";
|
||||
if (m_driverCtx.isConnected() || !m_header.isChannelCountSet() || !m_header.isSamplingFrequencySet()) { return false; }
|
||||
|
||||
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
|
||||
{
|
||||
m_header.setChannelUnits(i, OVTK_UNIT_Volts, OVTK_FACTOR_Base);
|
||||
if (CString(m_header.getChannelName(i)) == CString(""))
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << "Oscillator " << (i + 1);
|
||||
m_header.setChannelName(i, ss.str().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
m_samples.resize(m_header.getChannelCount() * nSamplePerSentBlock);
|
||||
|
||||
m_callback = &callback;
|
||||
m_nSamplePerSentBlock = nSamplePerSentBlock;
|
||||
|
||||
m_stimSet.setStimulationCount(1);
|
||||
m_stimSet.setStimulationIdentifier(0, OVTK_StimulationId_Label_00);
|
||||
m_stimSet.setStimulationDate(0, 0);
|
||||
m_stimSet.setStimulationDuration(0, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericOscillator::start()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericOscillator::start\n";
|
||||
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
m_nTotalSample = 0;
|
||||
m_startTime = System::Time::zgetTime();
|
||||
m_nTotalStim = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericOscillator::loop()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "CDriverGenericOscillator::loop\n";
|
||||
|
||||
if (!m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
if (m_driverCtx.isStarted())
|
||||
{
|
||||
// Generate the contents we want to send next
|
||||
const uint64_t elapsed = System::Time::zgetTime() - m_startTime;
|
||||
const uint64_t samplesNeededSoFar = CTime(elapsed).toSampleCount(m_header.getSamplingFrequency());
|
||||
if (samplesNeededSoFar <= m_nTotalSample)
|
||||
{
|
||||
// Too early
|
||||
return true;
|
||||
}
|
||||
const size_t remainingSamples = size_t(samplesNeededSoFar - m_nTotalSample);
|
||||
if (remainingSamples * m_header.getChannelCount() > m_samples.size()) { m_samples.resize(remainingSamples * m_header.getChannelCount()); }
|
||||
|
||||
// std::cout << "At " << CTime(elapsed).toSeconds() * 1000 << "ms, remaining " << remainingSamples << " samples\n";
|
||||
for (size_t i = 0; i < remainingSamples; ++i)
|
||||
{
|
||||
for (size_t j = 0; j < m_header.getChannelCount(); ++j)
|
||||
{
|
||||
const double value = sin((m_nTotalSample * (j + 1) * 12.3) / m_header.getSamplingFrequency())
|
||||
+ sin((m_nTotalSample * (j + 1) * 4.5) / m_header.getSamplingFrequency())
|
||||
+ sin((m_nTotalSample * (j + 1) * 67.8) / m_header.getSamplingFrequency());
|
||||
m_samples[j * remainingSamples + i] = float(value);
|
||||
}
|
||||
m_nTotalSample++;
|
||||
}
|
||||
|
||||
m_callback->setSamples(&m_samples[0], remainingSamples);
|
||||
|
||||
if (m_sendPeriodicStimulations && elapsed >= m_nTotalStim * CTime(m_stimulationInterval).time())
|
||||
{
|
||||
m_callback->setStimulationSet(m_stimSet);
|
||||
m_nTotalStim++;
|
||||
}
|
||||
|
||||
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
|
||||
}
|
||||
else { if (m_driverCtx.isImpedanceCheckRequested()) { for (size_t j = 0; j < m_header.getChannelCount(); ++j) { m_driverCtx.updateImpedance(j, 1); } } }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericOscillator::stop()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericOscillator::stop\n";
|
||||
return (m_driverCtx.isConnected() && m_driverCtx.isStarted());
|
||||
}
|
||||
|
||||
bool CDriverGenericOscillator::uninitialize()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericOscillator::uninitialize\n";
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
m_callback = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverGenericOscillator::isConfigurable()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericOscillator::isConfigurable\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericOscillator::configure()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericOscillator::configure\n";
|
||||
|
||||
CConfigurationDriverGenericOscillator config(m_driverCtx, Directories::getDataDir() + "/applications/acquisition-server/interface-Generic-Oscillator.ui",
|
||||
m_sendPeriodicStimulations, m_stimulationInterval);
|
||||
|
||||
if (config.configure(m_header))
|
||||
{
|
||||
m_settings.save();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "ovasIDriver.h"
|
||||
#include "../ovasCHeader.h"
|
||||
|
||||
#include "../ovasCSettingsHelper.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CDriverGenericOscillator
|
||||
* \author Yann Renard (INRIA)
|
||||
*/
|
||||
class CDriverGenericOscillator final : public IDriver
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CDriverGenericOscillator(IDriverContext& ctx);
|
||||
void release();
|
||||
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; }
|
||||
|
||||
protected:
|
||||
|
||||
SettingsHelper m_settings;
|
||||
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
CHeader m_header;
|
||||
|
||||
uint32_t m_nSamplePerSentBlock = 0;
|
||||
std::vector<float> m_samples;
|
||||
|
||||
uint32_t m_nTotalSample = 0;
|
||||
uint64_t m_nTotalStim = 0;
|
||||
uint64_t m_startTime = 0;
|
||||
|
||||
CStimulationSet m_stimSet;
|
||||
|
||||
private:
|
||||
bool m_sendPeriodicStimulations = false;
|
||||
double m_stimulationInterval = 0; // Seconds
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
#include "ovasCConfigurationGenericRawReader.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
CConfigurationGenericRawReader::CConfigurationGenericRawReader(const char* gtkBuilderFilename, bool& limitSpeed, uint32_t& sampleFormat, uint32_t& sampleEndian,
|
||||
uint32_t& startSkip, uint32_t& headerSkip, uint32_t& footerSkip, CString& filename)
|
||||
: CConfigurationNetworkBuilder(gtkBuilderFilename), m_limitSpeed(limitSpeed), m_sampleFormat(sampleFormat), m_sampleEndian(sampleEndian),
|
||||
m_startSkip(startSkip), m_headerSkip(headerSkip), m_footerSkip(footerSkip), m_filename(filename) {}
|
||||
|
||||
bool CConfigurationGenericRawReader::preConfigure()
|
||||
{
|
||||
if (!CConfigurationNetworkBuilder::preConfigure()) { return false; }
|
||||
|
||||
GtkToggleButton* speedLimit = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_limit_speed"));
|
||||
GtkEntry* filename = GTK_ENTRY(gtk_builder_get_object(m_builder, "entry_filename"));
|
||||
GtkComboBox* endianness = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_endianness"));
|
||||
GtkComboBox* sampleType = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_sample_type"));
|
||||
GtkSpinButton* startSize = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_start_size"));
|
||||
GtkSpinButton* headerSize = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_header_size"));
|
||||
GtkSpinButton* footerSize = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_footer_size"));
|
||||
|
||||
gtk_toggle_button_set_active(speedLimit, m_limitSpeed ? TRUE : FALSE);
|
||||
gtk_entry_set_text(filename, m_filename.toASCIIString());
|
||||
gtk_combo_box_set_active(endianness, m_sampleEndian);
|
||||
gtk_combo_box_set_active(sampleType, m_sampleFormat);
|
||||
gtk_spin_button_set_value(startSize, m_startSkip);
|
||||
gtk_spin_button_set_value(headerSize, m_headerSkip);
|
||||
gtk_spin_button_set_value(footerSize, m_footerSkip);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CConfigurationGenericRawReader::postConfigure()
|
||||
{
|
||||
if (m_applyConfig)
|
||||
{
|
||||
GtkToggleButton* speedLimit = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_limit_speed"));
|
||||
GtkEntry* filename = GTK_ENTRY(gtk_builder_get_object(m_builder, "entry_filename"));
|
||||
GtkComboBox* endianness = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_endianness"));
|
||||
GtkComboBox* sampleType = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_sample_type"));
|
||||
GtkSpinButton* startSize = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_start_size"));
|
||||
GtkSpinButton* headerSize = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_header_size"));
|
||||
GtkSpinButton* footerSize = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_footer_size"));
|
||||
|
||||
gtk_spin_button_update(startSize);
|
||||
gtk_spin_button_update(headerSize);
|
||||
gtk_spin_button_update(footerSize);
|
||||
|
||||
m_limitSpeed = gtk_toggle_button_get_active(speedLimit) ? true : false;
|
||||
m_filename = gtk_entry_get_text(filename);
|
||||
m_sampleEndian = uint32_t(gtk_combo_box_get_active(endianness));
|
||||
m_sampleFormat = uint32_t(gtk_combo_box_get_active(sampleType));
|
||||
m_startSkip = uint32_t(gtk_spin_button_get_value(startSize));
|
||||
m_headerSkip = uint32_t(gtk_spin_button_get_value(headerSize));
|
||||
m_footerSkip = uint32_t(gtk_spin_button_get_value(footerSize));
|
||||
}
|
||||
|
||||
if (!CConfigurationNetworkBuilder::postConfigure()) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovasCConfigurationNetworkBuilder.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
class CConfigurationGenericRawReader final : public CConfigurationNetworkBuilder
|
||||
{
|
||||
public:
|
||||
|
||||
CConfigurationGenericRawReader(const char* gtkBuilderFilename, bool& limitSpeed, uint32_t& sampleFormat, uint32_t& sampleEndian,
|
||||
uint32_t& startSkip, uint32_t& headerSkip, uint32_t& footerSkip, CString& filename);
|
||||
|
||||
protected:
|
||||
|
||||
bool preConfigure() override;
|
||||
bool postConfigure() override;
|
||||
|
||||
bool& m_limitSpeed;
|
||||
uint32_t& m_sampleFormat;
|
||||
uint32_t& m_sampleEndian;
|
||||
uint32_t& m_startSkip;
|
||||
uint32_t& m_headerSkip;
|
||||
uint32_t& m_footerSkip;
|
||||
CString& m_filename;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
#include "ovasCDriverGenericRawFileReader.h"
|
||||
#include "ovasCConfigurationGenericRawReader.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
CDriverGenericRawFileReader::CDriverGenericRawFileReader(IDriverContext& ctx)
|
||||
: CDriverGenericRawReader(ctx), m_settings("AcquisitionServer_Driver_GenericRawFileReader", m_driverCtx.getConfigurationManager())
|
||||
{
|
||||
m_filename = "/tmp/some_raw_file";
|
||||
|
||||
// Relay configuration properties to the configuration manager
|
||||
|
||||
m_settings.add("Header", &m_header);
|
||||
m_settings.add("LimitSpeed", &m_limitSpeed);
|
||||
m_settings.add("SampleFormat", &m_sampleFormat);
|
||||
m_settings.add("SampleEndian", &m_sampleEndian);
|
||||
m_settings.add("StartSkip", &m_startSkip);
|
||||
m_settings.add("HeaderSkip", &m_headerSkip);
|
||||
m_settings.add("FooterSkip", &m_footerSkip);
|
||||
m_settings.add("FileName", &m_filename);
|
||||
m_settings.load();
|
||||
}
|
||||
|
||||
bool CDriverGenericRawFileReader::configure()
|
||||
{
|
||||
CConfigurationGenericRawReader config(Directories::getDataDir() + "/applications/acquisition-server/interface-Generic-RawFileReader.ui", m_limitSpeed,
|
||||
m_sampleFormat, m_sampleEndian, m_startSkip, m_headerSkip, m_footerSkip, m_filename);
|
||||
|
||||
if (!config.configure(m_header)) { return false; }
|
||||
m_settings.save();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericRawFileReader::open()
|
||||
{
|
||||
m_file = fopen(m_filename.toASCIIString(), "rb");
|
||||
if (!m_file)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not open file [" << m_filename << "]\n";
|
||||
return false;
|
||||
}
|
||||
if (fseek(m_file, m_startSkip, SEEK_SET) != 0)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not seek to " << m_startSkip << " bytes from the file beginning\n";
|
||||
fclose(m_file);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericRawFileReader::close()
|
||||
{
|
||||
if (m_file)
|
||||
{
|
||||
fclose(m_file);
|
||||
m_file = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericRawFileReader::read()
|
||||
{
|
||||
if (!m_file) { return false; }
|
||||
const bool res = (fread(m_dataFrame, 1, m_dataFrameSize, m_file) == m_dataFrameSize);
|
||||
if (!res && feof(m_file)) { m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "End of file reached.\n"; }
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "ovasCDriverGenericRawReader.h"
|
||||
|
||||
#include "../ovasCSettingsHelper.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CDriverGenericRawFileReader
|
||||
* \author Yann Renard (INRIA)
|
||||
*/
|
||||
class CDriverGenericRawFileReader final : public CDriverGenericRawReader
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CDriverGenericRawFileReader(IDriverContext& ctx);
|
||||
|
||||
const char* getName() override { return "Generic Raw File Reader"; }
|
||||
bool isConfigurable() override { return true; }
|
||||
bool configure() override;
|
||||
|
||||
protected:
|
||||
|
||||
bool open() override;
|
||||
bool close() override;
|
||||
bool read() override;
|
||||
|
||||
SettingsHelper m_settings;
|
||||
FILE* m_file = nullptr;
|
||||
CString m_filename;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* The raw reader expects the data to be formatted as follows
|
||||
*
|
||||
* [START][BLOCK0][BLOCK1][BLOCK2]...
|
||||
* skip parse parse parse ...
|
||||
*
|
||||
* where each block is [===========BLOCKX=================]
|
||||
* is read as [===========dataFrameSize==========]
|
||||
* breaks to [header====][sample====][footer====]
|
||||
* equals [headerSize][sampleSize][footerSize]
|
||||
* means skip keep skip
|
||||
*
|
||||
* For correct parsing, user must provide the exact sizes of the skipped parts "start", "header" and "footer" in bytes.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ovasCDriverGenericRawReader.h"
|
||||
#include "../ovasCConfigurationBuilder.h"
|
||||
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <system/ovCMemory.h>
|
||||
#include <system/ovCTime.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
// #define OPENVIBE_DEBUG_RAW_READER
|
||||
|
||||
template <class T>
|
||||
static float decode_little_endian(const uint8_t* buffer)
|
||||
{
|
||||
T t;
|
||||
System::Memory::littleEndianToHost(buffer, &t);
|
||||
return float(t);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static float decode_big_endian(const uint8_t* buffer)
|
||||
{
|
||||
T t;
|
||||
System::Memory::bigEndianToHost(buffer, &t);
|
||||
return float(t);
|
||||
}
|
||||
|
||||
CDriverGenericRawReader::CDriverGenericRawReader(IDriverContext& ctx)
|
||||
: IDriver(ctx)
|
||||
{
|
||||
m_header.setSamplingFrequency(512);
|
||||
m_header.setChannelCount(16);
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverGenericRawReader::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
|
||||
{
|
||||
if (m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
switch (m_sampleFormat)
|
||||
{
|
||||
case Format_UnsignedInteger8:
|
||||
case Format_SignedInteger8: m_sampleSize = 1;
|
||||
break;
|
||||
case Format_UnsignedInteger16:
|
||||
case Format_SignedInteger16: m_sampleSize = 2;
|
||||
break;
|
||||
case Format_UnsignedInteger32:
|
||||
case Format_SignedInteger32:
|
||||
case Format_Float32: m_sampleSize = 4;
|
||||
break;
|
||||
case Format_Float64: m_sampleSize = 8;
|
||||
break;
|
||||
default: m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unsupported data format " << m_sampleFormat << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_dataFrameSize = m_sampleSize * m_header.getChannelCount();
|
||||
m_dataFrameSize += m_headerSkip;
|
||||
m_dataFrameSize += m_footerSkip;
|
||||
|
||||
m_sample = new float[m_header.getChannelCount()];
|
||||
m_dataFrame = new uint8_t[m_dataFrameSize];
|
||||
if (!m_sample || !m_dataFrame)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not allocate memory !\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// open() should skip m_startSkip worth of bytes already
|
||||
if (!this->open()) { return false; }
|
||||
|
||||
m_callback = &callback;
|
||||
m_nSamplePerSentBlock = nSamplePerSentBlock;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericRawReader::start()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
m_nTotalSample = 0;
|
||||
m_startTime = System::Time::zgetTime();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericRawReader::loop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected()) { return false; }
|
||||
// if(!m_driverCtx.isStarted()) { return true; }
|
||||
|
||||
const uint64_t time = CTime(m_header.getSamplingFrequency(), m_nTotalSample).time();
|
||||
if (m_limitSpeed && (time > System::Time::zgetTime() - m_startTime)) { return true; }
|
||||
|
||||
#ifdef OPENVIBE_DEBUG_RAW_READER
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Decoded : ";
|
||||
#endif
|
||||
|
||||
for (uint32_t j = 0; j < m_nSamplePerSentBlock; ++j)
|
||||
{
|
||||
if (!this->read()) { return false; }
|
||||
|
||||
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
|
||||
{
|
||||
uint8_t* dataFrame = m_dataFrame + m_headerSkip + i * m_sampleSize;
|
||||
switch (m_sampleEndian)
|
||||
{
|
||||
case Endian_Little: switch (m_sampleFormat)
|
||||
{
|
||||
case Format_UnsignedInteger8: m_sample[i] = *dataFrame;
|
||||
break;
|
||||
case Format_UnsignedInteger16: m_sample[i] = decode_little_endian<uint16_t>(dataFrame);
|
||||
break;
|
||||
case Format_UnsignedInteger32: m_sample[i] = decode_little_endian<uint32_t>(dataFrame);
|
||||
break;
|
||||
case Format_SignedInteger8: m_sample[i] = *dataFrame;
|
||||
break;
|
||||
case Format_SignedInteger16: m_sample[i] = decode_little_endian<int16_t>(dataFrame);
|
||||
break;
|
||||
case Format_SignedInteger32: m_sample[i] = decode_little_endian<int>(dataFrame);
|
||||
break;
|
||||
case Format_Float32: m_sample[i] = decode_little_endian<float>(dataFrame);
|
||||
break;
|
||||
case Format_Float64: m_sample[i] = decode_little_endian<double>(dataFrame);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Endian_Big: switch (m_sampleFormat)
|
||||
{
|
||||
case Format_UnsignedInteger8: m_sample[i] = *dataFrame;
|
||||
break;
|
||||
case Format_UnsignedInteger16: m_sample[i] = decode_big_endian<uint16_t>(dataFrame);
|
||||
break;
|
||||
case Format_UnsignedInteger32: m_sample[i] = decode_big_endian<uint32_t>(dataFrame);
|
||||
break;
|
||||
case Format_SignedInteger8: m_sample[i] = *dataFrame;
|
||||
break;
|
||||
case Format_SignedInteger16: m_sample[i] = decode_big_endian<int16_t>(dataFrame);
|
||||
break;
|
||||
case Format_SignedInteger32: m_sample[i] = decode_big_endian<int>(dataFrame);
|
||||
break;
|
||||
case Format_Float32: m_sample[i] = decode_big_endian<float>(dataFrame);
|
||||
break;
|
||||
case Format_Float64: m_sample[i] = decode_big_endian<double>(dataFrame);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
#ifdef OPENVIBE_DEBUG_RAW_READER
|
||||
m_driverCtx.getLogManager() << m_sample[i] << " ";
|
||||
#endif
|
||||
}
|
||||
if (m_driverCtx.isStarted()) { m_callback->setSamples(m_sample, 1); }
|
||||
}
|
||||
#ifdef OPENVIBE_DEBUG_RAW_READER
|
||||
m_driverCtx.getLogManager() << "\n";
|
||||
#endif
|
||||
|
||||
if (m_driverCtx.isStarted()) { m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount()); }
|
||||
m_nTotalSample += m_nSamplePerSentBlock;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericRawReader::stop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericRawReader::uninitialize()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted() || !this->close()) { return false; }
|
||||
|
||||
delete [] m_sample;
|
||||
delete [] m_dataFrame;
|
||||
m_sample = nullptr;
|
||||
m_dataFrame = nullptr;
|
||||
m_callback = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "ovasIDriver.h"
|
||||
#include "../ovasCHeader.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CDriverGenericRawReader
|
||||
* \author Yann Renard (INRIA)
|
||||
*/
|
||||
class CDriverGenericRawReader : public IDriver
|
||||
{
|
||||
public:
|
||||
|
||||
enum EParameter
|
||||
{
|
||||
Endian_Little = 0,
|
||||
Endian_Big = 1,
|
||||
|
||||
Format_UnsignedInteger8 = 0,
|
||||
Format_UnsignedInteger16 = 1,
|
||||
Format_UnsignedInteger32 = 2,
|
||||
Format_SignedInteger8 = 3,
|
||||
Format_SignedInteger16 = 4,
|
||||
Format_SignedInteger32 = 5,
|
||||
Format_Float32 = 6,
|
||||
Format_Float64 = 7,
|
||||
};
|
||||
|
||||
explicit CDriverGenericRawReader(IDriverContext& ctx);
|
||||
virtual void release() { delete this; }
|
||||
|
||||
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
|
||||
bool uninitialize() override;
|
||||
|
||||
bool start() override;
|
||||
bool stop() override;
|
||||
bool loop() override;
|
||||
|
||||
const IHeader* getHeader() override { return &m_header; }
|
||||
|
||||
protected:
|
||||
|
||||
virtual bool open() = 0;
|
||||
virtual bool close() = 0;
|
||||
virtual bool read() = 0;
|
||||
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
CHeader m_header;
|
||||
uint32_t m_nSamplePerSentBlock = 0;
|
||||
uint32_t m_sampleSize = 0;
|
||||
uint32_t m_sampleFormat = Format_SignedInteger32;
|
||||
uint32_t m_sampleEndian = 0;
|
||||
uint32_t m_startSkip = 0;
|
||||
uint32_t m_headerSkip = 0;
|
||||
uint32_t m_footerSkip = 20;
|
||||
uint32_t m_dataFrameSize = 0;
|
||||
bool m_limitSpeed = false;
|
||||
uint8_t* m_dataFrame = nullptr;
|
||||
float* m_sample = nullptr;
|
||||
uint64_t m_startTime = 0;
|
||||
uint64_t m_nTotalSample = 0;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
#include "ovasCDriverGenericRawTelnetReader.h"
|
||||
#include "ovasCConfigurationGenericRawReader.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
CDriverGenericRawTelnetReader::CDriverGenericRawTelnetReader(IDriverContext& ctx)
|
||||
: CDriverGenericRawReader(ctx), m_settings("AcquisitionServer_Driver_GenericRawTelnetReader", m_driverCtx.getConfigurationManager())
|
||||
{
|
||||
m_hostName = "localhost";
|
||||
m_hostPort = 1337;
|
||||
|
||||
// Relay configuration properties to the configuration manager
|
||||
m_settings.add("Header", &m_header);
|
||||
m_settings.add("LimitSpeed", &m_limitSpeed);
|
||||
m_settings.add("SampleFormat", &m_sampleFormat);
|
||||
m_settings.add("SampleEndian", &m_sampleEndian);
|
||||
m_settings.add("StartSkip", &m_startSkip);
|
||||
m_settings.add("HeaderSkip", &m_headerSkip);
|
||||
m_settings.add("FooterSkip", &m_footerSkip);
|
||||
m_settings.add("HostName", &m_hostName);
|
||||
m_settings.add("HostPort", &m_hostPort);
|
||||
m_settings.load();
|
||||
}
|
||||
|
||||
bool CDriverGenericRawTelnetReader::configure()
|
||||
{
|
||||
CString filename;
|
||||
CConfigurationGenericRawReader config(Directories::getDataDir() + "/applications/acquisition-server/interface-Generic-RawTelnetReader.ui",
|
||||
m_limitSpeed, m_sampleFormat, m_sampleEndian, m_startSkip, m_headerSkip, m_footerSkip, filename);
|
||||
|
||||
config.setHostName(m_hostName);
|
||||
config.setHostPort(m_hostPort);
|
||||
|
||||
if (!config.configure(m_header)) { return false; }
|
||||
|
||||
m_hostName = config.getHostName();
|
||||
m_hostPort = config.getHostPort();
|
||||
|
||||
m_settings.save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericRawTelnetReader::open()
|
||||
{
|
||||
m_connection = Socket::createConnectionClient();
|
||||
if (!m_connection)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not create client connection\n";
|
||||
return false;
|
||||
}
|
||||
if (!m_connection->connect(m_hostName.toASCIIString(), m_hostPort))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not connect to server [" << m_hostName << ":" << m_hostPort << "]\n";
|
||||
return false;
|
||||
}
|
||||
char* buffer = new char[m_startSkip];
|
||||
if (m_startSkip > 0 && !m_connection->receiveBufferBlocking(buffer, m_startSkip))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unable to skip " << m_startSkip << " bytes at the beginning\n";
|
||||
delete[] buffer;
|
||||
return false;
|
||||
}
|
||||
delete[] buffer;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericRawTelnetReader::close()
|
||||
{
|
||||
if (m_connection)
|
||||
{
|
||||
m_connection->close();
|
||||
m_connection->release();
|
||||
m_connection = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericRawTelnetReader::read()
|
||||
{
|
||||
if (!m_connection) { return false; }
|
||||
return m_connection->receiveBufferBlocking(m_dataFrame, m_dataFrameSize);
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "ovasCDriverGenericRawReader.h"
|
||||
|
||||
#include "../ovasCSettingsHelper.h"
|
||||
|
||||
#include <socket/IConnectionClient.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CDriverGenericRawTelnetReader
|
||||
* \author Yann Renard (INRIA)
|
||||
*/
|
||||
class CDriverGenericRawTelnetReader final : public CDriverGenericRawReader
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CDriverGenericRawTelnetReader(IDriverContext& ctx);
|
||||
|
||||
const char* getName() override { return "Generic Raw Telnet Reader"; }
|
||||
bool isConfigurable() override { return true; }
|
||||
bool configure() override;
|
||||
|
||||
protected:
|
||||
|
||||
bool open() override;
|
||||
bool close() override;
|
||||
bool read() override;
|
||||
|
||||
SettingsHelper m_settings;
|
||||
Socket::IConnectionClient* m_connection = nullptr;
|
||||
CString m_hostName = "localhost";
|
||||
uint32_t m_hostPort = 1337;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
#include "ovasCDriverGenericSawTooth.h"
|
||||
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <system/ovCTime.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
CDriverGenericSawTooth::CDriverGenericSawTooth(IDriverContext& ctx)
|
||||
: IDriver(ctx)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericSawTooth::CDriverGenericSawTooth\n";
|
||||
|
||||
m_header.setSamplingFrequency(512);
|
||||
m_header.setChannelCount(1);
|
||||
m_header.setChannelName(0, "Sawtooth");
|
||||
m_header.setChannelUnits(0, OVTK_UNIT_Volts, OVTK_FACTOR_Base);
|
||||
}
|
||||
|
||||
void CDriverGenericSawTooth::release()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericSawTooth::release\n";
|
||||
delete this;
|
||||
}
|
||||
|
||||
const char* CDriverGenericSawTooth::getName()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericSawTooth::getName\n";
|
||||
return "Generic Saw Tooth";
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverGenericSawTooth::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericSawTooth::initialize\n";
|
||||
if (m_driverCtx.isConnected() || !m_header.isChannelCountSet() || !m_header.isSamplingFrequencySet()) { return false; }
|
||||
|
||||
m_samples.resize(m_header.getChannelCount() * nSamplePerSentBlock);
|
||||
m_callback = &callback;
|
||||
m_externalBlockSize = nSamplePerSentBlock;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericSawTooth::start()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericSawTooth::start\n";
|
||||
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
m_nTotalSample = 0;
|
||||
m_startTime = System::Time::zgetTime();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericSawTooth::loop()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "CDriverGenericSawTooth::loop\n";
|
||||
|
||||
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return true; }
|
||||
|
||||
// Find out how many samples to send
|
||||
const uint64_t elapsed = System::Time::zgetTime() - m_startTime;
|
||||
const uint64_t samplesNeededSoFar = CTime(elapsed).toSampleCount(m_header.getSamplingFrequency());
|
||||
if (samplesNeededSoFar <= m_nTotalSample)
|
||||
{
|
||||
// Too early
|
||||
return true;
|
||||
}
|
||||
const size_t remainingSamples = size_t(samplesNeededSoFar - m_nTotalSample);
|
||||
if (remainingSamples * m_header.getChannelCount() > m_samples.size()) { m_samples.resize(remainingSamples * m_header.getChannelCount()); }
|
||||
|
||||
// Generate the data
|
||||
// The result should be a linear ramp between [0,1] for each block sent *out* by the acquisition server
|
||||
for (size_t i = 0; i < remainingSamples; ++i)
|
||||
{
|
||||
for (size_t j = 0; j < m_header.getChannelCount(); ++j)
|
||||
{
|
||||
m_samples[j * remainingSamples + i] = float(m_nTotalSample % m_externalBlockSize) / float(m_externalBlockSize - 1);
|
||||
}
|
||||
m_nTotalSample++;
|
||||
}
|
||||
|
||||
m_callback->setSamples(&m_samples[0], remainingSamples);
|
||||
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericSawTooth::stop()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericSawTooth::stop\n";
|
||||
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericSawTooth::uninitialize()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericSawTooth::uninitialize\n";
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
m_callback = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverGenericSawTooth::isConfigurable()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericSawTooth::isConfigurable\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CDriverGenericSawTooth::configure()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericSawTooth::configure\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "ovasIDriver.h"
|
||||
#include "../ovasCHeader.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CDriverGenericSawTooth
|
||||
* \author Yann Renard (INRIA)
|
||||
*/
|
||||
class CDriverGenericSawTooth final : public IDriver
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CDriverGenericSawTooth(IDriverContext& ctx);
|
||||
void release();
|
||||
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; }
|
||||
|
||||
protected:
|
||||
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
CHeader m_header;
|
||||
|
||||
uint32_t m_externalBlockSize = 0;
|
||||
std::vector<float> m_samples;
|
||||
|
||||
uint64_t m_nTotalSample = 0;
|
||||
uint64_t m_startTime = 0;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
#include "ovasCConfigurationGenericTimeSignal.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
CConfigurationGenericTimeSignal::CConfigurationGenericTimeSignal(IDriverContext& ctx, const char* gtkBuilderFilename)
|
||||
: CConfigurationBuilder(gtkBuilderFilename), m_driverCtx(ctx) {}
|
||||
|
||||
bool CConfigurationGenericTimeSignal::preConfigure() { return CConfigurationBuilder::preConfigure(); }
|
||||
|
||||
// normal header is filled (Subject ID, Age, Gender, channels, sampling frequency)
|
||||
bool CConfigurationGenericTimeSignal::postConfigure() { return CConfigurationBuilder::postConfigure(); }
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovasCConfigurationBuilder.h"
|
||||
#include "ovasIDriver.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CConfigurationGenericTimeSignal
|
||||
* \sa CDriverGenericTimeSignal
|
||||
*/
|
||||
class CConfigurationGenericTimeSignal final : public CConfigurationBuilder
|
||||
{
|
||||
public:
|
||||
CConfigurationGenericTimeSignal(IDriverContext& ctx, const char* gtkBuilderFilename);
|
||||
|
||||
bool preConfigure() override;
|
||||
bool postConfigure() override;
|
||||
|
||||
protected:
|
||||
IDriverContext& m_driverCtx;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
#include "ovasCDriverGenericTimeSignal.h"
|
||||
#include "../ovasCConfigurationBuilder.h"
|
||||
|
||||
#include "ovasCConfigurationGenericTimeSignal.h"
|
||||
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <system/ovCTime.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
CDriverGenericTimeSignal::CDriverGenericTimeSignal(IDriverContext& ctx)
|
||||
: IDriver(ctx), m_settings("AcquisitionServer_Driver_GenericTimeSignal", m_driverCtx.getConfigurationManager())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericTimeSignal::CDriverGenericTimeSignal\n";
|
||||
|
||||
m_header.setSamplingFrequency(512);
|
||||
m_header.setChannelCount(1);
|
||||
m_header.setChannelName(0, "Time(s)");
|
||||
m_header.setChannelUnits(0, OVTK_UNIT_Second, OVTK_FACTOR_Base);
|
||||
|
||||
m_settings.add("Header", &m_header);
|
||||
m_settings.load();
|
||||
}
|
||||
|
||||
void CDriverGenericTimeSignal::release()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericTimeSignal::release\n";
|
||||
delete this;
|
||||
}
|
||||
|
||||
const char* CDriverGenericTimeSignal::getName()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericTimeSignal::getName\n";
|
||||
return "Generic Time Signal";
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverGenericTimeSignal::initialize(const uint32_t /*nSamplePerSentBlock*/, IDriverCallback& callback)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericTimeSignal::initialize\n";
|
||||
if (m_driverCtx.isConnected() || !m_header.isChannelCountSet() || !m_header.isSamplingFrequencySet()) { return false; }
|
||||
|
||||
m_callback = &callback;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericTimeSignal::start()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericTimeSignal::start\n";
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
m_nTotalSample = 0;
|
||||
m_startTime = System::Time::zgetTime();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericTimeSignal::loop()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "CDriverGenericTimeSignal::loop\n";
|
||||
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return true; }
|
||||
|
||||
// Find out how many samples to send; note that we always just send 1 at a time with this driver
|
||||
const uint64_t now = System::Time::zgetTime();
|
||||
const uint64_t elapsed = now - m_startTime;
|
||||
const uint64_t neededSoFar = (m_header.getSamplingFrequency() * elapsed) >> 32;
|
||||
if (neededSoFar <= m_nTotalSample) { return true; } // Too early
|
||||
|
||||
const float timeNow = float(CTime(now).toSeconds());
|
||||
m_callback->setSamples(&timeNow, 1);
|
||||
m_nTotalSample++;
|
||||
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericTimeSignal::stop()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericTimeSignal::stop\n";
|
||||
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericTimeSignal::uninitialize()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericTimeSignal::uninitialize\n";
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
m_callback = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverGenericTimeSignal::isConfigurable()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericTimeSignal::isConfigurable\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverGenericTimeSignal::configure()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverGenericTimeSignal::configure\n";
|
||||
CConfigurationGenericTimeSignal config(m_driverCtx, Directories::getDataDir() + "/applications/acquisition-server/interface-Generic-TimeSignal.ui");
|
||||
if (!config.configure(m_header)) { return false; }
|
||||
m_settings.save();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "ovasIDriver.h"
|
||||
#include "../ovasCHeader.h"
|
||||
|
||||
#include "../ovasCSettingsHelper.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CDriverGenericTimeSignal
|
||||
* \author Jussi Lindgren (Inria)
|
||||
*
|
||||
* This driver may have some utility in debugging. For each sample, it returns the
|
||||
* current time as obtained from openvibe's System::Time:zgettime() converted to float seconds.
|
||||
*
|
||||
*/
|
||||
class CDriverGenericTimeSignal final : public IDriver
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CDriverGenericTimeSignal(IDriverContext& ctx);
|
||||
void release();
|
||||
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; }
|
||||
|
||||
protected:
|
||||
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
CHeader m_header;
|
||||
|
||||
SettingsHelper m_settings;
|
||||
|
||||
uint64_t m_nTotalSample = 0;
|
||||
uint64_t m_startTime = 0;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
%
|
||||
% A matlab example of sending data to OpenViBE via LabStreamingLayer. You need to have the LSL matlab stuff recursively on Matlab's Path.
|
||||
%
|
||||
% At the time of writing this, OpenViBE only supports numeric markers.
|
||||
%
|
||||
nChannels = 8;
|
||||
samplingFreq = 100;
|
||||
|
||||
disp('Loading LSL library...');
|
||||
lib = lsl_loadlib();
|
||||
|
||||
% make a new stream outlet
|
||||
disp('Creating a new signal stream ...');
|
||||
info = lsl_streaminfo(lib,'ACMEAmp','EEG',nChannels,samplingFreq,'cf_float32','abcdefgh');
|
||||
|
||||
disp('Opening a signal outlet...');
|
||||
outlet = lsl_outlet(info);
|
||||
|
||||
disp('Creating a new marker stream ...');
|
||||
markerInfo = lsl_streaminfo(lib,'ACMEMarkers','Markers',1,0,'cf_int32','myuniquesourceid23443');
|
||||
|
||||
disp('Opening a marker outlet...');
|
||||
markerOutlet = lsl_outlet(markerInfo);
|
||||
|
||||
disp('Sending data...');
|
||||
t=0;
|
||||
value = -1;
|
||||
while true
|
||||
|
||||
% If current time is even, sample is positive. When sample value changes to positive, send a marker.
|
||||
sendMarker = 0;
|
||||
if(mod(floor(t),2)==0)
|
||||
if(value~=1)
|
||||
sendMarker=1;
|
||||
end
|
||||
value = 1;
|
||||
else
|
||||
value = -1;
|
||||
end
|
||||
|
||||
% Push a sample
|
||||
outlet.push_sample(value*(1:nChannels),t);
|
||||
|
||||
% Push a marker?
|
||||
if(sendMarker>0)
|
||||
disp('push marker')
|
||||
markerOutlet.push_sample(777,t);
|
||||
end
|
||||
|
||||
% step time, wait
|
||||
t=t+(1/samplingFreq);
|
||||
pause(1/samplingFreq);
|
||||
end
|
||||
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
#if defined(TARGET_HAS_ThirdPartyLSL)
|
||||
|
||||
#include "ovasCConfigurationLabStreamingLayer.h"
|
||||
|
||||
#include <lsl_cpp.h>
|
||||
#include "ovasIHeader.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
CConfigurationLabStreamingLayer::CConfigurationLabStreamingLayer(IDriverContext& ctx, const char* gtkBuilderFilename, IHeader& header, bool& limitSpeed,
|
||||
CString& signalStream, CString& signalStreamID, CString& markerStream, CString& markerStreamID,
|
||||
uint32_t& fallbackSampling)
|
||||
: CConfigurationBuilder(gtkBuilderFilename), m_driverCtx(ctx), m_header(header), m_limitSpeed(limitSpeed), m_signalStream(signalStream),
|
||||
m_signalStreamID(signalStreamID), m_markerStream(markerStream), m_markerStreamID(markerStreamID), m_fallbackSampling(fallbackSampling) {}
|
||||
|
||||
bool CConfigurationLabStreamingLayer::preConfigure()
|
||||
{
|
||||
if (! CConfigurationBuilder::preConfigure()) { return false; }
|
||||
|
||||
GtkToggleButton* buttonSpeedLimit = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_limit_speed"));
|
||||
gtk_toggle_button_set_active(buttonSpeedLimit, m_limitSpeed ? TRUE : FALSE);
|
||||
|
||||
GtkComboBox* comboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_signal_stream"));
|
||||
if (!comboBox) { return false; }
|
||||
|
||||
GtkComboBox* markerComboBox = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_marker_stream"));
|
||||
if (!markerComboBox) { return false; }
|
||||
|
||||
GtkEntry* fallbackSamplingEntry = GTK_ENTRY(gtk_builder_get_object(m_builder, "entry_fallback_sampling_frequency"));
|
||||
if (!fallbackSamplingEntry) { return false; }
|
||||
|
||||
m_signalIdxs.clear();
|
||||
m_markerIdxs.clear();
|
||||
|
||||
// Allow operation without a marker stream
|
||||
gtk_combo_box_append_text(markerComboBox, "None");
|
||||
gtk_combo_box_set_active(markerComboBox, 0);
|
||||
m_markerIdxs.push_back(-1);
|
||||
|
||||
m_streams = lsl::resolve_streams(1.0);
|
||||
|
||||
// See if any of the streams can be interpreted as signal or marker
|
||||
uint32_t nStreams = 0;
|
||||
uint32_t nMarkerStreams = 0;
|
||||
bool exactSignalMatch = false; // If we can match both name and ID, stop at that. Otherwise we auto-accept the 'name only' match.
|
||||
bool exactMarkerMatch = false;
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Discovered " << m_streams.size() << " streams in total\n";
|
||||
|
||||
for (size_t i = 0; i < m_streams.size(); ++i)
|
||||
{
|
||||
if (m_streams[i].channel_format() == lsl::cf_float32)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << m_streams[i].name() << " / " << m_streams[i].source_id();
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << i << ". Discovered signal stream " << m_streams[i].name() << ", id " << ss.str() << "\n";
|
||||
|
||||
gtk_combo_box_append_text(comboBox, ss.str().c_str());
|
||||
m_signalIdxs.push_back(static_cast<int>(i));
|
||||
if ((m_signalStream == CString(m_streams[i].name().c_str()) && !exactSignalMatch) || !nStreams)
|
||||
{
|
||||
if (m_signalStreamID == CString(m_streams[i].source_id().c_str())) { exactSignalMatch = true; }
|
||||
gtk_combo_box_set_active(comboBox, nStreams);
|
||||
}
|
||||
nStreams++;
|
||||
}
|
||||
else if (m_streams[i].channel_format() == lsl::cf_int32)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << m_streams[i].name().c_str() << " / " << m_streams[i].source_id().c_str();
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << i << ". Discovered marker stream " << m_streams[i].name() << ", id " << ss.str() << "\n";
|
||||
|
||||
gtk_combo_box_append_text(markerComboBox, ss.str().c_str());
|
||||
m_markerIdxs.push_back(i);
|
||||
if ((m_markerStream == CString(m_streams[i].name().c_str()) && !exactMarkerMatch) || !nMarkerStreams)
|
||||
{
|
||||
if (m_markerStreamID == CString(m_streams[i].source_id().c_str()))
|
||||
{
|
||||
// If we can match both name and ID, stop at that. Otherwise we accept the 'name only' match.
|
||||
exactMarkerMatch = true;
|
||||
}
|
||||
gtk_combo_box_set_active(markerComboBox, nMarkerStreams + 1);
|
||||
}
|
||||
nMarkerStreams++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only float and int are currently supported for signals and markers respectively
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << i << ". Discovered stream with channel format " << m_streams[i].channel_format()
|
||||
<< " of stream [" << m_streams[i].name() << "] which is not supported, skipped.\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (m_fallbackSampling == 0) { gtk_entry_set_text(fallbackSamplingEntry, ""); }
|
||||
else
|
||||
{
|
||||
const std::string buffer = std::to_string(m_fallbackSampling);
|
||||
gtk_entry_set_text(fallbackSamplingEntry, buffer.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CConfigurationLabStreamingLayer::postConfigure()
|
||||
{
|
||||
if (m_applyConfig)
|
||||
{
|
||||
GtkToggleButton* buttonSpeedLimit = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_limit_speed"));
|
||||
m_limitSpeed = gtk_toggle_button_get_active(buttonSpeedLimit) ? true : false;
|
||||
|
||||
// Retrieve signal stream info
|
||||
GtkComboBox* comboBoxSignal = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_signal_stream"));
|
||||
if (!comboBoxSignal)
|
||||
{
|
||||
m_applyConfig = false;
|
||||
CConfigurationBuilder::postConfigure(); // close window etc
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_signalIdxs.empty())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "LSL: Cannot proceed without a signal stream.\n";
|
||||
m_applyConfig = false;
|
||||
CConfigurationBuilder::postConfigure(); // close window etc
|
||||
return false;
|
||||
}
|
||||
|
||||
const int signalIdx = m_signalIdxs[gtk_combo_box_get_active(comboBoxSignal)];
|
||||
|
||||
m_signalStream = m_streams[signalIdx].name().c_str();
|
||||
m_signalStreamID = m_streams[signalIdx].source_id().c_str();
|
||||
|
||||
GtkComboBox* comboBoxMarker = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_marker_stream"));
|
||||
if (!comboBoxMarker)
|
||||
{
|
||||
m_applyConfig = false;
|
||||
CConfigurationBuilder::postConfigure(); // close window etc
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retrieve marker stream info
|
||||
const int markerIdx = m_markerIdxs[gtk_combo_box_get_active(comboBoxMarker)];
|
||||
if (markerIdx >= 0)
|
||||
{
|
||||
m_markerStream = m_streams[markerIdx].name().c_str();
|
||||
m_markerStreamID = m_streams[markerIdx].source_id().c_str();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_markerStream = "None";
|
||||
m_markerStreamID = "";
|
||||
}
|
||||
|
||||
// Retrieve fallback sampling rate
|
||||
GtkEntry* entry = GTK_ENTRY(gtk_builder_get_object(m_builder, "entry_fallback_sampling_frequency"));
|
||||
uint32_t sampling = 0;
|
||||
if (sscanf(gtk_entry_get_text(entry), "%u", &sampling) == 1) { m_fallbackSampling = uint32_t(sampling); }
|
||||
else { m_fallbackSampling = 0; }
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Binding to [" << m_signalStream << ", id " << m_signalStreamID << "] and [" << m_markerStream
|
||||
<< ", id " << m_markerStreamID << "]\n";
|
||||
}
|
||||
|
||||
if (! CConfigurationBuilder::postConfigure()
|
||||
) { return false; } // normal header is filled (Subject ID, Age, Gender, channels, sampling frequency), ressources are realesed
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_HAS_ThirdPartyLSL
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(TARGET_HAS_ThirdPartyLSL)
|
||||
|
||||
#include "../ovasCConfigurationBuilder.h"
|
||||
#include "ovasIDriver.h"
|
||||
#include "ovasIHeader.h"
|
||||
|
||||
#include <lsl_cpp.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CConfigurationLabStreamingLayer
|
||||
* \author Jussi T. Lindgren / Inria
|
||||
* \date Wed Oct 15 09:41:18 2014
|
||||
* \brief The CConfigurationLabStreamingLayer handles the configuration dialog specific to the LabStreamingLayer (LSL) device.
|
||||
*
|
||||
* \sa CDriverLabStreamingLayer
|
||||
*/
|
||||
class CConfigurationLabStreamingLayer final : public CConfigurationBuilder
|
||||
{
|
||||
public:
|
||||
|
||||
CConfigurationLabStreamingLayer(IDriverContext& ctx, const char* gtkBuilderFilename, IHeader& header, bool& limitSpeed,
|
||||
CString& signalStream, CString& signalStreamID, CString& markerStream,
|
||||
CString& markerStreamID, uint32_t& fallbackSampling);
|
||||
|
||||
bool preConfigure() override;
|
||||
bool postConfigure() override;
|
||||
|
||||
protected:
|
||||
IDriverContext& m_driverCtx;
|
||||
|
||||
private:
|
||||
IHeader& m_header;
|
||||
|
||||
bool& m_limitSpeed;
|
||||
CString& m_signalStream;
|
||||
CString& m_signalStreamID;
|
||||
CString& m_markerStream;
|
||||
CString& m_markerStreamID;
|
||||
uint32_t& m_fallbackSampling;
|
||||
|
||||
std::vector<lsl::stream_info> m_streams;
|
||||
std::vector<int> m_signalIdxs;
|
||||
std::vector<int> m_markerIdxs;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyLSL
|
||||
+552
@@ -0,0 +1,552 @@
|
||||
#if defined(TARGET_HAS_ThirdPartyLSL)
|
||||
|
||||
/*
|
||||
*
|
||||
* Notes: This code should be kept compatible with changes to LSL Output plugin in OpenViBE Acquisition Server,
|
||||
* and LSL Export box in Designer.
|
||||
*
|
||||
* This driver makes a few assumptions:
|
||||
*
|
||||
* Signal streams
|
||||
* - are float
|
||||
* - dense, i.e. there are no dropped or extra samples in them
|
||||
* - the driver fills a sample block consequently until either the block has been filled or
|
||||
* time runs out. In case of the latter, the chunk is padded with NaNs.
|
||||
* Markers
|
||||
* - are int
|
||||
* - not dense
|
||||
* - markers are retimed wrt the current signal chunk
|
||||
*
|
||||
* Other notes: Due to network delays, it may be better to disable drift correction or
|
||||
* set the drift tolerance to high. This is because
|
||||
* 1) Signals are assumed dense, i.e. LSL pull results in samples stamped t,t+1,t+2,...
|
||||
* 2) However, Acquisition Server works in real time, so if there is a network delay between
|
||||
* the samples stamped t and t+1, this delay does not indicate a delay in the original signal
|
||||
* and it might not be correct to pad the corresponding signal block during the delay.
|
||||
*
|
||||
* Todo. It might make sense to improve this driver in the future to take into account the LSL
|
||||
* stamps when constructing each signal block. This is currently not done.
|
||||
*
|
||||
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include <limits> // for NaN
|
||||
|
||||
#include "ovasCDriverLabStreamingLayer.h"
|
||||
#include "ovasCConfigurationLabStreamingLayer.h"
|
||||
|
||||
#include <system/ovCTime.h>
|
||||
|
||||
#include <lsl_cpp.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
// In seconds
|
||||
static const double LSL_SAMPLING_ESTIMATATION_DURATION = 2.0;
|
||||
static const double LSL_RESOLVE_TIME_OUT = 2.0;
|
||||
static const double LSL_OPEN_TIME_OUT = 2.0;
|
||||
static const double LSL_READ_TIME_OUT = 2.0;
|
||||
|
||||
static uint32_t getClosestMultipleOf25(const uint32_t value) { return uint32_t(((value + 12) / 25) * 25); }
|
||||
|
||||
static uint32_t getClosestPowerOf2(const uint32_t value)
|
||||
{
|
||||
uint32_t bitShiftCount = 0;
|
||||
while ((1LL << bitShiftCount) < value) { bitShiftCount++; }
|
||||
|
||||
if (bitShiftCount > 0)
|
||||
{
|
||||
const uint32_t greater = uint32_t(1LL << bitShiftCount);
|
||||
const uint32_t lesser = greater >> 1;
|
||||
if (greater - value < value - lesser) { return greater; }
|
||||
return lesser;
|
||||
}
|
||||
|
||||
return uint32_t(1LL << bitShiftCount);
|
||||
}
|
||||
|
||||
static uint32_t getSmartFallbackSamplingRateEstimate(const uint32_t lslSampling, const bool roundToPowerOfTwoAndMultiple25 = true)
|
||||
{
|
||||
if (roundToPowerOfTwoAndMultiple25)
|
||||
{
|
||||
const uint32_t closestMultipleOf25 = getClosestMultipleOf25(lslSampling);
|
||||
const uint32_t closestPowerOfTwo = getClosestPowerOf2(lslSampling);
|
||||
const uint32_t closestMultipleOf25Diff = uint32_t(std::abs(int(closestMultipleOf25) - int(lslSampling)));
|
||||
const uint32_t closestPowerOfTwoDiff = uint32_t(std::abs(int(closestPowerOfTwo) - int(lslSampling)));
|
||||
if (closestMultipleOf25Diff < closestPowerOfTwoDiff) { return closestMultipleOf25; }
|
||||
return closestPowerOfTwo;
|
||||
}
|
||||
return lslSampling;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
CDriverLabStreamingLayer::CDriverLabStreamingLayer(IDriverContext& ctx)
|
||||
: IDriver(ctx), m_settings("AcquisitionServer_Driver_LabStreamingLayer", m_driverCtx.getConfigurationManager())
|
||||
{
|
||||
// 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("LimitSpeed", &m_limitSpeed);
|
||||
m_settings.add("SignalStreamName", &m_sSignalStream);
|
||||
m_settings.add("SignalStreamID", &m_sSignalStreamID);
|
||||
m_settings.add("MarkerStreamName", &m_sMarkerStream);
|
||||
m_settings.add("MarkerStreamID", &m_sMarkerStreamID);
|
||||
m_settings.add("FallbackSamplingRate", &m_fallbackSampling);
|
||||
m_settings.load();
|
||||
|
||||
/*
|
||||
for (uint32_t i = 1; i < 260; ++i)
|
||||
{
|
||||
ctx.getLogManager() << Kernel::LogLevel_Error << i << " --> " << getSmartFallbackSamplingRateEstimate(i) << "\n";
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
CDriverLabStreamingLayer::~CDriverLabStreamingLayer() {}
|
||||
|
||||
const char* CDriverLabStreamingLayer::getName() { return "LabStreamingLayer (LSL)"; }
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverLabStreamingLayer::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
|
||||
{
|
||||
if (m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
// Find the signal stream
|
||||
const std::vector<lsl::stream_info> streams = lsl::resolve_stream("name", m_sSignalStream.toASCIIString(), 1, LSL_RESOLVE_TIME_OUT);
|
||||
if (streams.empty())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error resolving signal stream with name [" << m_sSignalStream.toASCIIString() << "]\n";
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < streams.size(); ++i)
|
||||
{
|
||||
m_oSignalStream = streams[i];
|
||||
if (streams[i].source_id() == std::string(m_sSignalStreamID.toASCIIString()))
|
||||
{
|
||||
// This is the best one
|
||||
break;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Finally resolved signal stream to " << m_oSignalStream.name() << ", id " <<
|
||||
m_oSignalStream.source_id().c_str() << "\n";
|
||||
}
|
||||
|
||||
// Find the marker stream
|
||||
if (m_sMarkerStream != CString("None"))
|
||||
{
|
||||
const std::vector<lsl::stream_info> markerStreams = lsl::resolve_stream("name", m_sMarkerStream.toASCIIString(), 1, LSL_RESOLVE_TIME_OUT);
|
||||
if (markerStreams.empty())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error resolving marker stream with name [" << m_sMarkerStream.toASCIIString() << "]\n";
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < markerStreams.size(); ++i)
|
||||
{
|
||||
m_oMarkerStream = markerStreams[i];
|
||||
if (markerStreams[i].source_id() == std::string(m_sMarkerStreamID.toASCIIString()))
|
||||
{
|
||||
// This is the best one
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Finally resolved marker stream to " << m_oMarkerStream.name() << ", id " <<
|
||||
m_oMarkerStream.source_id().c_str() << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
// We do not have a marker stream. This is ok.
|
||||
}
|
||||
|
||||
// Get the channel names. We open a temporary inlet for this, it will be closed after going out of scope. The actual inlet will be opened in start().
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Polling channel names\n";
|
||||
lsl::stream_inlet tmpInlet(m_oSignalStream, 360, 0, false);
|
||||
try { tmpInlet.open_stream(LSL_OPEN_TIME_OUT); }
|
||||
catch (...)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to open signal stream with name [" << m_oSignalStream.name() <<
|
||||
"] for polling channel names\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
lsl::stream_info fullInfo;
|
||||
try { fullInfo = tmpInlet.info(LSL_READ_TIME_OUT); }
|
||||
catch (...)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Timeout reading full stream info for [" << m_oSignalStream.name() <<
|
||||
"] for polling channel names\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now sets channel count and names
|
||||
m_header.setChannelCount(m_oSignalStream.channel_count());
|
||||
|
||||
const lsl::xml_element channels = fullInfo.desc().child("channels");
|
||||
lsl::xml_element channel = channels.child("channel");
|
||||
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
|
||||
{
|
||||
const char* label = channel.child_value("label");
|
||||
|
||||
if (label) { m_header.setChannelName(i, label); }
|
||||
|
||||
channel = channel.next_sibling("channel");
|
||||
}
|
||||
|
||||
// Buffer to store a single sample
|
||||
m_buffer = new float[m_header.getChannelCount()];
|
||||
if (!m_buffer)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Memory allocation problem\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Buffer to store the signal chunk
|
||||
m_sample = new float[m_header.getChannelCount() * nSamplePerSentBlock];
|
||||
if (!m_sample)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Memory allocation problem\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now sets sampling rate
|
||||
|
||||
uint32_t sampling = uint32_t(m_oSignalStream.nominal_srate());
|
||||
if (sampling == 0)
|
||||
{
|
||||
// Check GUI fallback
|
||||
if (m_fallbackSampling != 0)
|
||||
{
|
||||
sampling = m_fallbackSampling;
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "LSL sampling rate is not defined, falls back to [" << sampling <<
|
||||
"] - You can change this in the driver settings\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Autodetection was requested, let`s go for a round
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "LSL sampling rate is not defined, falls back to [" << CString("Autodetection") <<
|
||||
"] - You can change this in the driver settings\n";
|
||||
|
||||
try
|
||||
{
|
||||
tmpInlet.open_stream(LSL_OPEN_TIME_OUT);
|
||||
|
||||
// First drop all available samples
|
||||
while (tmpInlet.samples_available() != 0) { tmpInlet.pull_sample(m_buffer, m_header.getChannelCount(), LSL_READ_TIME_OUT); }
|
||||
|
||||
// Now capture incoming samples over a period of 2 secs
|
||||
uint32_t nSample = 0;
|
||||
const double startCaptureTime = tmpInlet.pull_sample(m_buffer, m_header.getChannelCount(), LSL_READ_TIME_OUT);
|
||||
double currentCaptureTime = startCaptureTime;
|
||||
while (currentCaptureTime != 0.0 && currentCaptureTime - startCaptureTime < LSL_SAMPLING_ESTIMATATION_DURATION)
|
||||
{
|
||||
const double pulledSampleTime = tmpInlet.pull_sample(m_buffer, m_header.getChannelCount(), LSL_READ_TIME_OUT);
|
||||
if (pulledSampleTime != 0)
|
||||
{
|
||||
nSample++;
|
||||
currentCaptureTime = pulledSampleTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error <<
|
||||
"Timed out while receiving samples from LSL stream, avoiding auto detection.\n";
|
||||
delete[] m_buffer;
|
||||
delete[] m_sample;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally set estimated sampling rate
|
||||
const double duration = currentCaptureTime - startCaptureTime;
|
||||
const uint32_t lslSampling = uint32_t(nSample / duration);
|
||||
const uint32_t smartLSLSampling = getSmartFallbackSamplingRateEstimate(lslSampling);
|
||||
|
||||
if (smartLSLSampling != lslSampling)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Acquired " << nSample << " on a duration of " << duration <<
|
||||
" seconds, sampling rate might be approx " << lslSampling << " hz (adjusted to " << smartLSLSampling <<
|
||||
" hz to conform with typical EEG sampling rates).\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Acquired " << nSample << " on a duration of " << duration <<
|
||||
" seconds, sampling rate might be approx " << smartLSLSampling << " hz.\n";
|
||||
}
|
||||
|
||||
sampling = smartLSLSampling;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to detected actual LSL sampling rate\n";
|
||||
delete [] m_buffer;
|
||||
delete [] m_sample;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_header.setSamplingFrequency(sampling);
|
||||
|
||||
if (sampling != m_oSignalStream.nominal_srate())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info
|
||||
<< "Opened an LSL stream with " << m_oSignalStream.channel_count()
|
||||
<< " channels and a nominal rate of " << m_oSignalStream.nominal_srate() << " hz.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info
|
||||
<< "Opened an LSL stream with " << m_oSignalStream.channel_count()
|
||||
<< " channels and a nominal rate of " << m_oSignalStream.nominal_srate() << " hz"
|
||||
<< " adjusted to " << sampling << " hz.\n";
|
||||
}
|
||||
|
||||
// Stores parameters
|
||||
m_callback = &callback;
|
||||
m_nSamplePerSentBlock = nSamplePerSentBlock;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverLabStreamingLayer::start()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
m_pSignalInlet = new lsl::stream_inlet(m_oSignalStream, 360, 0, false);
|
||||
if (!m_pSignalInlet)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error getting signal inlet for [" << m_oSignalStream.name() << "]\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
try { m_pSignalInlet->open_stream(LSL_OPEN_TIME_OUT); }
|
||||
catch (...)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to open signal stream with name [" << m_oSignalStream.name() << "]\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_sMarkerStream != CString("None"))
|
||||
{
|
||||
m_pMarkerInlet = new lsl::stream_inlet(m_oMarkerStream, 360, 0, false);
|
||||
if (!m_pMarkerInlet)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error getting marker inlet for [" << m_oMarkerStream.name() << "]\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
try { m_pMarkerInlet->open_stream(LSL_OPEN_TIME_OUT); }
|
||||
catch (...)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to open marker stream with name [" << m_oSignalStream.name() << "]\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_startTime = System::Time::zgetTime();
|
||||
m_nSample = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverLabStreamingLayer::loop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected()) { return false; }
|
||||
if (!m_driverCtx.isStarted()) { return true; }
|
||||
|
||||
const size_t nChannels = m_header.getChannelCount();
|
||||
|
||||
bool timeOut = false;
|
||||
uint32_t timeOutAt = 0;
|
||||
bool blockStartTimeSet = false;
|
||||
double blockStartTime = 0;
|
||||
|
||||
// receive signal from the stream
|
||||
for (uint32_t i = 0; i < m_nSamplePerSentBlock; ++i)
|
||||
{
|
||||
double captureTime;
|
||||
try { captureTime = m_pSignalInlet->pull_sample(m_buffer, nChannels, LSL_READ_TIME_OUT); }
|
||||
catch (...)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to get signal sample from [" << m_oSignalStream.name() << "]\n";
|
||||
return false;
|
||||
}
|
||||
if (captureTime != 0)
|
||||
{
|
||||
if (!blockStartTimeSet)
|
||||
{
|
||||
blockStartTimeSet = true;
|
||||
blockStartTime = captureTime;
|
||||
//m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "First cap time is " << captureTime << "\n";
|
||||
}
|
||||
|
||||
// Sample ok, fill
|
||||
for (uint32_t j = 0; j < nChannels; ++j) { m_sample[j * m_nSamplePerSentBlock + i] = m_buffer[j]; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// Timeout
|
||||
timeOutAt = i;
|
||||
timeOut = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (timeOut)
|
||||
{
|
||||
// We fill the rest of the buffer with NaNs
|
||||
for (uint32_t i = timeOutAt; i < m_nSamplePerSentBlock; ++i)
|
||||
{
|
||||
for (uint32_t j = 0; j < nChannels; ++j) { m_sample[j * m_nSamplePerSentBlock + i] = std::numeric_limits<float>::quiet_NaN(); }
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Timeout reading sample from " << timeOutAt << ", filled rest of block with NaN\n";
|
||||
}
|
||||
|
||||
if (m_limitSpeed)
|
||||
{
|
||||
// If we were faster than what the AS expects, sleep.
|
||||
// n.b. This sleep may not be accurate on Windows (it may oversleep)
|
||||
const uint64_t timeNow = System::Time::zgetTime() - m_startTime;
|
||||
const uint64_t timeLimitForBuffer = CTime(m_header.getSamplingFrequency(), m_nSample + m_nSamplePerSentBlock).time();
|
||||
|
||||
if (timeNow < timeLimitForBuffer)
|
||||
{
|
||||
const uint64_t timeToSleep = timeLimitForBuffer - timeNow;
|
||||
|
||||
// std::cout << "PostNap " << CTime(timeToSleep).toSeconds()*1000 << "ms at " << m_nSample+m_nSamplePerSentBlock << "\n";
|
||||
|
||||
System::Time::zsleep(timeToSleep);
|
||||
}
|
||||
}
|
||||
|
||||
m_nSample += m_nSamplePerSentBlock;
|
||||
|
||||
m_callback->setSamples(m_sample);
|
||||
|
||||
// receive and pass markers. Markers are timed wrt the beginning of the signal block.
|
||||
CStimulationSet stimulationSet;
|
||||
if (m_pMarkerInlet)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int marker;
|
||||
double captureTime;
|
||||
try
|
||||
{
|
||||
captureTime = m_pMarkerInlet->pull_sample(&marker, 1, 0); // timeout is 0 here on purpose, either we have markers or not
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to get marker from [" << m_oMarkerStream.name() << "]\n";
|
||||
return false;
|
||||
}
|
||||
if (captureTime == 0)
|
||||
{
|
||||
// no more markers available at the moment
|
||||
break;
|
||||
}
|
||||
// double correction = m_pMarkerInlet->time_correction();
|
||||
// double stimTime = captureTime + correction - firstCaptureTime;
|
||||
|
||||
// For openvibe, we need to set the stimulus time relative to the start of the chunk
|
||||
static bool warningPrinted = false;
|
||||
if (captureTime < blockStartTime && !warningPrinted)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Received marker from LSL date from before the start of the current chunk."
|
||||
" Adjusting stamp to start of current chunk. Will not warn again.\n";
|
||||
warningPrinted = true;
|
||||
}
|
||||
const double stimTime = (captureTime > blockStartTime ? captureTime - blockStartTime : 0);
|
||||
|
||||
// m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Got a marker " << marker << " at " << captureTime << " -> "
|
||||
// << stimTime << "s."
|
||||
// << "\n";
|
||||
|
||||
stimulationSet.appendStimulation(uint64_t(marker), CTime(stimTime).time(), 0);
|
||||
// std::cout << "date " << stimTime << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
m_callback->setStimulationSet(stimulationSet);
|
||||
|
||||
// LSL is not forcing the sample stream to confirm to the nominal sample rate. Hence, data may be incoming
|
||||
// with slower or faster speed than implied by the rate (a little like reading from a file). In some
|
||||
// cases it may be meaningful to disable the following drift correction from the AS settings.
|
||||
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverLabStreamingLayer::stop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
if (m_pSignalInlet)
|
||||
{
|
||||
m_pSignalInlet->close_stream();
|
||||
|
||||
delete m_pSignalInlet;
|
||||
m_pSignalInlet = nullptr;
|
||||
}
|
||||
|
||||
if (m_pMarkerInlet)
|
||||
{
|
||||
m_pMarkerInlet->close_stream();
|
||||
|
||||
delete m_pMarkerInlet;
|
||||
m_pMarkerInlet = nullptr;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverLabStreamingLayer::uninitialize()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
if (m_buffer)
|
||||
{
|
||||
delete[] m_buffer;
|
||||
m_buffer = nullptr;
|
||||
}
|
||||
|
||||
if (m_sample)
|
||||
{
|
||||
delete[] m_sample;
|
||||
m_sample = nullptr;
|
||||
}
|
||||
|
||||
m_callback = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
bool CDriverLabStreamingLayer::isConfigurable()
|
||||
{
|
||||
return true; // change to false if your device is not configurable
|
||||
}
|
||||
|
||||
bool CDriverLabStreamingLayer::configure()
|
||||
{
|
||||
// Change this line if you need to specify some references to your driver attribute that need configuration, e.g. the connection ID.
|
||||
CConfigurationLabStreamingLayer config(m_driverCtx, Directories::getDataDir() + "/applications/acquisition-server/interface-LabStreamingLayer.ui",
|
||||
m_header, m_limitSpeed, m_sSignalStream, m_sSignalStreamID, m_sMarkerStream, m_sMarkerStreamID, m_fallbackSampling);
|
||||
|
||||
if (!config.configure(m_header)) { return false; }
|
||||
m_settings.save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_HAS_ThirdPartyLSL
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(TARGET_HAS_ThirdPartyLSL)
|
||||
|
||||
#include "ovasIDriver.h"
|
||||
#include "../ovasCHeader.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
|
||||
#include <lsl_cpp.h>
|
||||
|
||||
#include "../ovasCSettingsHelper.h"
|
||||
#include "../ovasCSettingsHelperOperators.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CDriverLabStreamingLayer
|
||||
* \author Jussi T. Lindgren / Inria
|
||||
* \date Wed Oct 15 09:41:18 2014
|
||||
* \brief The CDriverLabStreamingLayer allows the acquisition server to acquire data from a LabStreamingLayer (LSL) device.
|
||||
*
|
||||
* \sa CConfigurationLabStreamingLayer
|
||||
*/
|
||||
class CDriverLabStreamingLayer final : public IDriver
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CDriverLabStreamingLayer(IDriverContext& ctx);
|
||||
~CDriverLabStreamingLayer() 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 flag == EDriverFlag::IsUnstable; }
|
||||
|
||||
protected:
|
||||
|
||||
SettingsHelper m_settings;
|
||||
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
|
||||
CHeader m_header;
|
||||
|
||||
private:
|
||||
|
||||
uint32_t m_nSamplePerSentBlock = 0;
|
||||
uint32_t m_fallbackSampling = 0;
|
||||
float* m_sample = nullptr;
|
||||
float* m_buffer = nullptr;
|
||||
|
||||
uint64_t m_startTime = 0;
|
||||
uint64_t m_nSample = 0;
|
||||
|
||||
lsl::stream_info m_oSignalStream;
|
||||
lsl::stream_inlet* m_pSignalInlet = nullptr;
|
||||
|
||||
lsl::stream_info m_oMarkerStream;
|
||||
lsl::stream_inlet* m_pMarkerInlet = nullptr;
|
||||
|
||||
bool m_limitSpeed = false;
|
||||
CString m_sSignalStream;
|
||||
CString m_sSignalStreamID;
|
||||
CString m_sMarkerStream;
|
||||
CString m_sMarkerStreamID;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyLSL
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
#if defined(TARGET_HAS_ThirdPartyMCS)
|
||||
|
||||
#include "ovasCConfigurationMCSNVXDriver.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
/*_________________________________________________
|
||||
|
||||
Insert callback to specific widget here
|
||||
Example with a button that launch a calibration of the device:
|
||||
|
||||
//Callback connected to a dedicated gtk button:
|
||||
static void button_calibrate_pressed_cb(GtkButton* button, void* data)
|
||||
{
|
||||
CConfigurationMKSNVXDriver* config=static_cast<CConfigurationMKSNVXDriver*>(data);
|
||||
config->buttonCalibratePressedCB();
|
||||
}
|
||||
|
||||
//Callback actually called:
|
||||
void CConfigurationGTecGUSBamp::buttonCalibratePressedCB()
|
||||
{
|
||||
// Connect to the hardware, ask for calibration, verify the return code, etc.
|
||||
}
|
||||
_________________________________________________*/
|
||||
|
||||
// If you added more reference attribute, initialize them here
|
||||
CConfigurationMKSNVXDriver::CConfigurationMKSNVXDriver(IDriverContext& ctx, const char* gtkBuilderFilename, uint32_t& dataMode, bool& auxChannels)
|
||||
: CConfigurationBuilder(gtkBuilderFilename), dataMode_(dataMode), showAuxChannels_(auxChannels), m_driverCtx(ctx) {}
|
||||
|
||||
bool CConfigurationMKSNVXDriver::preConfigure()
|
||||
{
|
||||
if (! CConfigurationBuilder::preConfigure()) { return false; }
|
||||
|
||||
// Connect here all callbacks
|
||||
// Example:
|
||||
// g_signal_connect(gtk_builder_get_object(m_builder, "button_calibrate"), "pressed", G_CALLBACK(button_calibrate_pressed_cb), this);
|
||||
|
||||
// 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.
|
||||
GtkComboBox* comboDataMode = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_mode"));
|
||||
gtk_combo_box_set_active(comboDataMode, dataMode_);
|
||||
GtkToggleButton* toggleShowAuxChannels = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_show_aux_channels"));
|
||||
gtk_toggle_button_set_active(toggleShowAuxChannels, showAuxChannels_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CConfigurationMKSNVXDriver::postConfigure()
|
||||
{
|
||||
if (m_applyConfig)
|
||||
{
|
||||
// If the user pressed the "apply" button, you need to save the changes made in the configuration.
|
||||
// For example, you can save the connection ID of the selected device:
|
||||
// m_connectionID = <value-from-gtk-widget>
|
||||
GtkComboBox* comboDataMode = GTK_COMBO_BOX(gtk_builder_get_object(m_builder, "combobox_mode"));
|
||||
dataMode_ = uint32_t(gtk_combo_box_get_active(comboDataMode));
|
||||
GtkToggleButton* toggleShowAuxChannels = GTK_TOGGLE_BUTTON(gtk_builder_get_object(m_builder, "checkbutton_show_aux_channels"));
|
||||
showAuxChannels_ = (gtk_toggle_button_get_active(toggleShowAuxChannels) > 0);
|
||||
}
|
||||
|
||||
// normal header is filled (Subject ID, Age, Gender, channels, sampling frequency), ressources are realesed
|
||||
if (! CConfigurationBuilder::postConfigure()) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../ovasCConfigurationBuilder.h"
|
||||
#include "ovasIDriver.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CConfigurationMKSNVXDriver
|
||||
* \author mkochetkov (MKS)
|
||||
* \date Tue Jan 21 23:21:03 2014
|
||||
* \brief The CConfigurationMKSNVXDriver handles the configuration dialog specific to the MKSNVXDriver device.
|
||||
*
|
||||
* TODO: details
|
||||
*
|
||||
* \sa CDriverMKSNVXDriver
|
||||
*/
|
||||
class CConfigurationMKSNVXDriver final : public CConfigurationBuilder
|
||||
{
|
||||
uint32_t& dataMode_;
|
||||
bool& showAuxChannels_;
|
||||
protected:
|
||||
IDriverContext& m_driverCtx;
|
||||
public:
|
||||
// you may have to add to your constructor some reference parameters
|
||||
// for example, a connection ID:
|
||||
//CConfigurationMKSNVXDriver(IDriverContext& ctx, const char* gtkBuilderFilename, uint32_t& rConnectionId);
|
||||
CConfigurationMKSNVXDriver(IDriverContext& ctx, const char* gtkBuilderFilename, uint32_t& dataMode, bool& auxChannels);
|
||||
|
||||
bool preConfigure() override;
|
||||
bool postConfigure() override;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
+482
@@ -0,0 +1,482 @@
|
||||
#if defined(TARGET_HAS_ThirdPartyMCS)
|
||||
|
||||
#include "ovasCDriverMCSNVXDriver.h"
|
||||
#include "ovasCConfigurationMCSNVXDriver.h"
|
||||
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
void setNVXDataSettinsDefaults(t_NVXDataSettins& ds)
|
||||
{
|
||||
ds.DataRate = 2;
|
||||
for (size_t i = 0; i < NVX_SELECT_CHANNELS_COUNT; ++i)
|
||||
{
|
||||
ds.NVXChannelsSelect.MainChannels[i] = short(i);
|
||||
ds.NVXChannelsSelect.DiffChannels[i] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
CDriverMKSNVXDriver::CDriverMKSNVXDriver(IDriverContext& ctx)
|
||||
: IDriver(ctx), nvxDataModel_(NVX_DM_NORMAL)
|
||||
, m_settings("AcquisitionServer_Driver_MKSNVXDriver", m_driverCtx.getConfigurationManager())
|
||||
{
|
||||
setNVXDataSettinsDefaults(nvxDataSettings_);
|
||||
m_header.setSamplingFrequency(10000);
|
||||
m_header.setChannelCount(maxNumberOfChannels);
|
||||
|
||||
// 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("dataMode", &dataMode_);
|
||||
m_settings.add("showAuxChannels", &showAuxChannels_);
|
||||
|
||||
m_settings.load();
|
||||
}
|
||||
|
||||
CDriverMKSNVXDriver::~CDriverMKSNVXDriver() {}
|
||||
|
||||
const char* CDriverMKSNVXDriver::getName() { return "MCS NVX amplifier"; }
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverMKSNVXDriver::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
|
||||
{
|
||||
if (m_driverCtx.isConnected()) return false;
|
||||
if (!m_header.isChannelCountSet() || !m_header.isSamplingFrequencySet()) return false;
|
||||
|
||||
static const char* configuration = "<Configuration version=\"100\"></Configuration>";
|
||||
if (NVXAPIInit(configuration) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Cannot load NVX library.\n";
|
||||
return false;
|
||||
}
|
||||
// ...
|
||||
// initialize hardware and get
|
||||
// available header information
|
||||
// from it
|
||||
// Using for example the connection ID provided by the configuration (m_connectionID)
|
||||
// ...
|
||||
const size_t deviceCount = NVXGetCount();
|
||||
if (deviceCount != 1)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << (deviceCount == 0 ? "No MKS device found.\n" : "Only one MKS device is supported.\n");
|
||||
return false;
|
||||
}
|
||||
nvxDeviceId_ = NVXGetId(0);
|
||||
if (NVXGetInformation(nvxDeviceId_, &nvxInfo_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXGetInformation error.\n";
|
||||
return false;
|
||||
}
|
||||
uint32_t realChannelsCount = 0;
|
||||
switch (nvxInfo_.Model)
|
||||
{
|
||||
case NVX_MODEL_16: realChannelsCount = showAuxChannels_ ? 16 : 10;
|
||||
break;
|
||||
case NVX_MODEL_24: realChannelsCount = 24;
|
||||
break;
|
||||
case NVX_MODEL_36: realChannelsCount = showAuxChannels_ ? 36 : 32;
|
||||
break;
|
||||
case NVX_MODEL_52: realChannelsCount = showAuxChannels_ ? 52 : 48;
|
||||
break;
|
||||
default: m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unsupported device model: ." << nvxInfo_.Model << "\n";
|
||||
return false;
|
||||
}
|
||||
nvxDataModel_ = NVX_DM_NORMAL;
|
||||
switch (m_header.getSamplingFrequency())
|
||||
{
|
||||
case 50000: nvxDataSettings_.DataRate = 0; // it is not used
|
||||
realChannelsCount = 4;
|
||||
nvxDataModel_ = NVX_DM_50_KHZ;
|
||||
break;
|
||||
case 10000: nvxDataSettings_.DataRate = 0;
|
||||
realChannelsCount = 16;
|
||||
break;
|
||||
case 5000: nvxDataSettings_.DataRate = 1;
|
||||
realChannelsCount = 24;
|
||||
break;
|
||||
case 2000: nvxDataSettings_.DataRate = 2;
|
||||
break;
|
||||
case 1000: nvxDataSettings_.DataRate = 3;
|
||||
break;
|
||||
case 500: nvxDataSettings_.DataRate = 4;
|
||||
break;
|
||||
case 250: nvxDataSettings_.DataRate = 5;
|
||||
break;
|
||||
case 125: nvxDataSettings_.DataRate = 6;
|
||||
break;
|
||||
default: m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unsupported sampling frequency.\n";
|
||||
return false;
|
||||
}
|
||||
m_header.setChannelCount(realChannelsCount);
|
||||
samplesCounter_ = 0;
|
||||
triggerStates_ = 0;
|
||||
// Builds up a buffer to store
|
||||
// acquired samples. This buffer
|
||||
// will be sent to the acquisition
|
||||
// server later...
|
||||
sampleData_.resize(getDeviceBufferSamplesCapacity());
|
||||
|
||||
if (NVXSetDataMode(nvxDeviceId_, nvxDataModel_, &nvxDataSettings_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXSetDataMode error.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NVXGetProperty(nvxDeviceId_, &nvxProperty_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXGetProperty error.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NVXOpen(nvxDeviceId_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXSetDataMode error.\n";
|
||||
return false;
|
||||
}
|
||||
// Saves parameters
|
||||
m_callback = &callback;
|
||||
m_nSamplePerSentBlock = nSamplePerSentBlock;
|
||||
if (m_driverCtx.isImpedanceCheckRequested())
|
||||
{
|
||||
if (NVXStartImpedance(nvxDeviceId_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXStartImpedance error.\n";
|
||||
return false;
|
||||
}
|
||||
if (NVXStart(nvxDeviceId_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXStart error.\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMKSNVXDriver::start()
|
||||
{
|
||||
if (!m_driverCtx.isConnected()) return false;
|
||||
if (m_driverCtx.isStarted()) return false;
|
||||
|
||||
// ...
|
||||
// request hardware to start
|
||||
// sending data
|
||||
// ...
|
||||
samplesCounter_ = 0;
|
||||
if (m_driverCtx.isImpedanceCheckRequested())
|
||||
{
|
||||
if (NVXStop(nvxDeviceId_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXStop error.\n";
|
||||
return false;
|
||||
}
|
||||
if (NVXStopImpedance(nvxDeviceId_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXStopImpedance error.\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
switch (dataMode_)
|
||||
{
|
||||
case 1: // test
|
||||
if (NVXStartTest(nvxDeviceId_, 0) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXStartTest error.\n";
|
||||
return false;
|
||||
}
|
||||
// break; no break on purpose
|
||||
case 0: // normal
|
||||
if (NVXStart(nvxDeviceId_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXStart error.\n";
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const float floatNaN = std::numeric_limits<float>::quiet_NaN();
|
||||
#define NVXValueToFloat(v, resolution) ((v) == INT_MAX ? floatNaN : ((v)*(resolution)))
|
||||
// #define NVXValueToFloat(v, resolution) ((v) == INT_MAX ? 0.f : ((v)*(resolution)))
|
||||
|
||||
bool CDriverMKSNVXDriver::loop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected()) return false;
|
||||
if (m_driverCtx.isStarted())
|
||||
{
|
||||
const size_t bufferSize = sizeof(deviceBuffer_);
|
||||
int res = NVXGetData(nvxDeviceId_, deviceBuffer_, bufferSize);
|
||||
size_t sizeOfNVXDataModelStructure = nvxDataModel_ == NVX_DM_NORMAL ? (nvxInfo_.Model == NVX_MODEL_16 ? sizeof(t_NVXDataModel16)
|
||||
: (nvxInfo_.Model == NVX_MODEL_24 ? sizeof(t_NVXDataModel24)
|
||||
: (nvxInfo_.Model == NVX_MODEL_36 ? sizeof(t_NVXDataModel36)
|
||||
: sizeof(t_NVXDataModel52))))
|
||||
: sizeof(t_NVXDataMode50kHz);
|
||||
if (res < 0 || res > bufferSize || res % sizeOfNVXDataModelStructure)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXGetData returned " << res << " This is unexpected.\n";
|
||||
return false;
|
||||
}
|
||||
if (res)
|
||||
{
|
||||
CStimulationSet stimSet;
|
||||
|
||||
// ...
|
||||
// receive samples from hardware
|
||||
// put them the correct way in the sample array
|
||||
// whether the buffer is full, send it to the acquisition server
|
||||
//...
|
||||
const size_t channelsAmount = m_header.getChannelCount();
|
||||
const size_t sampblesPerChannel = res / sizeOfNVXDataModelStructure;
|
||||
if (sampblesPerChannel * channelsAmount > getDeviceBufferSamplesCapacity())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error <<
|
||||
"Internal error: the amount of data exceeds the size of sampleData storage. This is internal error due to sampleData storage miscalculation.\n";
|
||||
return false;
|
||||
}
|
||||
float* sampleDataPtr = &sampleData_.at(0);
|
||||
const float eegResolution = nvxProperty_.ResolutionEeg * 1000000.f, auxResolution = nvxProperty_.ResolutionAux * 1000000.f;
|
||||
for (size_t j = 0; j < sampblesPerChannel; ++j)
|
||||
{
|
||||
for (size_t i = 0; i < channelsAmount; ++i)
|
||||
{
|
||||
if (nvxDataModel_ == NVX_DM_NORMAL)
|
||||
{
|
||||
switch (nvxInfo_.Model)
|
||||
{
|
||||
case NVX_MODEL_16: if (i < NVX_MODEL_16_CHANNELS_MAIN)
|
||||
{
|
||||
sampleDataPtr[i * sampblesPerChannel + j] = NVXValueToFloat(reinterpret_cast<t_NVXDataModel16*>(deviceBuffer_)[j].Main[i], eegResolution);
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t auxChannelNumber = i - NVX_MODEL_16_CHANNELS_MAIN;
|
||||
sampleDataPtr[i * sampblesPerChannel + j] = NVXValueToFloat(
|
||||
reinterpret_cast<t_NVXDataModel16*>(deviceBuffer_)[j].Aux[auxChannelNumber], auxResolution);
|
||||
}
|
||||
break;
|
||||
case NVX_MODEL_24: sampleDataPtr[i * sampblesPerChannel + j] = NVXValueToFloat(reinterpret_cast<t_NVXDataModel24*>(deviceBuffer_)[j].Main[i], eegResolution);
|
||||
break;
|
||||
case NVX_MODEL_36: if (i < NVX_MODEL_36_CHANNELS_MAIN)
|
||||
{
|
||||
sampleDataPtr[i * sampblesPerChannel + j] = NVXValueToFloat(reinterpret_cast<t_NVXDataModel36*>(deviceBuffer_)[j].Main[i], eegResolution);
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t auxChannelNumber = i - NVX_MODEL_36_CHANNELS_MAIN;
|
||||
sampleDataPtr[i * sampblesPerChannel + j] = NVXValueToFloat(reinterpret_cast<t_NVXDataModel36*>(deviceBuffer_)[j].Aux[auxChannelNumber], auxResolution);
|
||||
}
|
||||
break;
|
||||
case NVX_MODEL_52: if (i < NVX_MODEL_52_CHANNELS_MAIN)
|
||||
{
|
||||
sampleDataPtr[i * sampblesPerChannel + j] = NVXValueToFloat(reinterpret_cast<t_NVXDataModel52*>(deviceBuffer_)[j].Main[i], eegResolution);
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t auxChannelNumber = i - NVX_MODEL_52_CHANNELS_MAIN;
|
||||
sampleDataPtr[i * sampblesPerChannel + j] = NVXValueToFloat(reinterpret_cast<t_NVXDataModel52*>(deviceBuffer_)[j].Aux[auxChannelNumber], auxResolution);
|
||||
}
|
||||
break;
|
||||
default: m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unsupported device model: ." << nvxInfo_.Model << "\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sampleDataPtr[i * sampblesPerChannel + j] = NVXValueToFloat(reinterpret_cast<t_NVXDataMode50kHz*>(deviceBuffer_)[j].Main[i], eegResolution);
|
||||
}
|
||||
}
|
||||
// check data integrity
|
||||
uint32_t correctSamplesCounter = 0;
|
||||
if (nvxDataModel_ == NVX_DM_NORMAL)
|
||||
{
|
||||
switch (nvxInfo_.Model)
|
||||
{
|
||||
case NVX_MODEL_16: correctSamplesCounter = reinterpret_cast<t_NVXDataModel16*>(deviceBuffer_)[j].Counter;
|
||||
break;
|
||||
case NVX_MODEL_24: correctSamplesCounter = reinterpret_cast<t_NVXDataModel24*>(deviceBuffer_)[j].Counter;
|
||||
break;
|
||||
case NVX_MODEL_36: correctSamplesCounter = reinterpret_cast<t_NVXDataModel36*>(deviceBuffer_)[j].Counter;
|
||||
break;
|
||||
case NVX_MODEL_52: correctSamplesCounter = reinterpret_cast<t_NVXDataModel52*>(deviceBuffer_)[j].Counter;
|
||||
break;
|
||||
default: m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unsupported device model: ." << nvxInfo_.Model << "\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else { correctSamplesCounter = reinterpret_cast<t_NVXDataMode50kHz*>(deviceBuffer_)[j].Counter; }
|
||||
if (samplesCounter_ != correctSamplesCounter)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Data loss: expected sample number is " << samplesCounter_
|
||||
<< " and the hardware reports " << correctSamplesCounter << "\n";
|
||||
samplesCounter_ = correctSamplesCounter;
|
||||
stimSet.appendStimulation(OVTK_StimulationId_Label_00, CTime(m_header.getSamplingFrequency(), j).time(), 0);
|
||||
}
|
||||
++samplesCounter_;
|
||||
// set triggers
|
||||
uint32_t currentTriggersState = 0;
|
||||
if (nvxDataModel_ == NVX_DM_NORMAL)
|
||||
{
|
||||
switch (nvxInfo_.Model)
|
||||
{
|
||||
case NVX_MODEL_16: currentTriggersState = ~reinterpret_cast<t_NVXDataModel16*>(deviceBuffer_)[j].Status & 0x1ff;
|
||||
break;
|
||||
case NVX_MODEL_24: currentTriggersState = ~reinterpret_cast<t_NVXDataModel24*>(deviceBuffer_)[j].Status & 0x1ff;
|
||||
break;
|
||||
case NVX_MODEL_36: currentTriggersState = ~reinterpret_cast<t_NVXDataModel36*>(deviceBuffer_)[j].Status & 0x1ff;
|
||||
break;
|
||||
case NVX_MODEL_52: currentTriggersState = ~reinterpret_cast<t_NVXDataModel52*>(deviceBuffer_)[j].Status & 0x1ff;
|
||||
break;
|
||||
default: m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Unsupported device model: ." << nvxInfo_.Model << "\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else { currentTriggersState = ~reinterpret_cast<t_NVXDataMode50kHz*>(deviceBuffer_)[j].Status & 0x1ff; }
|
||||
for (uint32_t triggerBit = 1, ovtkLabel = OVTK_StimulationId_Label_01; triggerBit <= 256; triggerBit <<= 1, ++ovtkLabel)
|
||||
{
|
||||
if (currentTriggersState & triggerBit)
|
||||
{
|
||||
if (!(triggerStates_ & triggerBit))
|
||||
{
|
||||
triggerStates_ |= triggerBit;
|
||||
stimSet.appendStimulation(ovtkLabel, CTime(m_header.getSamplingFrequency(), j).time(), 0);
|
||||
}
|
||||
}
|
||||
else { triggerStates_ &= ~triggerBit; }
|
||||
}
|
||||
}
|
||||
m_callback->setSamples(sampleDataPtr, sampblesPerChannel);
|
||||
|
||||
// When your sample buffer is fully loaded,
|
||||
// it is advised to ask the acquisition server
|
||||
// to correct any drift in the acquisition automatically.
|
||||
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
|
||||
|
||||
// ...
|
||||
// receive events from hardware
|
||||
// and put them the correct way in a CStimulationSet object
|
||||
//...
|
||||
if (stimSet.getStimulationCount() != 0) { m_callback->setStimulationSet(stimSet); }
|
||||
}
|
||||
}
|
||||
else if (m_driverCtx.isImpedanceCheckRequested())
|
||||
{ // impedance
|
||||
const size_t impedanceBubberCapacityAmount = 48;
|
||||
uint32_t impedanceBuffer[impedanceBubberCapacityAmount];
|
||||
size_t impedanceChannelsAmount = m_header.getChannelCount();
|
||||
if (impedanceChannelsAmount == 36 || impedanceChannelsAmount == 52) { impedanceChannelsAmount -= 4; }
|
||||
if (impedanceBubberCapacityAmount < impedanceChannelsAmount)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Incorrect channels amount in impedance mode. This is internal error which shall never happen.\n";
|
||||
return false;
|
||||
}
|
||||
if (NVXGetImpedance(nvxDeviceId_, impedanceBuffer, impedanceChannelsAmount * sizeof(impedanceBuffer[0])) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXGetImpedance error.\n";
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < impedanceChannelsAmount; ++i) { m_driverCtx.updateImpedance(i, impedanceBuffer[i]); }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMKSNVXDriver::stop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected()) return false;
|
||||
if (!m_driverCtx.isStarted()) return false;
|
||||
|
||||
// ...
|
||||
// request the hardware to stop
|
||||
// sending data
|
||||
// ...
|
||||
switch (dataMode_)
|
||||
{
|
||||
case 0: // normal
|
||||
if (NVXStop(nvxDeviceId_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXStop error.\n";
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 1: // test
|
||||
if (NVXStop(nvxDeviceId_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXStop error.\n";
|
||||
return false;
|
||||
}
|
||||
if (NVXStopTest(nvxDeviceId_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXStopTest error.\n";
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMKSNVXDriver::uninitialize()
|
||||
{
|
||||
if (!m_driverCtx.isConnected()) return false;
|
||||
if (!m_driverCtx.isStarted())
|
||||
{
|
||||
if (m_driverCtx.isImpedanceCheckRequested())
|
||||
{
|
||||
if (NVXStop(nvxDeviceId_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXStop error.\n";
|
||||
return false;
|
||||
}
|
||||
if (NVXStopImpedance(nvxDeviceId_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXStopImpedance error.\n";
|
||||
return false;
|
||||
}
|
||||
if (NVXClose(nvxDeviceId_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXClose error.\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ...
|
||||
// uninitialize hardware here
|
||||
// ...
|
||||
if (NVXClose(nvxDeviceId_) != NVX_ERR_OK)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "NVXClose error.\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_callback = nullptr;
|
||||
NVXAPIStop();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
bool CDriverMKSNVXDriver::isConfigurable() { return true; } // change to false if your device is not configurable
|
||||
|
||||
bool CDriverMKSNVXDriver::configure()
|
||||
{
|
||||
// Change this line if you need to specify some references to your driver attribute that need configuration, e.g. the connection ID.
|
||||
CConfigurationMKSNVXDriver config(m_driverCtx, Directories::getDataDir() + "/applications/acquisition-server/interface-MCSNVXDriver.ui", dataMode_, showAuxChannels_);
|
||||
|
||||
if (!config.configure(m_header)) { return false; }
|
||||
m_settings.save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(TARGET_HAS_ThirdPartyMCS)
|
||||
|
||||
#include <vector>
|
||||
#include "ovasIDriver.h"
|
||||
#include "../../ovasCHeader.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
|
||||
#include "../../ovasCSettingsHelper.h"
|
||||
#include "../../ovasCSettingsHelperOperators.h"
|
||||
#include "NVX.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CDriverMKSNVXDriver
|
||||
* \author mkochetkov (MKS)
|
||||
* \date Tue Jan 21 23:21:03 2014
|
||||
* \brief The CDriverMKSNVXDriver allows the acquisition server to acquire data from a MKSNVXDriver device.
|
||||
*
|
||||
* TODO: details
|
||||
*
|
||||
* \sa CConfigurationMKSNVXDriver
|
||||
*/
|
||||
const size_t maxNumberOfChannels = 36;
|
||||
|
||||
class CDriverMKSNVXDriver : public IDriver
|
||||
{
|
||||
uint8_t deviceBuffer_[1024 * maxNumberOfChannels];
|
||||
int nvxDeviceId_ = -1;
|
||||
t_NVXDataSettins nvxDataSettings_;
|
||||
int nvxDataModel_ = 0;
|
||||
uint32_t dataMode_ = 0; // normal, test, impedance
|
||||
t_NVXProperty nvxProperty_;
|
||||
uint32_t samplesCounter_ = 0; // previous t_NVXDataModelXX.Counter
|
||||
uint32_t triggerStates_ = 0;
|
||||
bool showAuxChannels_ = false;
|
||||
t_NVXInformation nvxInfo_;
|
||||
public:
|
||||
|
||||
CDriverMKSNVXDriver(IDriverContext& ctx);
|
||||
size_t getDeviceBufferSamplesCapacity() const
|
||||
{
|
||||
return sizeof(deviceBuffer_) / sizeof(t_NVXDataModel36) * maxNumberOfChannels;
|
||||
} // I really do not know if it is accurate for all modes.
|
||||
~CDriverMKSNVXDriver() 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 flag == EDriverFlag::IsUnstable; }
|
||||
|
||||
protected:
|
||||
|
||||
SettingsHelper m_settings;
|
||||
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
|
||||
// Replace this generic Header with any specific header you might have written
|
||||
CHeader m_header;
|
||||
|
||||
uint32_t m_nSamplePerSentBlock = 0;
|
||||
std::vector<float> sampleData_;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
|
||||
#endif
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
#ifdef TARGET_OS_Windows
|
||||
|
||||
#include "ovasCConfigurationDriverMensiaAcquisition.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
//TODO_JL Add the URL as parameter for configuration so it can be saved and loaded
|
||||
|
||||
CConfigurationDriverMensiaAcquisition::CConfigurationDriverMensiaAcquisition(IDriverContext& ctx, const char* gtkBuilderFilename)
|
||||
: CConfigurationBuilder(gtkBuilderFilename), m_driverCtx(ctx) {}
|
||||
|
||||
bool CConfigurationDriverMensiaAcquisition::preConfigure()
|
||||
{
|
||||
//TODO_JL call preConfigure from DLL
|
||||
return CConfigurationBuilder::preConfigure();
|
||||
}
|
||||
|
||||
bool CConfigurationDriverMensiaAcquisition::postConfigure()
|
||||
{
|
||||
//if (m_applyConfig) { } //TODO_JL call postConfigure from DLL
|
||||
return CConfigurationBuilder::postConfigure();
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_OS_Windows
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef TARGET_OS_Windows
|
||||
|
||||
#include "../ovasCConfigurationBuilder.h"
|
||||
#include "ovasIDriver.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CConfigurationDriverMensiaAcquisition
|
||||
* \author Jozef Legeny (Mensia)
|
||||
* \date 28 jan 2013
|
||||
* \brief The CConfigurationDriverMensiaAcquisition loads the configuration using the mensia-acquisition dynamic library
|
||||
*
|
||||
* \sa CDriverGenericOscillator
|
||||
*/
|
||||
|
||||
class CConfigurationDriverMensiaAcquisition final : public CConfigurationBuilder
|
||||
{
|
||||
public:
|
||||
CConfigurationDriverMensiaAcquisition(IDriverContext& ctx, const char* gtkBuilderFilename);
|
||||
|
||||
bool preConfigure() override;
|
||||
bool postConfigure() override;
|
||||
|
||||
protected:
|
||||
|
||||
IDriverContext& m_driverCtx;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_OS_Windows
|
||||
+372
@@ -0,0 +1,372 @@
|
||||
#ifdef TARGET_OS_Windows
|
||||
|
||||
#include "ovasCDriverMensiaAcquisition.h"
|
||||
#include "ovasCConfigurationDriverMensiaAcquisition.h"
|
||||
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
//#include <Windows.h>
|
||||
#include <system/WindowsUtilities.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
namespace {
|
||||
enum class EAcquisitionModes { Undefined = 0, Data = 1, Impedance = 2 };
|
||||
|
||||
CString mensiaDll = "openvibe-driver-mensia-acquisition.dll";
|
||||
HINSTANCE libMensiaAcquisition; // Library Handle
|
||||
|
||||
typedef int (* initialize_acquisition_driver_t)(const char* sDeviceIdentifier, IDriverContext& ctx);
|
||||
|
||||
typedef const char* (* get_name_t)(size_t);
|
||||
|
||||
typedef const char* (* get_device_url_t)(size_t);
|
||||
|
||||
typedef bool (*preconfigure_t)(size_t, const char*);
|
||||
typedef bool (*configure_t)(size_t, const char*);
|
||||
typedef uint32_t (*get_sampling_t)(size_t);
|
||||
typedef uint32_t (*get_channel_count_t)(size_t);
|
||||
typedef const char* (*get_channel_name_t)(size_t, size_t);
|
||||
typedef bool (*is_impedance_check_requested_t)(size_t);
|
||||
typedef void (*set_impedance_check_requested_t)(size_t, bool);
|
||||
typedef float (*get_channel_impedance_t)(size_t, size_t);
|
||||
typedef uint32_t (*get_impedance_limit_t)(size_t);
|
||||
typedef void (*set_impedance_limit_t)(size_t, uint32_t);
|
||||
typedef uint32_t (*get_experiment_id_t)(size_t);
|
||||
typedef uint32_t (*set_experiment_id_t)(size_t, uint32_t);
|
||||
typedef uint32_t (*get_subject_age_t)(size_t);
|
||||
typedef uint32_t (*set_subject_age_t)(size_t, uint32_t);
|
||||
typedef uint32_t (*get_subject_gender_t)(size_t);
|
||||
typedef uint32_t (*set_subject_gender_t)(size_t, uint32_t);
|
||||
typedef uint32_t (*set_sample_count_per_buffer_t)(size_t, uint32_t);
|
||||
typedef bool (*initialize_t)(size_t, IDriverCallback*, uint32_t, const char*, EAcquisitionModes);
|
||||
typedef bool (*start_t)(size_t);
|
||||
typedef bool (*stop_t)(size_t);
|
||||
typedef bool (*uninitialize_t)(size_t);
|
||||
typedef bool (*loop_t)(size_t);
|
||||
|
||||
initialize_acquisition_driver_t fpInitializeAcquisitionDriver;
|
||||
get_name_t fpGetName;
|
||||
get_device_url_t fpGetDeviceURL;
|
||||
|
||||
preconfigure_t fpPreconfigure;
|
||||
configure_t fpConfigure;
|
||||
get_sampling_t fpGetSampling;
|
||||
get_channel_count_t fpGetChannelCount;
|
||||
get_channel_name_t fpGetChannelName;
|
||||
is_impedance_check_requested_t fpIsImpedanceCheckRequested;
|
||||
set_impedance_check_requested_t fpSetImpedanceCheckRequested;
|
||||
get_channel_impedance_t fpGetChannelImpedance;
|
||||
get_impedance_limit_t fpGetImpedanceLimit;
|
||||
set_impedance_limit_t fpSetImpedanceLimit;
|
||||
get_experiment_id_t fpGetExperimentID;
|
||||
set_experiment_id_t fpSetExperimentID;
|
||||
get_subject_age_t fpGetSubjectAge;
|
||||
set_subject_age_t fpSetSubjectAge;
|
||||
get_subject_gender_t fpGetSubjectGender;
|
||||
set_subject_gender_t fpSetSubjectGender;
|
||||
set_sample_count_per_buffer_t fpSetSampleCountPerBuffer;
|
||||
initialize_t fpInitialize;
|
||||
start_t fpStart;
|
||||
stop_t fpStop;
|
||||
uninitialize_t fpUninitialize;
|
||||
loop_t fpLoop;
|
||||
} // namespace
|
||||
|
||||
template <typename T>
|
||||
void CDriverMensiaAcquisition::loadDLLfunct(T* pointer, const char* name)
|
||||
{
|
||||
*pointer = T(GetProcAddress(libMensiaAcquisition, name));
|
||||
if (!*pointer)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Load method " << name << "\n";
|
||||
m_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CDriverMensiaAcquisition::CDriverMensiaAcquisition(IDriverContext& ctx, const char* driverID)
|
||||
: IDriver(ctx)
|
||||
// This hax only works because m_settings does creates a copy of the string
|
||||
, m_settings(std::string(std::string("AcquisitionServer_Driver_MensiaAcquisition_") + driverID).c_str(), m_driverCtx.getConfigurationManager())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverMensiaAcquisition::CDriverMensiaAcquisition\n";
|
||||
m_valid = true;
|
||||
|
||||
// Load the Mensia Acquisition Library
|
||||
const CString path = m_driverCtx.getConfigurationManager().expand("${Path_Bin}") + "/" + mensiaDll;
|
||||
if (!std::ifstream(path.toASCIIString()).is_open())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverMensiaAcquisition::CDriverMensiaAcquisition: " <<
|
||||
" dll file [" << path << "] not openable, perhaps it was not installed.\n";
|
||||
m_valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
libMensiaAcquisition = HINSTANCE(System::WindowsUtilities::utf16CompliantLoadLibrary(path.toASCIIString()));
|
||||
if (!libMensiaAcquisition)
|
||||
{
|
||||
m_driverCtx.getLogManager() << "CDriverMensiaAcquisition::CDriverMensiaAcquisition: utf16CompliantLoadLibrary failed to load: " <<
|
||||
path << " with error " << size_t(GetLastError()) << "\n";
|
||||
}
|
||||
|
||||
loadDLLfunct<initialize_acquisition_driver_t>(&fpInitializeAcquisitionDriver, "initializeAcquisitionDriver");
|
||||
loadDLLfunct<get_name_t>(&fpGetName, "getName");
|
||||
loadDLLfunct<get_device_url_t>(&fpGetDeviceURL, "getDeviceURL");
|
||||
loadDLLfunct<preconfigure_t>(&fpPreconfigure, "preconfigure");
|
||||
loadDLLfunct<configure_t>(&fpConfigure, "configure");
|
||||
loadDLLfunct<get_sampling_t>(&fpGetSampling, "getSamplingRate");
|
||||
loadDLLfunct<get_channel_count_t>(&fpGetChannelCount, "getChannelCount");
|
||||
loadDLLfunct<get_channel_name_t>(&fpGetChannelName, "getChannelName");
|
||||
loadDLLfunct<is_impedance_check_requested_t>(&fpIsImpedanceCheckRequested, "isImpedanceCheckRequested");
|
||||
loadDLLfunct<set_impedance_check_requested_t>(&fpSetImpedanceCheckRequested, "setImpedanceCheckRequested");
|
||||
loadDLLfunct<get_channel_impedance_t>(&fpGetChannelImpedance, "getChannelImpedance");
|
||||
loadDLLfunct<get_impedance_limit_t>(&fpGetImpedanceLimit, "getImpedanceLimit");
|
||||
loadDLLfunct<set_impedance_limit_t>(&fpSetImpedanceLimit, "setImpedanceLimit");
|
||||
loadDLLfunct<get_experiment_id_t>(&fpGetExperimentID, "getExperimentID");
|
||||
loadDLLfunct<set_experiment_id_t>(&fpSetExperimentID, "setExperimentID");
|
||||
loadDLLfunct<get_subject_age_t>(&fpGetSubjectAge, "getSubjectAge");
|
||||
loadDLLfunct<set_subject_age_t>(&fpSetSubjectAge, "setSubjectAge");
|
||||
loadDLLfunct<get_subject_gender_t>(&fpGetSubjectGender, "getSubjectGender");
|
||||
loadDLLfunct<set_subject_gender_t>(&fpSetSubjectGender, "setSubjectGender");
|
||||
loadDLLfunct<set_sample_count_per_buffer_t>(&fpSetSampleCountPerBuffer, "setSampleCountPerBuffer");
|
||||
loadDLLfunct<initialize_t>(&fpInitialize, "initialize");
|
||||
loadDLLfunct<start_t>(&fpStart, "start");
|
||||
loadDLLfunct<stop_t>(&fpStop, "stop");
|
||||
loadDLLfunct<uninitialize_t>(&fpUninitialize, "uninitialize");
|
||||
loadDLLfunct<loop_t>(&fpLoop, "loop");
|
||||
|
||||
if (!m_valid)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Could not initialize Mensia Acquisition Driver driver\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// prepare the device library
|
||||
|
||||
const int id = fpInitializeAcquisitionDriver(driverID, ctx);
|
||||
|
||||
// Negative value is considered an error
|
||||
if (id < 0)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Could not initialize Mensia Acquisition Driver driver\n";
|
||||
return;
|
||||
}
|
||||
m_driverID = uint32_t(id);
|
||||
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Initialized Mensia Acquisition Driver on id [" << m_driverID << "]" << "\n";
|
||||
|
||||
//TODO_JL Set sampling frenquency and channel count in header?
|
||||
m_header.setSamplingFrequency(128);
|
||||
m_header.setChannelCount(0);
|
||||
m_header.setExperimentID(0);
|
||||
m_header.setSubjectAge(0);
|
||||
m_header.setSubjectGender(0);
|
||||
|
||||
m_settings.add("Header", &m_header);
|
||||
m_settings.add("DeviceURL", &m_deviceURL);
|
||||
//TODO_JL Add the URL to settings so it can be saved (we should be able to expose it)
|
||||
m_settings.load();
|
||||
fpSetExperimentID(m_driverID, m_header.getExperimentID());
|
||||
fpSetSubjectAge(m_driverID, m_header.getSubjectAge());
|
||||
fpSetSubjectGender(m_driverID, m_header.getSubjectGender());
|
||||
|
||||
fpSetImpedanceCheckRequested(m_driverID, m_header.isImpedanceCheckRequested());
|
||||
fpSetImpedanceLimit(m_driverID, m_header.getImpedanceLimit());
|
||||
|
||||
fpPreconfigure(m_driverID, m_deviceURL.toASCIIString());
|
||||
|
||||
m_header.setChannelCount(fpGetChannelCount(m_driverID));
|
||||
m_header.setSamplingFrequency(fpGetSampling(m_driverID));
|
||||
|
||||
for (size_t index = 0; index < m_header.getChannelCount(); ++index) { m_header.setChannelName(index, fpGetChannelName(m_driverID, index)); }
|
||||
}
|
||||
|
||||
void CDriverMensiaAcquisition::release()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverMensiaAcquisition::release\n";
|
||||
delete this;
|
||||
}
|
||||
|
||||
const char* CDriverMensiaAcquisition::getName() { return fpGetName(m_driverID); }
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverMensiaAcquisition::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverMensiaAcquisition::initialize\n";
|
||||
|
||||
if (m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
m_nSamplePerSentBlock = nSamplePerSentBlock;
|
||||
fpSetSampleCountPerBuffer(m_driverID, m_nSamplePerSentBlock);
|
||||
m_deviceURL = CString(fpGetDeviceURL(m_driverID));
|
||||
|
||||
m_callback = &callback;
|
||||
|
||||
m_header.setImpedanceCheckRequested(fpIsImpedanceCheckRequested(m_driverID));
|
||||
const EAcquisitionModes mode = m_header.isImpedanceCheckRequested() ? EAcquisitionModes::Impedance : EAcquisitionModes::Data;
|
||||
if (!fpInitialize(m_driverID, m_callback, m_nSamplePerSentBlock, m_deviceURL.toASCIIString(), mode)) { return false; }
|
||||
|
||||
fpSetExperimentID(m_driverID, m_header.getExperimentID());
|
||||
fpSetSubjectAge(m_driverID, m_header.getSubjectAge());
|
||||
fpSetSubjectGender(m_driverID, m_header.getSubjectGender());
|
||||
|
||||
m_header.setSamplingFrequency(fpGetSampling(m_driverID));
|
||||
m_header.setChannelCount(fpGetChannelCount(m_driverID));
|
||||
m_header.setExperimentID(fpGetExperimentID(m_driverID));
|
||||
m_header.setSubjectAge(fpGetSubjectAge(m_driverID));
|
||||
m_header.setSubjectGender(fpGetSubjectGender(m_driverID));
|
||||
m_header.setImpedanceLimit(fpGetImpedanceLimit(m_driverID));
|
||||
m_header.setImpedanceCheckRequested(fpIsImpedanceCheckRequested(m_driverID));
|
||||
|
||||
|
||||
for (size_t uiChannelIndex = 0; uiChannelIndex < m_header.getChannelCount(); ++uiChannelIndex)
|
||||
{
|
||||
m_header.setChannelName(uiChannelIndex, fpGetChannelName(m_driverID, uiChannelIndex));
|
||||
m_header.setChannelUnits(uiChannelIndex, OVTK_UNIT_Volts, OVTK_FACTOR_Micro);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMensiaAcquisition::start()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverMensiaAcquisition::start\n";
|
||||
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
if (!fpStart(m_driverID)) { return false; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMensiaAcquisition::loop()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "CDriverMensiaAcquisition::loop\n";
|
||||
|
||||
if (!m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
if (m_driverCtx.isStarted()) { if (!fpLoop(m_driverID)) { return false; } }
|
||||
else
|
||||
{
|
||||
if (!fpLoop(m_driverID)) { return false; }
|
||||
// impedance check here
|
||||
if (m_driverCtx.isImpedanceCheckRequested())
|
||||
{
|
||||
for (size_t j = 0; j < m_header.getChannelCount(); ++j) { m_driverCtx.updateImpedance(j, fpGetChannelImpedance(m_driverID, j)); }
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMensiaAcquisition::stop()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverMensiaAcquisition::stop\n";
|
||||
|
||||
|
||||
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
return fpStop(m_driverID);
|
||||
}
|
||||
|
||||
bool CDriverMensiaAcquisition::uninitialize()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverMensiaAcquisition::uninitialize\n";
|
||||
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
fpUninitialize(m_driverID);
|
||||
|
||||
delete [] m_sample;
|
||||
m_sample = nullptr;
|
||||
m_callback = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverMensiaAcquisition::isConfigurable()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverMensiaAcquisition::isConfigurable\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMensiaAcquisition::configure()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "CDriverMensiaAcquisition::configure\n";
|
||||
fpSetExperimentID(m_driverID, m_header.getExperimentID());
|
||||
fpSetSubjectAge(m_driverID, m_header.getSubjectAge());
|
||||
fpSetSubjectGender(m_driverID, m_header.getSubjectGender());
|
||||
|
||||
/*CConfigurationDriverMensiaAcquisition config(m_driverCtx, Directories::getDataDir() + "/applications/acquisition-server/interface-Generic-Oscillator.ui" );
|
||||
*/
|
||||
if (fpConfigure(m_driverID, m_deviceURL.toASCIIString()))
|
||||
{
|
||||
m_deviceURL = CString(fpGetDeviceURL(m_driverID));
|
||||
|
||||
// We need to escape the URL before we save the setting to the file because
|
||||
// of the way OpenViBE handles strings
|
||||
const CString tempURL = m_deviceURL;
|
||||
std::string escapedURL = m_deviceURL.toASCIIString();
|
||||
size_t pos = 0;
|
||||
while ((pos = escapedURL.find('{', pos)) != std::string::npos)
|
||||
{
|
||||
escapedURL.replace(pos, 1, "\\{");
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
pos = 0;
|
||||
while ((pos = escapedURL.find('}', pos)) != std::string::npos)
|
||||
{
|
||||
escapedURL.replace(pos, 1, "\\}");
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
pos = 0;
|
||||
while ((pos = escapedURL.find('$', pos)) != std::string::npos)
|
||||
{
|
||||
escapedURL.replace(pos, 1, "\\$");
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
m_deviceURL = escapedURL.c_str();
|
||||
m_header.setSamplingFrequency(fpGetSampling(m_driverID));
|
||||
m_header.setChannelCount(fpGetChannelCount(m_driverID));
|
||||
m_header.setExperimentID(fpGetExperimentID(m_driverID));
|
||||
m_header.setSubjectAge(fpGetSubjectAge(m_driverID));
|
||||
m_header.setSubjectGender(fpGetSubjectGender(m_driverID));
|
||||
m_header.setImpedanceLimit(fpGetImpedanceLimit(m_driverID));
|
||||
m_header.setImpedanceCheckRequested(fpIsImpedanceCheckRequested(m_driverID));
|
||||
/*
|
||||
fpSetExperimentID(m_driverID, m_header.getExperimentID());
|
||||
fpSetSubjectAge(m_driverID, m_header.getSubjectAge());
|
||||
fpSetSubjectGender(m_driverID, m_header.getSubjectGender());
|
||||
*/
|
||||
|
||||
/*
|
||||
for (size_t index = 0; index < m_header.getChannelCount(); ++index) { m_header.setChannelName(index, ""); }
|
||||
*/
|
||||
|
||||
m_settings.save();
|
||||
m_deviceURL = tempURL;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_OS_Windows
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef TARGET_OS_Windows
|
||||
|
||||
#include "ovasIDriver.h"
|
||||
#include "../ovasCHeader.h"
|
||||
|
||||
#include "../ovasCSettingsHelper.h"
|
||||
#include "../ovasCSettingsHelperOperators.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
class CDriverMensiaAcquisition final : public IDriver
|
||||
{
|
||||
public:
|
||||
|
||||
CDriverMensiaAcquisition(IDriverContext& ctx, const char* driverID);
|
||||
void release();
|
||||
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; }
|
||||
|
||||
protected:
|
||||
|
||||
SettingsHelper m_settings;
|
||||
bool m_valid = false;
|
||||
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
CHeader m_header;
|
||||
|
||||
uint32_t m_nSamplePerSentBlock = 0;
|
||||
float* m_sample = nullptr;
|
||||
|
||||
uint32_t m_nTotalSample = 0;
|
||||
uint64_t m_startTime = 0;
|
||||
|
||||
uint32_t* m_sampleCountPerBuffer = nullptr;
|
||||
|
||||
private:
|
||||
// Settings
|
||||
CString m_deviceURL;
|
||||
|
||||
template <typename T>
|
||||
void loadDLLfunct(T* pointer, const char* name);
|
||||
uint32_t m_driverID = 0;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // TARGET_OS_Windows
|
||||
+814
@@ -0,0 +1,814 @@
|
||||
#if defined(TARGET_HAS_ThirdPartyMicromed)
|
||||
|
||||
#include "ovasCDriverMicromedSystemPlusEvolution.h"
|
||||
#include "../ovasCConfigurationNetworkBuilder.h"
|
||||
|
||||
#if defined TARGET_OS_Windows
|
||||
|
||||
#define MicromedDLL "dllMicromed.dll"
|
||||
|
||||
#include <system/ovCTime.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <windows.h>
|
||||
#include <cstring>
|
||||
|
||||
#include <algorithm> // std::min, etc on VS2013
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
typedef char* ( __stdcall * STRUCTHEADER)();
|
||||
typedef int ( __stdcall * STRUCTHEADERSIZE)();
|
||||
typedef char* ( __stdcall * STRUCTHEADERINFO)();
|
||||
typedef int ( __stdcall * STRUCTHEADERINFOSIZE)();
|
||||
typedef unsigned short int* ( __stdcall * STRUCTBUFFDATA)();
|
||||
typedef int ( __stdcall * STRUCTBUFFDATASIZE)();
|
||||
typedef unsigned char* ( __stdcall * STRUCTBUFFNOTE)();
|
||||
typedef int ( __stdcall * STRUCTBUFFNOTESIZE)();
|
||||
typedef unsigned char* ( __stdcall * STRUCTBUFFTRIGGER)();
|
||||
typedef int ( __stdcall * STRUCTBUFFTRIGGERSIZE)();
|
||||
typedef bool ( __stdcall * HEADERVALID)();
|
||||
typedef bool ( __stdcall * DATAHEADER)();
|
||||
typedef bool ( __stdcall * NOTEHEADER)();
|
||||
typedef bool ( __stdcall * TRIGGERHEADER)();
|
||||
typedef bool ( __stdcall * INITHEADER)();
|
||||
typedef uint32_t ( __stdcall * DATALENGTH)();
|
||||
//typedef uint32_t ( __stdcall * ADDRESSOFDATA) ();
|
||||
typedef uint32_t ( __stdcall * NBOFCHANNELS)();
|
||||
typedef uint32_t ( __stdcall * MINSAMPLINGRATE)();
|
||||
typedef uint32_t ( __stdcall * SIZEOFEACHDATAINBYTE)();
|
||||
typedef float ( __stdcall * DATAVALUE)(int numChannel, int numSample);
|
||||
typedef int ( __stdcall * TRIGGERCOUNT)();
|
||||
typedef unsigned long int ( __stdcall * TRIGGERSAMPLE)(int indexTrigger);
|
||||
typedef unsigned short int ( __stdcall * TRIGGERVALUE)(int indexTrigger);
|
||||
typedef int ( __stdcall * NOTECOUNT)();
|
||||
typedef unsigned long int ( __stdcall * NOTESAMPLE)(int indexNote);
|
||||
typedef char* ( __stdcall * NOTECOMMENT)(int indexNote);
|
||||
typedef void ( __stdcall * SHOWELECTRODE)(std::stringstream* info);
|
||||
typedef void ( __stdcall * SHOWNOTE)(std::stringstream* info);
|
||||
typedef void ( __stdcall * SHOWTRIGGER)(std::stringstream* trigger);
|
||||
typedef void ( __stdcall * SHOWSIGNAL)(std::stringstream* signal);
|
||||
|
||||
// Header Structure
|
||||
//---------------------------
|
||||
//pointer on the header structure. the header structure is send before any HeaderInfo or BuffData structure.
|
||||
STRUCTHEADER fGetStructHeader;
|
||||
|
||||
//give the size of the structHeader
|
||||
STRUCTHEADERSIZE fGetStructHeaderSize;
|
||||
|
||||
//say if the last structHeader is valid.
|
||||
//must be call just after structHeader.
|
||||
HEADERVALID fIsHeaderValid;
|
||||
|
||||
//say if the Header structure is following by a Buffer Data structure
|
||||
//must be call after structHeader.
|
||||
DATAHEADER fIsDataHeader;
|
||||
|
||||
//say if the Header structure is following by a Buffer Note structure
|
||||
//must be call after structHeader.
|
||||
NOTEHEADER fIsNoteHeader;
|
||||
|
||||
//say if the Header structure is following by a Buffer Trigger structure
|
||||
//must be call after structHeader.
|
||||
TRIGGERHEADER fIsTriggerHeader;
|
||||
|
||||
//say if the Header structure is following by a Header Info Structure
|
||||
//must be call after structHeader.
|
||||
INITHEADER fIsInitHeader;
|
||||
|
||||
//give the size of the following data receive
|
||||
//must be call after structHeader.
|
||||
DATALENGTH fGetDataLength;
|
||||
|
||||
// Header Info Structure
|
||||
//---------------------------
|
||||
|
||||
//give many information of the device configuration.
|
||||
STRUCTHEADERINFO fGetStructHeaderInfo;
|
||||
|
||||
//give the size of the structHeaderInfo
|
||||
STRUCTHEADERINFOSIZE fGetStructHeaderInfoSize;
|
||||
|
||||
//give address of the first data. the address count start to the beginning of this structure.
|
||||
//it necessary to receive information between this structure and the first address data before beginning.
|
||||
//must be call after structHeaderInfo.
|
||||
//ADDRESSOFDATA fGetAddressOfData;
|
||||
|
||||
//give the number of channels connected to this device.
|
||||
//must be call after structHeaderInfo.
|
||||
NBOFCHANNELS fGetNbOfChannels;
|
||||
|
||||
//give the sampling rate of the device
|
||||
//must be call after structHeaderInfo.
|
||||
MINSAMPLINGRATE fGetMinimumSamplingRate;
|
||||
|
||||
//give the size in byte for one sample of one channels
|
||||
//must be call after structHeaderInfo.
|
||||
SIZEOFEACHDATAINBYTE fGetSizeOfEachDataInByte;
|
||||
|
||||
//Give the value of the samples and channel specify
|
||||
DATAVALUE fGetDataValue;
|
||||
|
||||
SHOWELECTRODE fShowElectrode;
|
||||
SHOWNOTE fShowNote;
|
||||
SHOWTRIGGER fShowTrigger;
|
||||
SHOWSIGNAL fShowSignal;
|
||||
|
||||
// Buffer Data Structure
|
||||
//---------------------------
|
||||
|
||||
//give sample of channels
|
||||
STRUCTBUFFDATA fGetStructBuffData;
|
||||
//give the size of the Data buffer
|
||||
STRUCTBUFFDATASIZE fGetStructBuffDataSize;
|
||||
// Buffer Note Structure
|
||||
//---------------------------
|
||||
|
||||
//give sample of channels
|
||||
STRUCTBUFFNOTE fGetStructBuffNote;
|
||||
//give the size of the Data buffer
|
||||
STRUCTBUFFNOTESIZE fGetStructBuffNoteSize;
|
||||
|
||||
//give the number of Note received in the last data block
|
||||
NOTECOUNT fGetNoteCount;
|
||||
|
||||
//give the number of the sample whose the time corresponding to the reception of the note specified by the parameter
|
||||
NOTESAMPLE fGetNoteSample;
|
||||
|
||||
//give the comment corresponding to the note specified by the parameter
|
||||
NOTECOMMENT fGetNoteComment;
|
||||
|
||||
// Buffer Trigger Structure
|
||||
//---------------------------
|
||||
|
||||
//give sample of channels
|
||||
STRUCTBUFFTRIGGER fGetStructBuffTrigger;
|
||||
//give the size of the Data buffer
|
||||
STRUCTBUFFTRIGGERSIZE fGetStructBuffTriggerSize;
|
||||
|
||||
//give the number of Trigger received in the last data block
|
||||
TRIGGERCOUNT fGetTriggerCount;
|
||||
|
||||
//give the number of the sample whose the time corresponding to the reception of the trigger specified by the parameter
|
||||
TRIGGERSAMPLE fGetTriggerSample;
|
||||
|
||||
//give the value corresponding to the trigger specified by the parameter
|
||||
TRIGGERVALUE fGetTriggerValue;
|
||||
|
||||
//lib
|
||||
HINSTANCE libMicromed; //Library Handle
|
||||
|
||||
//reg key
|
||||
static char tcpPortNumber[1024];
|
||||
static char tcpSendAcq[1024];
|
||||
static char tcpServerName[1024];
|
||||
static HKEY registryKey = nullptr;
|
||||
// bool g_bInitializedFromRegistry=false;
|
||||
static const char* registeryKeyName = "Software\\VB and VBA Program Settings\\Brain Quick - System 98\\EEG_Settings";
|
||||
|
||||
#define LOAD_DLL_FUNC(var, type, name) \
|
||||
var = (type)::GetProcAddress(libMicromed, name); \
|
||||
if(!var) \
|
||||
{ \
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Load method " << name << "\n"; \
|
||||
m_valid=false; \
|
||||
return false; \
|
||||
}
|
||||
|
||||
CDriverMicromedSystemPlusEvolution::CDriverMicromedSystemPlusEvolution(IDriverContext& ctx)
|
||||
: IDriver(ctx), m_settings("AcquisitionServer_Driver_SystemPlusEvolution", m_driverCtx.getConfigurationManager())
|
||||
{
|
||||
libMicromed = nullptr;
|
||||
|
||||
m_settings.add("Header", &m_header);
|
||||
m_settings.add("ServerHostPort", &m_ServerHostPort);
|
||||
m_settings.add("TimeOutMs", &m_timeOutMilliseconds);
|
||||
m_settings.load();
|
||||
}
|
||||
|
||||
CDriverMicromedSystemPlusEvolution::~CDriverMicromedSystemPlusEvolution()
|
||||
{
|
||||
if (m_ConnectionServer)
|
||||
{
|
||||
m_ConnectionServer->release();
|
||||
m_ConnectionServer = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is it necessary to restore the
|
||||
* registry keys here ?
|
||||
*/
|
||||
|
||||
#if 0
|
||||
if(ERROR_SUCCESS!=::RegOpenKeyEx(HKEY_CURRENT_USER, registeryKeyName, 0, KEY_WRITE, ®istryKey))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Registery key " << registeryKeyName << " not restored\n";
|
||||
}
|
||||
|
||||
if(tcpPortNumber!=std::string(""))
|
||||
{
|
||||
if(ERROR_SUCCESS!=::RegSetValueEx(registryKey, "tcpPortNumber", 0, REG_SZ, (LPBYTE)tcpPortNumber, ::strlen(tcpPortNumber)))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Registery key tcpPortNumber not restored\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(ERROR_SUCCESS!=::RegDeleteValue(registryKey, "tcpPortNumber"))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Registery key tcpPortNumber not deleted\n";
|
||||
}
|
||||
}
|
||||
|
||||
if(tcpSendAcq!=std::string(""))
|
||||
{
|
||||
if(ERROR_SUCCESS!=::RegSetValueEx(registryKey, "tcpSendAcq", 0, REG_SZ, (LPBYTE)tcpSendAcq, ::strlen(tcpSendAcq)))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Registery key tcpSendAcq not restored\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(ERROR_SUCCESS!=::RegDeleteValue(registryKey, "tcpSendAcq"))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Registery key tcpSendAcq not deleted\n";
|
||||
}
|
||||
}
|
||||
|
||||
if(tcpServerName!=std::string(""))
|
||||
{
|
||||
if(ERROR_SUCCESS!=::RegSetValueEx(registryKey, "tcpServerName", 0, REG_SZ, (LPBYTE)tcpServerName, ::strlen(tcpServerName)))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Registery key tcpServerName not restored\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(ERROR_SUCCESS!=::RegDeleteValue(registryKey, "tcpServerName"))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Registery key tcpServerName not deleted\n";
|
||||
}
|
||||
}
|
||||
|
||||
::RegCloseKey(registryKey);
|
||||
registryKey= nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Load the ddl of the driver
|
||||
bool CDriverMicromedSystemPlusEvolution::loadDLL()
|
||||
{
|
||||
if (libMicromed) { return true; } // already loaded
|
||||
|
||||
//Open library
|
||||
const CString path = m_driverCtx.getConfigurationManager().expand("${Path_Bin}") + "/" + MicromedDLL;
|
||||
libMicromed = ::LoadLibrary(path.toASCIIString());
|
||||
|
||||
//if it can't be open return FALSE;
|
||||
if (libMicromed == nullptr)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Couldn't load DLL: " << path << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
//load the method for initialized the driver
|
||||
LOAD_DLL_FUNC(fGetStructHeader, STRUCTHEADER, "getStructHeader");
|
||||
LOAD_DLL_FUNC(fGetStructHeaderSize, STRUCTHEADERSIZE, "getStructHeaderSize");
|
||||
LOAD_DLL_FUNC(fGetStructHeaderInfo, STRUCTHEADERINFO, "getStructHeaderInfo");
|
||||
LOAD_DLL_FUNC(fGetStructHeaderInfoSize, STRUCTHEADERINFOSIZE, "getStructHeaderInfoSize");
|
||||
LOAD_DLL_FUNC(fGetStructBuffData, STRUCTBUFFDATA, "getStructBuffData");
|
||||
LOAD_DLL_FUNC(fGetStructBuffDataSize, STRUCTBUFFDATASIZE, "getStructBuffDataSize");
|
||||
LOAD_DLL_FUNC(fGetStructBuffNote, STRUCTBUFFNOTE, "getStructBuffNote");
|
||||
LOAD_DLL_FUNC(fGetStructBuffNoteSize, STRUCTBUFFNOTESIZE, "getStructBuffNoteSize");
|
||||
LOAD_DLL_FUNC(fGetStructBuffTrigger, STRUCTBUFFTRIGGER, "getStructBuffTrigger");
|
||||
LOAD_DLL_FUNC(fGetStructBuffTriggerSize, STRUCTBUFFTRIGGERSIZE, "getStructBuffTriggerSize");
|
||||
LOAD_DLL_FUNC(fIsHeaderValid, HEADERVALID, "isHeaderValid");
|
||||
LOAD_DLL_FUNC(fIsDataHeader, DATAHEADER, "isDataHeader");
|
||||
LOAD_DLL_FUNC(fIsNoteHeader, NOTEHEADER, "isNoteHeader");
|
||||
LOAD_DLL_FUNC(fIsTriggerHeader, TRIGGERHEADER, "isTriggerHeader");
|
||||
LOAD_DLL_FUNC(fIsInitHeader, INITHEADER, "isInitHeader");
|
||||
LOAD_DLL_FUNC(fGetDataLength, DATALENGTH, "getDataLength");
|
||||
//LOAD_DLL_FUNC(fGetAddressOfData, ADDRESSOFDATA, "getAddressOfData");
|
||||
|
||||
LOAD_DLL_FUNC(fGetNbOfChannels, NBOFCHANNELS, "getNbOfChannels");
|
||||
LOAD_DLL_FUNC(fGetMinimumSamplingRate, MINSAMPLINGRATE, "getMinimumSamplingRate");
|
||||
LOAD_DLL_FUNC(fGetSizeOfEachDataInByte, SIZEOFEACHDATAINBYTE, "getSizeOfEachDataInByte");
|
||||
LOAD_DLL_FUNC(fGetDataValue, DATAVALUE, "getDataValue");
|
||||
LOAD_DLL_FUNC(fGetTriggerCount, TRIGGERCOUNT, "getTriggerCount");
|
||||
LOAD_DLL_FUNC(fGetTriggerSample, TRIGGERSAMPLE, "getTriggerSample");
|
||||
LOAD_DLL_FUNC(fGetTriggerValue, TRIGGERVALUE, "getTriggerValue");
|
||||
LOAD_DLL_FUNC(fGetNoteCount, NOTECOUNT, "getNoteCount");
|
||||
LOAD_DLL_FUNC(fGetNoteSample, NOTESAMPLE, "getNoteSample");
|
||||
LOAD_DLL_FUNC(fGetNoteComment, NOTECOMMENT, "getNoteComment");
|
||||
LOAD_DLL_FUNC(fShowElectrode, SHOWELECTRODE, "show_Electrode");
|
||||
LOAD_DLL_FUNC(fShowNote, SHOWNOTE, "show_Note");
|
||||
LOAD_DLL_FUNC(fShowTrigger, SHOWTRIGGER, "show_Trigger");
|
||||
LOAD_DLL_FUNC(fShowSignal, SHOWSIGNAL, "showSignal");
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Succeeded in loading DLL: " << path << "\n";
|
||||
m_structHeader = fGetStructHeader();
|
||||
m_structHeaderInfo = fGetStructHeaderInfo();
|
||||
m_structBuffData = fGetStructBuffData();
|
||||
m_structBuffNote = fGetStructBuffNote();
|
||||
m_structBuffTrigger = fGetStructBuffTrigger();
|
||||
|
||||
#if 1
|
||||
if (ERROR_SUCCESS != ::RegOpenKeyEx(HKEY_CURRENT_USER, registeryKeyName, 0, KEY_QUERY_VALUE, ®istryKey))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Registery key " << registeryKeyName << " is not initialized\n";
|
||||
strcpy(tcpPortNumber, "");
|
||||
strcpy(tcpSendAcq, "");
|
||||
strcpy(tcpServerName, "");
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD taille = sizeof(tcpPortNumber);
|
||||
|
||||
if (ERROR_SUCCESS != ::RegQueryValueEx(registryKey, "tcpPortNumber", nullptr, nullptr, reinterpret_cast<LPBYTE>(tcpPortNumber), &taille))
|
||||
{
|
||||
strcpy(tcpPortNumber, "");
|
||||
}
|
||||
else { m_ServerHostPort = atoi(tcpPortNumber); }
|
||||
|
||||
if (ERROR_SUCCESS != ::RegQueryValueEx(registryKey, "tcpSendAcq", nullptr, nullptr, reinterpret_cast<LPBYTE>(tcpSendAcq), &taille))
|
||||
{
|
||||
strcpy(tcpSendAcq, "");
|
||||
}
|
||||
|
||||
if (ERROR_SUCCESS != ::RegQueryValueEx(registryKey, "tcpServerName", nullptr, nullptr, reinterpret_cast<LPBYTE>(tcpServerName), &taille))
|
||||
{
|
||||
strcpy(tcpServerName, "");
|
||||
}
|
||||
|
||||
RegCloseKey(registryKey);
|
||||
registryKey = nullptr;
|
||||
// g_bInitializedFromRegistry=true;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
short CDriverMicromedSystemPlusEvolution::myReceive(char* buf, const long dataLen)
|
||||
{
|
||||
long nDati = 0;
|
||||
//get data contains in the temp buffer
|
||||
while (!m_tempBuffs.empty() && nDati < dataLen)
|
||||
{
|
||||
buf[nDati] = m_tempBuffs.front();
|
||||
m_tempBuffs.pop_front();
|
||||
nDati++;
|
||||
}
|
||||
|
||||
while (nDati < dataLen)
|
||||
{
|
||||
const long recByte = m_Connection->receiveBuffer((&buf[nDati]), dataLen - nDati);
|
||||
if (recByte == 0)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "No data was received, check if the device is still connected\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
nDati += recByte;
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Received Data: = " << nDati << " /" << dataLen << "\n";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool CDriverMicromedSystemPlusEvolution::receiveAllHeader()
|
||||
{
|
||||
do
|
||||
{
|
||||
// Receive Header
|
||||
if (this->myReceive(m_structHeader, fGetStructHeaderSize()) == -1) { return false; }
|
||||
if (fIsHeaderValid())
|
||||
{
|
||||
char* header = new char[fGetStructHeaderSize()];
|
||||
memcpy(header, m_structHeader, fGetStructHeaderSize());
|
||||
m_headers.push_back(header);
|
||||
}
|
||||
else
|
||||
{
|
||||
//if no header was found, its impossible to find the next data block
|
||||
if (m_headers.empty())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Header received not in correct form\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
//save data in the temp buffer
|
||||
for (int i = fGetStructHeaderSize(); i > 0; i--) { m_tempBuffs.push_front(m_structHeader[i - 1]); }
|
||||
}
|
||||
} while (fIsHeaderValid());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMicromedSystemPlusEvolution::loadNextHeader()
|
||||
{
|
||||
//get the next header
|
||||
memcpy(m_structHeader, m_headers.back(), fGetStructHeaderSize());
|
||||
|
||||
//load next data
|
||||
char* temp = new char[fGetStructHeaderSize()];
|
||||
if (this->myReceive(temp, fGetStructHeaderSize()) == -1) { return false; }
|
||||
//check if the next data correspond to the current header
|
||||
while (m_headers.size() > 1 && ((fIsNoteHeader() && strncmp(&(temp[4]), "Note", 4) != 0) || (!fIsNoteHeader() && strncmp(&(temp[4]), "Note", 4) == 0)))
|
||||
{
|
||||
m_headers.push_front(m_headers.back());
|
||||
m_headers.pop_back();
|
||||
//load the last
|
||||
memcpy(m_structHeader, m_headers.back(), fGetStructHeaderSize());
|
||||
}
|
||||
|
||||
//save data in the temp buffer
|
||||
for (int i = fGetStructHeaderSize(); i > 0; i--) { m_tempBuffs.push_front(temp[i - 1]); }
|
||||
delete[] temp;
|
||||
delete[] m_headers.back();
|
||||
m_headers.pop_back();
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverMicromedSystemPlusEvolution::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
|
||||
{
|
||||
if (!m_valid || m_driverCtx.isConnected()) { return false; }
|
||||
if (!libMicromed && !loadDLL()) { return false; }
|
||||
|
||||
//update register key
|
||||
if (ERROR_SUCCESS != ::RegOpenKeyEx(HKEY_CURRENT_USER, registeryKeyName, 0, KEY_WRITE, ®istryKey))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Registery key not initialized\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
char hostPort[1024];
|
||||
sprintf(hostPort, "%i", m_ServerHostPort);
|
||||
//const std::string port = std::to_string(m_ServerHostPort);
|
||||
//if (ERROR_SUCCESS != ::RegSetValueEx(registryKey, "tcpPortNumber", 0, REG_SZ, reinterpret_cast<LPBYTE>(const_cast<char*>(port.c_str())), port.size()))
|
||||
|
||||
if (ERROR_SUCCESS != ::RegSetValueEx(registryKey, "tcpPortNumber", 0, REG_SZ, reinterpret_cast<LPBYTE>(hostPort), strlen(hostPort)))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Registery key tcpPortNumber not initialized to '" << m_ServerHostPort << "'\n";
|
||||
}
|
||||
|
||||
if (tcpSendAcq == std::string())
|
||||
{
|
||||
char* acq = "1";
|
||||
if (ERROR_SUCCESS != ::RegSetValueEx(registryKey, "tcpSendAcq", 0, REG_SZ, reinterpret_cast<LPBYTE>(acq), 1))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Registery key tcpSendAcq not initialized to '1'\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (tcpServerName == std::string())
|
||||
{
|
||||
strcpy(tcpServerName, "localhost");
|
||||
if (ERROR_SUCCESS != ::RegSetValueEx(registryKey, "tcpServerName", 0, REG_SZ, reinterpret_cast<LPBYTE>(tcpServerName), strlen(tcpServerName)))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "Registery key tcpServerName not initialized to '" << tcpServerName << "'\n";
|
||||
}
|
||||
}
|
||||
|
||||
RegCloseKey(registryKey);
|
||||
registryKey = nullptr;
|
||||
}
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Configure Register key\n";
|
||||
|
||||
// Builds up server connection
|
||||
m_ConnectionServer = Socket::createConnectionServer();
|
||||
|
||||
if (!m_ConnectionServer)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_ImportantWarning << "> Could not create server socket\n";
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Server is on \n";
|
||||
|
||||
// Server start listening on defined port
|
||||
if (!m_ConnectionServer->listen(m_ServerHostPort))
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_ImportantWarning << "> Could not listen TCP port " << m_ServerHostPort << "\n";
|
||||
|
||||
// Cleans up server connection
|
||||
m_ConnectionServer->close();
|
||||
m_ConnectionServer->release();
|
||||
m_ConnectionServer = nullptr;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
m_nSamplePerSentBlock = nSamplePerSentBlock;
|
||||
m_callback = &callback;
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Server is listening on port : " << m_ServerHostPort << "\n";
|
||||
|
||||
if (m_ConnectionServer->isReadyToReceive(m_timeOutMilliseconds))
|
||||
{
|
||||
// Accept new client
|
||||
m_Connection = m_ConnectionServer->accept();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "> Time out after " << m_timeOutMilliseconds << " milliseconds\n";
|
||||
|
||||
// Cleans up server connection
|
||||
m_ConnectionServer->close();
|
||||
m_ConnectionServer->release();
|
||||
m_ConnectionServer = nullptr;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Receive Header
|
||||
if (this->myReceive(m_structHeader, fGetStructHeaderSize()) == -1) { return false; }
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Receiving Header....\n";
|
||||
|
||||
// Verify header validity
|
||||
if (!fIsHeaderValid())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Header received not in correct form : pb with fixCode\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fIsInitHeader())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Header received not in correct form : pb not receive Init information\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fGetStructHeaderInfoSize() != fGetDataLength())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error <<
|
||||
"Header received not in correct form : pb the data header Info hasn't the good size\n the structure size:" << fGetStructHeaderInfoSize() <<
|
||||
"the size of data received:" << fGetDataLength() << "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Receive Header
|
||||
if (this->myReceive(m_structHeaderInfo, fGetStructHeaderInfoSize()) == -1) { return false; }
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Header received\n";
|
||||
|
||||
m_header.setChannelCount(fGetNbOfChannels());
|
||||
|
||||
m_header.setSamplingFrequency(uint32_t(fGetMinimumSamplingRate()));
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "size for 1 channel, 1 block: " << m_nSamplePerSentBlock << "\n";
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "number of channels: " << m_header.getChannelCount() << "\n";
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Maximum sample rate =" << m_header.getSamplingFrequency() << " Hz" << "\n";
|
||||
m_sample = new float[m_header.getChannelCount() * m_nSamplePerSentBlock];
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "size of m_sample=" << (m_header.getChannelCount() * m_nSamplePerSentBlock * sizeof(float)) << "\n";
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Maximum Buffer size =" << (
|
||||
m_header.getChannelCount() * m_nSamplePerSentBlock * sizeof(signed short int)) << " Samples" << "\n";
|
||||
|
||||
if (!m_sample)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_ImportantWarning << "Could not allocate sample buffer\n";
|
||||
uninitialize();
|
||||
return false;
|
||||
}
|
||||
m_buffDataIdx = 0;
|
||||
m_posFirstSampleOfCurrentBlock = 0;
|
||||
if (m_driverCtx.getLogManager().isActive(Kernel::LogLevel_Debug))
|
||||
{
|
||||
std::stringstream info;
|
||||
fShowElectrode(&info);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << info.str();
|
||||
fShowNote(&info);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << info.str();
|
||||
fShowTrigger(&info);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << info.str();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMicromedSystemPlusEvolution::start()
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "start device\n";
|
||||
if (!m_valid || !m_driverCtx.isConnected() || m_driverCtx.isStarted() || !m_Connection) { return false; }
|
||||
m_nSamplesBlock = m_header.getChannelCount() * m_nSamplePerSentBlock;
|
||||
m_sizeInByte = fGetSizeOfEachDataInByte();
|
||||
//calculate the number max of complete samples can be contains in the buffer.
|
||||
m_buffSize = fGetStructBuffDataSize() / (m_sizeInByte * m_header.getChannelCount());
|
||||
m_buffSize = m_buffSize * (m_sizeInByte * m_header.getChannelCount());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMicromedSystemPlusEvolution::dropData()
|
||||
{
|
||||
//drop data
|
||||
uint32_t totalReceived = 0;
|
||||
|
||||
do
|
||||
{
|
||||
const uint32_t maxByteRecv = std::min(uint32_t(fGetStructBuffDataSize()), uint32_t(fGetDataLength() - totalReceived));
|
||||
if (this->myReceive((char*)m_structBuffData, maxByteRecv) == -1) { return false; }
|
||||
totalReceived += maxByteRecv;
|
||||
} while (totalReceived < fGetDataLength());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMicromedSystemPlusEvolution::loop()
|
||||
{
|
||||
if (!m_valid || !m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
if (m_Connection)
|
||||
{
|
||||
// Receive All consecutive Header
|
||||
//this->myReceive(m_structHeader, fGetStructHeaderSize());
|
||||
if (!this->receiveAllHeader())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "No Header received, an error was occurred during the data acquisition!\n";
|
||||
return false;
|
||||
}
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << ">Number header received: " << m_headers.size() << "\n";
|
||||
|
||||
while (!m_headers.empty())
|
||||
{
|
||||
if (!loadNextHeader()) { return false; }
|
||||
// Verify header validity
|
||||
if (!fIsHeaderValid())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Header received not in correct form\n";
|
||||
return false;
|
||||
}
|
||||
if (!fIsDataHeader() && !fIsNoteHeader() && !fIsTriggerHeader())
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Header received not in correct form : problem with infoType\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
//if(m_driverCtx.isStarted())
|
||||
//{
|
||||
if (fIsDataHeader())
|
||||
{
|
||||
//if the device is not start or the first block after start haven't been received, data will be dropped
|
||||
if (!m_driverCtx.isStarted())
|
||||
{
|
||||
dropData();
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Device not started, dropped data: data.len = " << fGetDataLength() << "\n";
|
||||
return true;
|
||||
}
|
||||
// Receive Data
|
||||
uint32_t maxByteRecv = 0;
|
||||
uint32_t totalReceived = 0;
|
||||
uint32_t receivedSampleCount = 0;
|
||||
do
|
||||
{
|
||||
maxByteRecv = std::min(m_buffSize, std::min(fGetDataLength() - totalReceived,
|
||||
uint32_t(m_nSamplesBlock - m_buffDataIdx * m_header.getChannelCount()) * m_sizeInByte));
|
||||
if (this->myReceive((char*)m_structBuffData, maxByteRecv) == -1) { return false; }
|
||||
receivedSampleCount = maxByteRecv / (m_sizeInByte * m_header.getChannelCount());
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Number of Samples Received:" << receivedSampleCount << "\n";
|
||||
|
||||
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
|
||||
{
|
||||
for (uint32_t j = 0; j < receivedSampleCount; ++j)
|
||||
{
|
||||
m_sample[m_buffDataIdx + j + i * m_nSamplePerSentBlock] = float(fGetDataValue(i, j));
|
||||
}
|
||||
}
|
||||
|
||||
totalReceived += maxByteRecv;
|
||||
m_buffDataIdx += receivedSampleCount;
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Convert Data: dataConvert = " << totalReceived << "/" << fGetDataLength() << "\n";
|
||||
|
||||
if (m_nSamplesBlock < m_buffDataIdx)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Data not received in correct form : problem with lenData\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_nSamplesBlock == m_buffDataIdx * m_header.getChannelCount())
|
||||
{
|
||||
m_callback->setSamples(m_sample);
|
||||
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << "Send samples back to CAcquisitionServer: samples.len = " << m_buffDataIdx << "\n";
|
||||
if (m_stimSet.getStimulationCount() > 0)
|
||||
{
|
||||
m_callback->setStimulationSet(m_stimSet);
|
||||
m_stimSet.clear();
|
||||
}
|
||||
m_posFirstSampleOfCurrentBlock += m_nSamplesBlock;
|
||||
m_buffDataIdx = 0;
|
||||
}
|
||||
} while (totalReceived < fGetDataLength());
|
||||
if (m_driverCtx.getLogManager().isActive(Kernel::LogLevel_Debug))
|
||||
{
|
||||
std::stringstream signal;
|
||||
fShowSignal(&signal);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Debug << signal.str();
|
||||
}
|
||||
}
|
||||
else if (fIsNoteHeader())
|
||||
{
|
||||
if (this->myReceive((char*)m_structBuffNote, long(fGetDataLength())) == -1) { return false; }
|
||||
std::stringstream note;
|
||||
fShowNote(¬e);
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << note.str();
|
||||
}
|
||||
else if (fIsTriggerHeader())
|
||||
{
|
||||
if (this->myReceive((char*)m_structBuffTrigger, long(fGetDataLength())) == -1) { return false; }
|
||||
|
||||
for (int i = 0; i < fGetTriggerCount(); ++i)
|
||||
{
|
||||
uint32_t sample = fGetTriggerSample(i);
|
||||
if (sample < m_posFirstSampleOfCurrentBlock)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Warning <<
|
||||
" A trigger was received too late! this trigger will not be send to the acquisition server.";
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t pos = uint32_t(sample - m_posFirstSampleOfCurrentBlock);
|
||||
uint64_t time = CTime(m_header.getSamplingFrequency(), uint64_t(pos)).time();
|
||||
m_stimSet.appendStimulation(fGetTriggerValue(i), time, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_driverCtx.getLogManager().isActive(Kernel::LogLevel_Trace))
|
||||
{
|
||||
std::stringstream trigger;
|
||||
fShowTrigger(&trigger);
|
||||
//m_driverCtx.getLogManager() << Kernel::LogLevel_Warning << "A Trigger was received but this function is not implemented. Please submit a bug report (including the acquisition server log file in debug mode)";
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << trigger.str();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMicromedSystemPlusEvolution::stop()
|
||||
{
|
||||
if (!m_valid) { return false; }
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Server stopped\n";
|
||||
|
||||
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMicromedSystemPlusEvolution::uninitialize()
|
||||
{
|
||||
if (!m_valid || !m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
if (m_sample)
|
||||
{
|
||||
delete [] m_sample;
|
||||
m_sample = nullptr;
|
||||
m_callback = nullptr;
|
||||
}
|
||||
|
||||
// Cleans up client connection
|
||||
if (m_Connection)
|
||||
{
|
||||
m_Connection->close();
|
||||
m_Connection->release();
|
||||
m_Connection = nullptr;
|
||||
}
|
||||
|
||||
// Cleans up server connection
|
||||
if (m_ConnectionServer)
|
||||
{
|
||||
m_ConnectionServer->close();
|
||||
m_ConnectionServer->release();
|
||||
m_ConnectionServer = nullptr;
|
||||
}
|
||||
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "> Server disconnected\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverMicromedSystemPlusEvolution::configure()
|
||||
{
|
||||
if (!libMicromed) { loadDLL(); }
|
||||
|
||||
CConfigurationNetworkBuilder config(Directories::getDataDir() + "/applications/acquisition-server/interface-Micromed-SystemPlusEvolution.ui");
|
||||
config.setHostPort(m_ServerHostPort);
|
||||
|
||||
if (config.configure(m_header))
|
||||
{
|
||||
m_ServerHostPort = config.getHostPort();
|
||||
m_settings.save();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // #if defined TARGET_OS_Windows
|
||||
#endif // #if defined(TARGET_HAS_ThirdPartyMicromed)
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(TARGET_HAS_ThirdPartyMicromed)
|
||||
|
||||
#include "ovasIDriver.h"
|
||||
#include "../ovasCHeader.h"
|
||||
|
||||
#include "../ovasCSettingsHelper.h"
|
||||
#include "../ovasCSettingsHelperOperators.h"
|
||||
|
||||
#if defined TARGET_OS_Windows
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <iostream>
|
||||
#include <socket/IConnectionServer.h>
|
||||
#include <list>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CDriverMicromedSystemPlusEvolution
|
||||
* \author Yann Renard (INRIA)
|
||||
*/
|
||||
class CDriverMicromedSystemPlusEvolution final : public IDriver
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CDriverMicromedSystemPlusEvolution(IDriverContext& ctx);
|
||||
~CDriverMicromedSystemPlusEvolution() override;
|
||||
const char* getName() override { return "Micromed SD LTM (through SystemPlus Evolution)"; }
|
||||
|
||||
//virtual bool isFlagSet(const EDriverFlag flag) const { return flag==EDriverFlag::IsUnstable; }
|
||||
|
||||
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
|
||||
bool uninitialize() override;
|
||||
|
||||
bool start() override;
|
||||
bool stop() override;
|
||||
bool loop() override;
|
||||
|
||||
bool isConfigurable() override { return true; }
|
||||
bool configure() override;
|
||||
const IHeader* getHeader() override { return &m_header; }
|
||||
|
||||
bool loadDLL();
|
||||
|
||||
Socket::IConnectionServer* m_ConnectionServer = nullptr;
|
||||
uint32_t m_ServerHostPort = 3000;
|
||||
Socket::IConnection* m_Connection = nullptr;
|
||||
short myReceive(char* buf, long dataLen);
|
||||
bool receiveAllHeader();
|
||||
bool loadNextHeader();
|
||||
|
||||
protected:
|
||||
|
||||
bool dropData();
|
||||
|
||||
SettingsHelper m_settings;
|
||||
|
||||
bool m_valid = true;
|
||||
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
CHeader m_header;
|
||||
|
||||
uint32_t m_nSamplePerSentBlock = 0;
|
||||
float* m_sample = nullptr;
|
||||
|
||||
uint32_t m_indexIn = 0;
|
||||
uint32_t m_indexOut = 0;
|
||||
uint32_t m_buffDataIdx = 0;
|
||||
|
||||
uint32_t m_timeOutMilliseconds = 5000;
|
||||
|
||||
char* m_structHeader = nullptr;
|
||||
char* m_structHeaderInfo = nullptr;
|
||||
unsigned short int* m_structBuffData = nullptr;
|
||||
unsigned char* m_structBuffNote = nullptr;
|
||||
unsigned char* m_structBuffTrigger = nullptr;
|
||||
uint64_t m_posFirstSampleOfCurrentBlock = 0;
|
||||
CStimulationSet m_stimSet;
|
||||
|
||||
uint32_t m_nSamplesBlock = 0;
|
||||
uint32_t m_sizeInByte = 0;
|
||||
uint32_t m_buffSize = 0;
|
||||
|
||||
std::list<char*> m_headers;
|
||||
std::list<char> m_tempBuffs;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif
|
||||
#endif
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
#if defined(TARGET_HAS_ThirdPartyNeXus)
|
||||
|
||||
#include "ovasCDriverMindMediaNeXus32B.h"
|
||||
#include "../ovasCConfigurationBuilder.h"
|
||||
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <system/ovCTime.h>
|
||||
#include <system/ovCMemory.h>
|
||||
|
||||
#if defined TARGET_OS_Windows
|
||||
|
||||
#include <cmath>
|
||||
#include <windows.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
|
||||
static const uint32_t INTERNALE_BUFFER_COUNT = 32;
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
CDriverMindMediaNeXus32B::CDriverMindMediaNeXus32B(IDriverContext& ctx)
|
||||
: IDriver(ctx), m_settings("AcquisitionServer_Driver_MindMediaNexus32B", m_driverCtx.getConfigurationManager())
|
||||
{
|
||||
m_header.setSamplingFrequency(512);
|
||||
m_header.setChannelCount(4);
|
||||
|
||||
m_settings.add("Header", &m_header);
|
||||
m_settings.load();
|
||||
}
|
||||
|
||||
void CDriverMindMediaNeXus32B::release() { delete this; }
|
||||
|
||||
const char* CDriverMindMediaNeXus32B::getName() { return "MindMedia NeXus32B"; }
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
#define _MindMedia_NeXus32B_DLLFileName_ "NeXusDLL.dll"
|
||||
|
||||
typedef void (*NeXusDLL_ProcessData)(int sampleCount, int channel, float* sample);
|
||||
typedef DWORD (*NeXusDLL_Init)(NeXusDLL_ProcessData fpProcessData);
|
||||
typedef DWORD (*NeXusDLL_Start)(DWORD* sampling);
|
||||
typedef DWORD (*NeXusDLL_Stop)();
|
||||
|
||||
static HANDLE mutex = nullptr;
|
||||
static HINSTANCE neXusDLLInstance = nullptr;
|
||||
static NeXusDLL_Init fpNeXusDLLInit = nullptr;
|
||||
static NeXusDLL_Start fpNeXusDLLStart = nullptr;
|
||||
static NeXusDLL_Stop fpNeXusDLLStop = nullptr;
|
||||
|
||||
static CDriverMindMediaNeXus32B* driver = nullptr;
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
static void processData(const int sampleCount, const int channel, float* sample)
|
||||
{
|
||||
if (driver) { driver->processData(uint32_t(sampleCount), uint32_t(channel), sample); }
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverMindMediaNeXus32B::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
|
||||
{
|
||||
if (m_driverCtx.isConnected()) { return false; }
|
||||
|
||||
const CString binPath = m_driverCtx.getConfigurationManager().expand("${Path_Bin}");
|
||||
neXusDLLInstance = ::LoadLibrary((binPath + "/" + _MindMedia_NeXus32B_DLLFileName_).toASCIIString());
|
||||
if (!neXusDLLInstance) { return false; }
|
||||
|
||||
fpNeXusDLLInit = NeXusDLL_Init(GetProcAddress(neXusDLLInstance, "InitNeXusDevice"));
|
||||
fpNeXusDLLStart = NeXusDLL_Start(GetProcAddress(neXusDLLInstance, "StartNeXusDevice"));
|
||||
fpNeXusDLLStop = NeXusDLL_Stop(GetProcAddress(neXusDLLInstance, "StopNeXusDevice"));
|
||||
m_sample = new float[m_header.getChannelCount() * nSamplePerSentBlock * INTERNALE_BUFFER_COUNT];
|
||||
|
||||
if (!fpNeXusDLLInit || !fpNeXusDLLStart || !fpNeXusDLLStop || !m_sample)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Error finding NeXus API functions / allocating sample buffer\n";
|
||||
|
||||
FreeLibrary(neXusDLLInstance);
|
||||
delete [] m_sample;
|
||||
neXusDLLInstance = nullptr;
|
||||
fpNeXusDLLInit = nullptr;
|
||||
fpNeXusDLLStart = nullptr;
|
||||
fpNeXusDLLStop = nullptr;
|
||||
m_sample = nullptr;
|
||||
mutex = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
mutex = CreateMutex(nullptr, FALSE, nullptr); // default security attributes, not initially owned, no name
|
||||
|
||||
if (!mutex)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not create synchronisation mutex\n";
|
||||
FreeLibrary(neXusDLLInstance);
|
||||
delete [] m_sample;
|
||||
neXusDLLInstance = nullptr;
|
||||
fpNeXusDLLInit = nullptr;
|
||||
fpNeXusDLLStart = nullptr;
|
||||
fpNeXusDLLStop = nullptr;
|
||||
m_sample = nullptr;
|
||||
mutex = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD error = fpNeXusDLLInit(OpenViBE::AcquisitionServer::processData);
|
||||
if (error)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not initialize device with NeXus API\n";
|
||||
FreeLibrary(neXusDLLInstance);
|
||||
delete [] m_sample;
|
||||
CloseHandle(mutex);
|
||||
neXusDLLInstance = nullptr;
|
||||
fpNeXusDLLInit = nullptr;
|
||||
fpNeXusDLLStart = nullptr;
|
||||
fpNeXusDLLStop = nullptr;
|
||||
m_sample = nullptr;
|
||||
mutex = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_callback = &callback;
|
||||
m_nSamplePerSentBlock = nSamplePerSentBlock;
|
||||
m_sampleIdx = 0;
|
||||
driver = this;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMindMediaNeXus32B::start()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
DWORD sampling = DWORD(m_header.getSamplingFrequency());
|
||||
const DWORD error = fpNeXusDLLStart(&sampling);
|
||||
if (error)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not start acquisition with NeXus API\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMindMediaNeXus32B::loop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return true; }
|
||||
|
||||
WaitForSingleObject(mutex, INFINITE);
|
||||
if (m_sampleIdx < m_nSamplePerSentBlock)
|
||||
{
|
||||
ReleaseMutex(mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
m_callback->setSamples(m_sample);
|
||||
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
|
||||
memcpy(m_sample, m_sample + m_header.getChannelCount() * m_nSamplePerSentBlock, m_header.getChannelCount() * m_nSamplePerSentBlock * sizeof(float));
|
||||
m_sampleIdx -= m_nSamplePerSentBlock;
|
||||
ReleaseMutex(mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMindMediaNeXus32B::stop()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
const DWORD error = fpNeXusDLLStop();
|
||||
if (error)
|
||||
{
|
||||
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Could not stop acquisition with NeXus API\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDriverMindMediaNeXus32B::uninitialize()
|
||||
{
|
||||
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
|
||||
|
||||
FreeLibrary(neXusDLLInstance);
|
||||
delete [] m_sample;
|
||||
CloseHandle(mutex);
|
||||
m_sample = nullptr;
|
||||
m_callback = nullptr;
|
||||
neXusDLLInstance = nullptr;
|
||||
fpNeXusDLLInit = nullptr;
|
||||
fpNeXusDLLStart = nullptr;
|
||||
fpNeXusDLLStop = nullptr;
|
||||
driver = nullptr;
|
||||
mutex = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//___________________________________________________________________//
|
||||
// //
|
||||
|
||||
bool CDriverMindMediaNeXus32B::configure()
|
||||
{
|
||||
CConfigurationBuilder config(Directories::getDataDir() + "/applications/acquisition-server/interface-MindMedia-NeXus32B.ui");
|
||||
if (!config.configure(m_header)) { return false; }
|
||||
m_settings.save();
|
||||
return true;
|
||||
}
|
||||
|
||||
void CDriverMindMediaNeXus32B::processData(uint32_t sampleCount, uint32_t channel, float* sample)
|
||||
{
|
||||
WaitForSingleObject(mutex, INFINITE);
|
||||
|
||||
if (m_sampleIdx < m_nSamplePerSentBlock * INTERNALE_BUFFER_COUNT)
|
||||
{
|
||||
const uint32_t bufferIdx = m_sampleIdx / m_nSamplePerSentBlock;
|
||||
const uint32_t sampleIdx = m_sampleIdx % m_nSamplePerSentBlock;
|
||||
|
||||
for (size_t i = 0; i < m_header.getChannelCount(); ++i)
|
||||
{
|
||||
m_sample[bufferIdx * m_nSamplePerSentBlock * m_header.getChannelCount() + i * m_nSamplePerSentBlock + sampleIdx] = sample[i];
|
||||
}
|
||||
|
||||
m_sampleIdx++; // Please don't overflow :o)
|
||||
}
|
||||
|
||||
ReleaseMutex(mutex);
|
||||
}
|
||||
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
#endif // defined TARGET_OS_Windows
|
||||
#endif
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(TARGET_HAS_ThirdPartyNeXus)
|
||||
|
||||
#include "ovasIDriver.h"
|
||||
#include "../ovasCHeader.h"
|
||||
|
||||
#include "../ovasCSettingsHelper.h"
|
||||
#include "../ovasCSettingsHelperOperators.h"
|
||||
|
||||
#if defined TARGET_OS_Windows
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace AcquisitionServer {
|
||||
/**
|
||||
* \class CDriverMindMediaNeXus32B
|
||||
* \author Yann Renard (INRIA)
|
||||
*/
|
||||
class CDriverMindMediaNeXus32B : public IDriver
|
||||
{
|
||||
public:
|
||||
|
||||
CDriverMindMediaNeXus32B(IDriverContext& ctx);
|
||||
virtual void release();
|
||||
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 { return true; }
|
||||
bool configure() override;
|
||||
const IHeader* getHeader() override { return &m_header; }
|
||||
|
||||
virtual void processData(uint32_t sampleCount, uint32_t channel, float* sample);
|
||||
|
||||
protected:
|
||||
|
||||
SettingsHelper m_settings;
|
||||
|
||||
IDriverCallback* m_callback = nullptr;
|
||||
CHeader m_header;
|
||||
|
||||
uint32_t m_nSamplePerSentBlock = 0;
|
||||
float* m_sample = nullptr;
|
||||
uint32_t m_sampleIdx = 0;
|
||||
};
|
||||
} // namespace AcquisitionServer
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif // defined TARGET_OS_Windows
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user