BrainBit foe developers Visit website
Home
SDK
Device

Tutorial C++

Software development kit for Windows operating system is represented as native C++ libraries and .NET assemblies.

 

Platform requirements

OS: Windows 10 1803 or higher is required.

Software: Visual Studio 2015 or higher,  Download link

Language standard: C++ 17

CMake v3.3 or higher.  Download link

Hardware: Bluetooth v4.0, External USB Module with Chipset CSR8510 is recommended.

 

SDK Installer

To get latest release installers, use the following links:

neurosdk-setup-win-x86-v1.4.5.exe

neurosdk-setup-win-x64-v1.4.5.exe

These installers will create environment variables NEUROSDK_86 or/and NEUROSDK_64 linking the path to installed libraries. Installed samples use these variables to get libraries and it can be used in your custom project configurations. It’s also recommended to leave PATH-variable checkbox checked, to add SDK library directory to PATH.

 

Running С++ example applications

By default, example applications source code is installed in user Documents directory in samples\cpp subfolder. You need CMake to create Visual Studio projects for each sample. To create project run CMake and set source and build directories. Set CMakeLists.txt containing directory as a source directory and select directory for VisualStudio generated project and press Configure.

Select code generator for 32 or 64 bit architecture and click Finish.

After generating is done click Generate to create project files and then click Open Project.

In the menu bar go to Build->Build Solution to build all examples or just press F6.

Mark selected project in solution explorer as Startup Project.

Click Start or press F5 to run an application

 

Scanning for devices

To find nearby Brainbit devices use Neuro::DeviceEnumerator class. To create an instance of enumerator use Neuro::make_device_enumerator function with template parameter Neuro::Brainbit.

    
auto enumerator = Neuro::make_device_enumerator<Neuro::Brainbit>();
auto notifierHandle = enumerator.subscribeDeviceListChanged([&]() {
	onDeviceListChanged(enumerator.devices());
});
    


The function passed to subscribeDeviceListChanged method will be called when a new device appears or one of the found devices disappears from range. Notifications will be received only when notifierHandle is alive, so it must be stored unless you want to stop receiving notifications. DeviceEnumerator always works and scans for devices during its lifetime.

To get information about nearby devices, use method devices. You could read it every time onDeviceListChanged function is called. This method returns a vector of simple device information records containing device name and address.

    
void onDeviceListChanged(const std::vector<Neuro::DeviceInfo>& devices){
	for (const auto &deviceInfo : devices) {
		std::cout << deviceInfo << std::endl;
	}
}
    

  

DeviceInfo structure contains device name and address

    
struct DeviceInfo {
    std::string Name;
    DeviceAddressType Address;
};
    

 

Connecting to the device 

To receive data from the Brainbit device and to send commands to it first you need to connect to it

    
auto device = Neuro::Device(deviceInfo);
device.connect();
//do work
device.disconnect();
    


You could read the device state to check whether it is connected or not. You also could subscribe ParameterChanged event to receive notifications when the device goes out of range. 

    
auto parameterChangedHandler = device.setParamChangedCallback([](auto parameter) {
		if (parameter == Neuro::Parameter::State) {
			
		}
});
auto deviceState = device.readParam<Neuro::Parameter::State>();
if (deviceState != Neuro::DeviceState::Connected) {
		device.connect();
}
    

 

When the device is connected you could read lists of supported parameters, signal channels, available commands. You could read the device name and address in string format and firmware version. 

    
var name = device.readParam<Neuro::Parameter::Name>();
var address = device.readParam<Neuro::Parameter::Address>();
var firmwareVersion = device.readParam<Neuro::Parameter::FirmwareVersion>();
    


To get list of command and channels use appropriate properties. 

    
auto commands = device.commands();
for (auto& cmd : commands) {
	std::cout << "-" << to_string(cmd) << std::endl;
}
auto channels = device.channels();
for (auto& channel : channels) {
	std::cout << "-" << channel.getName() << std::endl;
}
    


Retrieving signal data 

For Brainbit device, actual channels are Signal, Battery and Resistance. To subscribe for new data notifications use subscribeDataReceived method of Device class. There are four commands for managing device measuring state: StartSignal, StopSignal, StartResist, StopResist. Battery data channel contains data as int values, Resistance and Signal channels as double values. Write callback methods to handle new data. Information about the channel which triggered notification is contained in notification parameter.

    
for (const auto &channelInfo : device.channels()) {
	constexpr auto BatteryType = Neuro::ChannelInfo::Type::Battery;
	if (channelInfo.getType() == BatteryType) {
		auto subscriberHandle = device.subscribeDataReceived<BatteryType>([](const auto &battery_charge) {
		  	//do something
		  	//save subscriber handle to receive notifications!
		}, channelInfo);
	}

	constexpr auto SignalType = Neuro::ChannelInfo::Type::Signal;
	if (channelInfo.getType() == SignalType) {
		auto subscriberHandle = device.subscribeDataReceived<SignalType>([](const auto &data) {
		  	//do something
		  	//save subscriber handle to receive notifications!
		}, channelInfo);
	}
}

device.execute(Neuro::Command::StartSignal);
    

 

To simplify receiving and storing signal data you could use Channel buffer classes. There is a set of different channel buffers in the SDK. One of them is called EegChannel. This buffer receives data from the device and applies special filters to signal to get perfect EEG signal. Pass offset (samples count from buffer start) and length to readData method to get EEG signal.

    
auto devicePtr = std::make_shared<Neuro::Device>(deviceInfo);
for (const auto &channelInfo : devicePtr->channels()) {
	constexpr auto SignalType = Neuro::ChannelInfo::Type::Signal;
	if (channelInfo.getType() == SignalType) {
		auto eegChannel = Neuro::EegChannel(devicePtr, channelInfo);
		auto lengthChangedHandler = eegChannel.subscribeLengthChanged([](auto length) {
					//do work
		});
		auto length = eegChannel.totalLength();
		auto eegData = eegChannel.readData(0, length); //read all data from channel buffer
	}
}
    

You could also use a channel buffer without filters or select filters you need. There are different variants of Signal channel to do it. You could use following filters for Brainbit signals: LowPass_30Hz_SF250, HighPass_2Hz_SF250, BandStop_45_55Hz_SF250. 

    
//Signal channel without filters
auto signalChannel = Neuro::DeviceChannel<Neuro::ChannelInfo::Type::Signal>(devicePtr, channelInfo);
//Signal channel with 3 EEG filters
std::vector<std::unique_ptr<DSP::DigitalFilter<double>>> digitalFilters;
digitalFilters.push_back(std::make_unique<DSP::IIRForwardFilter<DSP::HighPass<2, 2, 250>>>());
digitalFilters.push_back(std::make_unique<DSP::IIRForwardFilter<DSP::LowPass<30,2,250>>>());
digitalFilters.push_back(std::make_unique<DSP::IIRForwardFilter<DSP::BandStop<45,55,4,250>>>());
auto cascadeFilter = DSP::make_cascade_filter<std::unique_ptr>(std::move(digitalFilters));
auto signalChannel = Neuro::DeviceChannel<Neuro::ChannelInfo::Type::Signal>(devicePtr, std::make_unique<decltype(cascadeFilter)>(std::move(cascadeFilter)), channelInfo);
    


Channel buffers collection 

There is a set of different channel buffers for storing and processing signal data

    
//Resistance channel. Receives and stores information about the resistance of electrodes
if (channelInfo.getType() == Neuro::ChannelInfo::Type::Resistance) {				
	auto resistanceChannel = Neuro::DeviceChannel<Neuro::ChannelInfo::Type::Resistance>(devicePtr, channelInfo);
}

//Bipolar channel. Calculates the difference between two EEG (or simple signal) channels. 
//Could be used to reduce some types of artifacts
//signalChannel1Ptr and signalChannel2Ptr are std::shared_ptr's
auto bipolarChannel = Neuro::BipolarChannel(signalChannel1Ptr, signalChannel2Ptr);

//Spectrum channel. Calculates signal spectrum for a selected signal region.
//readData returns array with spectrum samples of length provided in spectrumLength method
//offset and length parameters relate to source signal channel which was used to make spectrum channel
auto spectrumChannel = Neuro::SpectrumChannel(anyDoubleChannelPtr);
auto frequencyStep = spectrumChannel.hzPerSpectrumSample();
auto spectrumLength = spectrumChannel.spectrumLength();

//Spectrum power channel. Calculates and stores a power of spectrum on a selected frequency band
//You could use several spectrum channels for spectrum power calculation. Results will be joined into one
auto spectrumPowerChannel = Neuro::SpectrumPowerChannel(std::vector{ std::make_shared<decltype(spectrumChannel)>(spectrumChannel) }, startFreq, stopFreq);
    


Emulation of device
 

If you don’t have Brainbit device yet, you could try all signal processing features using emulation channel. You need to provide spectral components of an emulation signal to create this channel. You could use this channel for all calculations that receive channels of doubles.

    
std::vector<Neuro::EmulationSine> emulationComponents{
	Neuro::EmulationSine{ 
		100e-6, //amplitude = 100uV;
		10, //frequency = 10 Hz
		0
	},
	Neuro::EmulationSine{
		50e-6, //amplitude = 50uV;
		18, //frequency = 18 Hz
		0
	},
	Neuro::EmulationSine{
		80e-6, //amplitude = 80uV;
		30, //frequency = 30 Hz
		0
	},
};
auto emulationChannel = Neuro::EmulationChannel(emulationComponents, 250.f, 2000);//8 seconds of signal
emulationChannel.startTimer();//begins emulation of receiving new signal
emulationChannel.stopTimer();