This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
@@ -0,0 +1,403 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkAdjustment" id="adjustment1">
<property name="lower">24</property>
<property name="upper">27</property>
<property name="value">24</property>
<property name="step_increment">3</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="upper">100</property>
<property name="value">18</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment3">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment4">
<property name="upper">255</property>
<property name="value">0</property>
<property name="lower">0</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkListStore" id="model1">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">unspecified</col>
</row>
<row>
<col id="0" translatable="yes">female</col>
</row>
<row>
<col id="0" translatable="yes">male</col>
</row>
<row>
<col id="0" translatable="yes">unknown</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model2">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">500</col>
</row>
<row>
<col id="0" translatable="yes">250</col>
</row>
</data>
</object>
<object class="GtkDialog" id="openvibe-acquisition-server-settings">
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">Device configuration</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="spacing">8</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_apply">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_title">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">mBrainTrain configuration (24 channels)</property>
<property name="use_markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">8</property>
<property name="spacing">8</property>
<child>
<object class="GtkHBox" id="hbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">8</property>
<child>
<object class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="n_rows">6</property>
<property name="n_columns">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkSpinButton" id="spinbutton_number_of_channels">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="sensitive">False</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_number_of_channels">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Number of channels</property>
<property name="use_markup">True</property>
<property name="justify">center</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_port_number">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Port number :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_port_number">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment2</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_gender">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">model1</property>
<child>
<object class="GtkCellRendererText" id="renderer1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_age">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment2</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_identifier">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="adjustment">adjustment3</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_gender">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Gender :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_age">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Age :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_identifier">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Identifier :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
</child>
<child>
<object class="GtkComboBox" id="combobox_sampling_frequency">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="model">model2</property>
<child>
<object class="GtkCellRendererText" id="renderer3"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_sampling_frequency">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Sampling frequency :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_change_channel_names">
<property name="label" translatable="yes">Change channel names</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-10">button_apply</action-widget>
<action-widget response="-6">button_cancel</action-widget>
</action-widgets>
</object>
</interface>
@@ -0,0 +1,77 @@
#include "ovasCConfigurationMBTSmarting.h"
#include <string.h>
#include <iostream>
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)
{
CConfigurationMBTSmarting* config=static_cast<CConfigurationMBTSmarting*>(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
CConfigurationMBTSmarting::CConfigurationMBTSmarting(IDriverContext& ctx, const char* gtkBuilderFilename, uint32_t& rConnectionId)
: CConfigurationBuilder(gtkBuilderFilename), m_driverCtx(ctx), m_connectionID(rConnectionId)
{
//m_listStore = gtk_list_store_new(1, G_TYPE_STRING);
}
CConfigurationMBTSmarting::~CConfigurationMBTSmarting()
{
//g_object_unref(m_listStore);
}
bool CConfigurationMBTSmarting::preConfigure()
{
if (! CConfigurationBuilder::preConfigure()) { return false; }
// Apply previous port number
GtkSpinButton* buttonPortNumber = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_port_number"));
gtk_spin_button_set_value(buttonPortNumber, m_connectionID);
// 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.
return true;
}
bool CConfigurationMBTSmarting::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>
GtkSpinButton* buttonPortNumber = GTK_SPIN_BUTTON(gtk_builder_get_object(m_builder, "spinbutton_port_number"));
gtk_spin_button_update(buttonPortNumber);
m_connectionID = gtk_spin_button_get_value_as_int(buttonPortNumber);
}
// normal header is filled (Subject ID, Age, Gender, channels, sampling frequency), ressources are realesed
return CConfigurationBuilder::postConfigure();
}
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,46 @@
#pragma once
#include "../ovasCConfigurationBuilder.h"
#include "ovasIDriver.h"
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CConfigurationMBTSmarting
* \author mBrainTrain dev team (mBrainTrain)
* \date Tue Oct 14 16:09:43 2014
* \brief The CConfigurationMBTSmarting handles the configuration dialog specific to the MBTSmarting device.
*
* TODO: details
*
* \sa CDriverMBTSmarting
*/
class CConfigurationMBTSmarting final : public CConfigurationBuilder
{
public:
// you may have to add to your constructor some reference parameters
// for example, a connection ID:
CConfigurationMBTSmarting(IDriverContext& ctx, const char* gtkBuilderFilename, uint32_t& rConnectionId);
//CConfigurationMBTSmarting(IDriverContext& ctx, const char* gtkBuilderFilename);
bool preConfigure() override;
bool postConfigure() override;
~CConfigurationMBTSmarting() override;
protected:
IDriverContext& m_driverCtx;
private:
/*
* Insert here all specific attributes, such as a connection ID.
* use references to directly modify the corresponding attribute of the driver
* Example:
*/
uint32_t& m_connectionID;
//::GtkListStore* m_listStore;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,240 @@
#include "ovasCDriverMBTSmarting.h"
#include "ovasCConfigurationMBTSmarting.h"
#include <toolkit/ovtk_all.h>
#include <string>
#include <sstream>
namespace OpenViBE {
namespace AcquisitionServer {
//___________________________________________________________________//
// //
CDriverMBTSmarting::CDriverMBTSmarting(IDriverContext& ctx)
: IDriver(ctx)
, m_settings("AcquisitionServer_Driver_MBTSmarting", m_driverCtx.getConfigurationManager())
{
m_header.setSamplingFrequency(500);
m_header.setChannelCount(27);
// 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("ConnectionID", &m_connectionID);
m_settings.load();
}
//___________________________________________________________________//
// //
bool CDriverMBTSmarting::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
{
if (m_driverCtx.isConnected() || !m_header.isChannelCountSet() || !m_header.isSamplingFrequencySet()) { 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()];
if (!m_sample)
{
delete [] m_sample;
m_sample = nullptr;
return false;
}
// ...
// initialize hardware and get
// available header information
// from it
// Using for example the connection ID provided by the configuration (m_connectionID)
// ...
m_pSmartingAmp.reset(new SmartingAmp);
std::stringstream port_ss;
#ifdef TARGET_OS_Windows
port_ss << "COM" << m_connectionID;
#elif defined TARGET_OS_Linux
port_ss << "/dev/rfcomm" << m_connectionID;
#endif
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Attempting to Connect to Device at : " << port_ss.str() << "\n";
std::string port(port_ss.str());
const bool connected = m_pSmartingAmp->connect(port);
if (connected)
{
// set sampling frequency
switch (m_header.getSamplingFrequency())
{
case 250: m_pSmartingAmp->send_command(FREQUENCY_250);
m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Setting the sampling frequency at " << 250 << "\n";
break;
case 500: m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Setting the sampling frequency at " << 500 << "\n";
m_pSmartingAmp->send_command(FREQUENCY_500);
break;
default: m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Only sampling frequencies 250 and 500 are supported\n";
return false;
}
// Declare channel units
for (uint32_t c = 0; c < 24; ++c)
{
m_header.setChannelUnits(c, OVTK_UNIT_Volts, OVTK_FACTOR_Micro); // signal channels
}
m_header.setChannelUnits(24, OVTK_UNIT_Degree_Per_Second, OVTK_FACTOR_Base); // gyroscope outputs
m_header.setChannelUnits(25, OVTK_UNIT_Degree_Per_Second, OVTK_FACTOR_Base);
m_header.setChannelUnits(26, OVTK_UNIT_Degree_Per_Second, OVTK_FACTOR_Base);
m_header.setChannelName(24, "Gyro 1");
m_header.setChannelName(25, "Gyro 2");
m_header.setChannelName(26, "Gyro 3");
// Saves parameters
m_callback = &callback;
m_nSamplePerSentBlock = nSamplePerSentBlock;
return true;
}
return false;
}
bool CDriverMBTSmarting::start()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// ...
// request hardware to start
// sending data
// ...
m_pSmartingAmp->send_command(ON);
m_byteArray.clear();
sample_number = 1;
latency = 1;
return true;
}
bool CDriverMBTSmarting::loop()
{
if (!m_driverCtx.isConnected()) { return false; }
if (!m_driverCtx.isStarted()) { return true; }
const 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
//...
unsigned char* receiveBuffer = new unsigned char[MAX_PACKAGE_SIZE];
const int readed = m_pSmartingAmp->read(receiveBuffer, MAX_PACKAGE_SIZE);
for (int i = 0; i < readed; ++i)
{
if (!m_byteArray.empty())
{
m_byteArray.push_back(receiveBuffer[i]);
if (m_byteArray.size() == 83)
{
if (m_byteArray[82] == '<')
{
if (sample_number % 5000 == 0)
{
sample_number = 1;
if (m_driverCtx.getDriftSampleCount() < 2)
{
m_sample = m_pSmartingAmp->convert_data(m_byteArray);
m_callback->setSamples(m_sample, 1);
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
}
}
else
{
sample_number++;
m_sample = m_pSmartingAmp->convert_data(m_byteArray);
m_callback->setSamples(m_sample, 1);
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
}
}
m_byteArray.clear();
}
}
if (m_byteArray.empty() && receiveBuffer[i] == '>') { m_byteArray.push_back(receiveBuffer[i]); }
}
if (latency == 300)
{
latency = 0;
m_driverCtx.setInnerLatencySampleCount(-m_driverCtx.getDriftSampleCount());
}
else { if (latency != 0) { latency++; } }
// ...
// receive events from hardware
// and put them the correct way in a CStimulationSet object
//...
m_callback->setStimulationSet(stimSet);
return true;
}
bool CDriverMBTSmarting::stop()
{
if (!m_driverCtx.isConnected() || !m_driverCtx.isStarted()) { return false; }
// ...
// request the hardware to stop
// sending data
// ...
m_driverCtx.setInnerLatencySampleCount(0);
m_pSmartingAmp->off();
return true;
}
bool CDriverMBTSmarting::uninitialize()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// ...
// uninitialize hardware here
// ...
m_pSmartingAmp->disconnect();
delete [] m_sample;
m_sample = nullptr;
m_callback = nullptr;
return true;
}
//___________________________________________________________________//
// //
bool CDriverMBTSmarting::configure()
{
// Change this line if you need to specify some references to your driver attribute that need configuration, e.g. the connection ID.
CConfigurationMBTSmarting config(m_driverCtx, Directories::getDataDir() + "/applications/acquisition-server/interface-MBTSmarting.ui",
m_connectionID);
if (!config.configure(m_header)) { return false; }
m_settings.save();
return true;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,73 @@
#pragma once
#include "ovasIDriver.h"
#include "../ovasCHeader.h"
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
#include "ovasCSmartingAmp.h"
namespace OpenViBE {
namespace AcquisitionServer {
/**
* \class CDriverMBTSmarting
* \author mBrainTrain dev team (mBrainTrain)
* \date Tue Oct 14 16:09:43 2014
* \brief The CDriverMBTSmarting allows the acquisition server to acquire data from a MBTSmarting device.
*
* TODO: details
*
* \sa CConfigurationMBTSmarting
*/
class CDriverMBTSmarting final : public IDriver
{
public:
explicit CDriverMBTSmarting(IDriverContext& ctx);
~CDriverMBTSmarting() override {}
const char* getName() override { return "mBrainTrain Smarting"; }
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; } // change to false if your device is not configurable
bool configure() override;
const IHeader* getHeader() override { return &m_header; }
#ifdef TARGET_OS_Windows
bool isFlagSet(const EDriverFlag /*flag*/) const override { return false; }
#elif defined TARGET_OS_Linux
bool isFlagSet(const EDriverFlag /*flag*/) const override { return true; }
#endif
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;
float* m_sample = nullptr;
private:
/*
* Insert here all specific attributes, such as USB port number or device ID.
* Example :
*/
uint32_t m_connectionID = 1;
std::shared_ptr<SmartingAmp> m_pSmartingAmp;
std::vector<unsigned char> m_byteArray;
int sample_number = 0;
int latency = 0;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
@@ -0,0 +1,431 @@
#include "ovasCSmartingAmp.h"
#include <system/ovCTime.h>
#include <boost/bind.hpp>
#include <thread>
#include <functional>
#include <iostream>
#define CHANNEL_SCALAR_FACTOR 2.235174445530706e-2
#define GYRO_SCALAR_FACTOR 0.00875
SmartingAmp::SmartingAmp()
{
m_port.reset(new boost::asio::serial_port(m_io));
m_timer.reset(new boost::asio::deadline_timer(m_io));
}
void SmartingAmp::disconnect()
{
if (!m_port->is_open()) { return; }
m_port->close();
}
void SmartingAmp::write(const unsigned char* data, const size_t& size) const
{
boost::system::error_code code;
m_port->write_some(boost::asio::buffer(data, size), code);
}
int SmartingAmp::read(unsigned char* data, const size_t size) const
{
boost::system::error_code code;
return m_port->read_some(boost::asio::buffer(data, size), code);
}
void SmartingAmp::acquire()
{
if (m_port.get() == nullptr || !m_port->is_open()) { return; }
m_port->async_read_some(boost::asio::buffer(m_receiveBuffer, MAX_PACKAGE_SIZE),
boost::bind(&SmartingAmp::onReceive, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void SmartingAmp::onReceive(const boost::system::error_code& ec, const size_t bytesTransferred)
{
std::lock_guard<std::mutex> m(m_on_receive_lock);
if (m_port.get() == nullptr || !m_port->is_open()) { return; }
if (ec)
{
//acquire();
return;
}
for (uint32_t i = 0; i < bytesTransferred; ++i)
{
if (!m_byteArray.empty())
{
m_byteArray.push_back(m_receiveBuffer[i]);
if (m_byteArray.size() == 83)
{
if (m_byteArray[82] == '<')
{
float* sample = convert_data(m_byteArray);
m_byteArray.clear();
m_receivedSamples++;
m_samples_lock.lock();
m_samplesBuffer.push(sample);
m_samples_lock.unlock();
}
else
{
m_failedSamples++;
m_byteArray.clear();
}
}
}
if (m_byteArray.empty() && m_receiveBuffer[i] == '>') { m_byteArray.push_back(m_receiveBuffer[i]); }
}
acquire();
}
std::pair<unsigned char*, int> SmartingAmp::make_command(const Command command)
{
unsigned char* cmd;
std::pair<unsigned char*, size_t> out;
switch (command)
{
case ON: cmd = new unsigned char[4];
cmd[0] = '>';
cmd[1] = 'O';
cmd[2] = 'N';
cmd[3] = '<';
out = std::make_pair(cmd, 4);
break;
case OFF: cmd = new unsigned char[5];
cmd[0] = '>';
cmd[1] = 'O';
cmd[2] = 'F';
cmd[3] = 'F';
cmd[4] = '<';
out = std::make_pair(cmd, 5);
break;
case TEST: cmd = new unsigned char[6];
cmd[0] = '>';
cmd[1] = 'T';
cmd[2] = 'E';
cmd[3] = 'S';
cmd[4] = 'T';
cmd[5] = '<';
out = std::make_pair(cmd, 6);
break;
case NORMAL: cmd = new unsigned char[8];
cmd[0] = '>';
cmd[1] = 'N';
cmd[2] = 'O';
cmd[3] = 'R';
cmd[4] = 'M';
cmd[5] = 'A';
cmd[6] = 'L';
cmd[7] = '<';
out = std::make_pair(cmd, 8);
break;
case SELECT_CHANNELS: cmd = new unsigned char[8];
cmd[0] = '>';
cmd[1] = 'S';
cmd[2] = 'C';
cmd[3] = ';';
cmd[4] = 0xFF;
cmd[5] = 0xFF;
cmd[6] = 0xFF;
cmd[7] = '<';
out = std::make_pair(cmd, 8);
break;
case REFON: cmd = new unsigned char[7];
cmd[0] = '>';
cmd[1] = 'R';
cmd[2] = 'E';
cmd[3] = 'F';
cmd[4] = 'O';
cmd[5] = 'N';
cmd[6] = '<';
out = std::make_pair(cmd, 7);
break;
case REFOFF: cmd = new unsigned char[8];
cmd[0] = '>';
cmd[1] = 'R';
cmd[2] = 'E';
cmd[3] = 'F';
cmd[4] = 'O';
cmd[5] = 'F';
cmd[6] = 'F';
cmd[7] = '<';
out = std::make_pair(cmd, 8);
break;
case IMPON: cmd = new unsigned char[7];
cmd[0] = '>';
cmd[1] = 'I';
cmd[2] = 'M';
cmd[3] = 'P';
cmd[4] = 'O';
cmd[5] = 'N';
cmd[6] = '<';
out = std::make_pair(cmd, 7);
break;
case IMPOFF: cmd = new unsigned char[8];
cmd[0] = '>';
cmd[1] = 'I';
cmd[2] = 'M';
cmd[3] = 'P';
cmd[4] = 'O';
cmd[5] = 'F';
cmd[6] = 'F';
cmd[7] = '<';
out = std::make_pair(cmd, 8);
break;
case FREQUENCY_250: cmd = new unsigned char[5];
cmd[0] = '>';
cmd[1] = '2';
cmd[2] = '5';
cmd[3] = '0';
cmd[4] = '<';
out = std::make_pair(cmd, 5);
break;
case FREQUENCY_500: cmd = new unsigned char[5];
cmd[0] = '>';
cmd[1] = '5';
cmd[2] = '0';
cmd[3] = '0';
cmd[4] = '<';
out = std::make_pair(cmd, 5);
break;
case NOISE: cmd = new unsigned char[7];
cmd[0] = '>';
cmd[1] = 'N';
cmd[2] = 'O';
cmd[3] = 'I';
cmd[4] = 'S';
cmd[5] = 'E';
cmd[6] = '<';
out = std::make_pair(cmd, 7);
break;
}
return out;
}
CommandStatus SmartingAmp::send_command(const Command cmd)
{
const std::pair<unsigned char*, size_t> newCmd = make_command(cmd);
write(newCmd.first, newCmd.second);
unsigned char* response = new unsigned char[OK_RESPONSE_SIZE];
const int readed = read(response, OK_RESPONSE_SIZE);
return command_status(response, readed);
}
bool SmartingAmp::connect(std::string& portName)
{
boost::system::error_code errorCode;
m_port->open(portName, errorCode);
if (errorCode)
{
std::cout << "error : port->open() failed...com_port_name = " << portName << ", e=" << errorCode.message() << std::endl;
disconnect();
// m_port->open(portName, error_code);
return false;
}
try
{
m_port->set_option(boost::asio::serial_port_base::baud_rate(921600));
m_port->set_option(boost::asio::serial_port_base::character_size(8));
m_port->set_option(boost::asio::serial_port_base::stop_bits(boost::asio::serial_port_base::stop_bits::one));
m_port->set_option(boost::asio::serial_port_base::parity(boost::asio::serial_port_base::parity::none));
m_port->set_option(boost::asio::serial_port_base::flow_control(boost::asio::serial_port_base::flow_control::hardware));
}
catch (const std::exception&)
{
disconnect();
return false;
}
// NOTE: After connection is established wait 3s and then you can start using the amp
System::Time::sleep(3000);
// NOTE: Leaving MAX_PORT_SIZE bytes to read for the first time because we are not sure
// how many bytes are in serial port buffer
read_with_timeout(MAX_PORT_SIZE, 3000);
if (m_bytes_readed > 0) { if (m_commandReceiveBuffer[m_bytes_readed - 1] == 'g') { if (send_command(SELECT_CHANNELS) == Success) { return true; } } }
disconnect();
return false;
}
CommandStatus SmartingAmp::command_status(const unsigned char* response, const int readed)
{
if (readed < 4) { return Failure; }
char* msg = new char[5];
msg[0] = response[readed - 4];
msg[1] = response[readed - 3];
msg[2] = response[readed - 2];
msg[3] = response[readed - 1];
msg[4] = '\0';
if (!strcmp(msg, ">OK<")) { return Success; }
// TODO: Implement timeout expired method
return Failure;
}
bool SmartingAmp::start()
{
send_command(ON);
m_samplesBuffer = std::queue<float*>();
m_failedSamples = 0;
m_receivedSamples = 0;
// start reading
acquire();
auto f = [this]() { this->m_io.run(); };
// auto f = boost::bind(&boost::asio::io_service::run, &m_io);
acquire_t.reset(new std::thread(f));
return true;
}
float* SmartingAmp::get_sample()
{
if (!m_samplesBuffer.empty())
{
m_samples_lock.lock();
float* sample = m_samplesBuffer.front();
m_samplesBuffer.pop();
m_samples_lock.unlock();
return sample;
}
return nullptr;
}
bool SmartingAmp::stop()
{
// NOTE: Stop would rarely return SUCCESS
off();
try
{
// cancel all async operations
m_io.stop();
m_io.reset();
// TODO: Implement proper stopping of the acquiring thread
}
catch (std::exception&)
{
// to do
}
// NOTE: Currently, I am very unhappy with flushing
// It slows down everything and after 6 play/stops open vibe is blocked
// flush();
//std::cout << "Failed " << m_failedSamples << std::endl;
//std::cout << "Received " << m_receivedSamples << std::endl;
return false;
}
float* SmartingAmp::convert_data(std::vector<unsigned char> in)
{
float* converted = new float[30];
// channel data
for (int i = 0; i < 24; ++i) { converted[i] = get_channel_value(in[3 * i + 1], in[3 * i + 2], in[3 * i + 3]); }
// gyroX
converted[24] = get_gyro_value(in[74], in[75]);
// gyroY
converted[25] = get_gyro_value(in[76], in[77]);
// gyroZ
converted[26] = get_gyro_value(in[78], in[79]);
// counter
converted[27] = float(in[73]);
// battery
converted[28] = float(in[80] & 0x7F);//add baterry data, removing highest bit, because it's used for impedance measurement
converted[29] = in[81]; //add checksum data
return converted;
}
float SmartingAmp::get_channel_value(const unsigned char first, const unsigned char second, const unsigned char third)
{
const int firstByte = int(first) & 0xff;
const int secondByte = int(second) & 0xff;
const int thirdByte = int(third) & 0xff;
int channelValue = (firstByte << 16) + (secondByte << 8) + thirdByte;
// Checking if value is positive or negative
if (channelValue > 0x007FFFFF) { channelValue = channelValue - 0x01000000; }
return float(channelValue) * float(CHANNEL_SCALAR_FACTOR);
}
float SmartingAmp::get_gyro_value(const unsigned char first, const unsigned char second)
{
const int firstByte = int(first) & 0xff;
const int secondByte = int(second) & 0xff;
int out = (firstByte << 8) + secondByte;
if (out > 0x00007FFF) { out = out - 0x00008000; }
else { out = out + 0x00008000; }
return float(out) * float(GYRO_SCALAR_FACTOR);
}
void SmartingAmp::read_with_timeout(const int size, const size_t timeout)
{
// Asynchronously read.
if (m_port.get() == nullptr || !m_port->is_open()) { return; }
m_bytes_readed = 0;
m_port->async_read_some(boost::asio::buffer(m_commandReceiveBuffer, size),
boost::bind(&SmartingAmp::read_complete, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
// Setup a deadline time to implement our timeout.
m_timer->expires_from_now(boost::posix_time::milliseconds(timeout));
m_timer->async_wait(boost::bind(&SmartingAmp::timeout_expired, this, boost::asio::placeholders::error));
// This will block until a character is read
// or until it is cancelled.
m_io.run();
m_io.stop();
// After a timeout & cancel it seems we need
// to do a reset for subsequent reads to work.
m_port->get_io_service().reset();
}
// Called when the timer's deadline expires.
void SmartingAmp::timeout_expired(const boost::system::error_code& /*error*/) { m_io.stop(); }
// Called when an async read completes or has been cancelled
void SmartingAmp::read_complete(const boost::system::error_code& error, const size_t bytesTransferred)
{
// IMPORTANT NOTE: when timer expires, and port is canceled, read complete is
// called for the last time. Its extremly important to cancel the timer even though
// it has already expired. So, here it goes
if (error) { return; }
m_bytes_readed = bytesTransferred;
m_timer->cancel();
}
void SmartingAmp::off()
{
const std::pair<unsigned char*, size_t> transformedCmd = make_command(OFF);
write(transformedCmd.first, transformedCmd.second);
}
@@ -0,0 +1,88 @@
#ifndef SMARTINGAMP_H
#define SMARTINGAMP_H
#ifndef _WIN32_WINNT // Minimum platform is Windows 7
#define _WIN32_WINNT 0x0601
#endif
#include <stdexcept>
#include <queue>
#include <string>
#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
#include <boost/system/error_code.hpp>
#include <boost/utility.hpp>
#include <memory> // shared_ptr
#include <mutex>
#include <thread>
#define OK_RESPONSE_SIZE 4
#define MAX_PACKAGE_SIZE 4096
#define MAX_PORT_SIZE 1000000
#define BOOST_ASIO_ENABLE_HANDLER_TRACKING
enum Command { SELECT_CHANNELS, ON, OFF, TEST, NORMAL, REFON, REFOFF, IMPON, IMPOFF, FREQUENCY_250, FREQUENCY_500, NOISE };
enum CommandStatus { Success, Failure, TimeoutExpired };
/**
* \class SmartingAmp
* \author mBrainTrain dev team (mBrainTrain)
* \brief Communicates with a MBTSmarting device.
*/
class SmartingAmp : boost::noncopyable
{
public:
SmartingAmp();
~SmartingAmp() {}
bool connect(std::string& portName);
void disconnect();
bool start();
bool stop();
/* NOTE: off doesn't wait for response */
void off();
CommandStatus send_command(Command cmd);
CommandStatus command_status(const unsigned char* response, int readed);
void write(const unsigned char* data, const size_t& size) const;
int read(unsigned char* data, size_t size) const;
void acquire();
void onReceive(const boost::system::error_code& ec, size_t bytesTransferred);
float* get_sample();
float* convert_data(std::vector<unsigned char> in);
void read_with_timeout(int size, size_t timeout);
void read_complete(const boost::system::error_code& error, size_t bytesTransferred);
void timeout_expired(const boost::system::error_code& error);
float get_channel_value(unsigned char first, unsigned char second, unsigned char third);
float get_gyro_value(unsigned char first, unsigned char second);
private:
std::pair<unsigned char*, int> make_command(Command command);
boost::asio::io_service m_io;
std::shared_ptr<boost::asio::serial_port> m_port;
unsigned char m_receiveBuffer[MAX_PACKAGE_SIZE];
unsigned char m_commandReceiveBuffer[MAX_PORT_SIZE];
std::queue<float*> m_samplesBuffer;
std::vector<unsigned char> m_byteArray;
std::mutex m_on_receive_lock;
std::mutex m_read_complete_lock;
std::mutex m_samples_lock;
std::mutex m_bytes_readed_lock;
std::mutex m_timer_expired_lock;
int m_failedSamples = 0;
int m_receivedSamples = 0;
std::shared_ptr<std::thread> acquire_t;
std::shared_ptr<boost::asio::deadline_timer> m_timer;
// read with timeout
int m_bytes_readed = 0;
};
#endif