Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Showing
with 2354 additions and 0 deletions
//
// RapidMixThread/h
// This implementation of rapidMix trains in a different thread and has a synchronized
// update process with your main thread, which triggers a ( sync ) callback on doneTraining.
// Allows you to train one model and keep using it while the next one trains, the new
// model will be swapped in sync with the thread the update is called in once it's done.
// Created by James on 19/11/2017.
//
//
#ifndef RapidMixThread_h
#define RapidMixThread_h
#include <algorithm>
#include <functional>
#include <unistd.h> // usleep
#include "ThreadedProcess.h"
#include "rapidmix.h"
typedef rapidmix::staticRegression selectedTraining;
class RapidMixThread : public ThreadedProcess
{
public:
std::function< void ( void ) > doneTrainingCallback = nullptr;
selectedTraining* model = nullptr; // The model to be accessed from outside this thread
RapidMixThread ( void ) : ThreadedProcess ( )
{
training.store( false );
doneTraining.store( false );
}
~RapidMixThread ( void )
{
stopThread( );
if ( newModel )
delete newModel;
if ( model )
delete model;
printf ( "Stop rapidmixthread\n" );
}
void setup ( void )
{
model = createModel( );
startThread( 100000 ); // start training thread with sleep time of 100 ms
}
bool train ( rapidmix::trainingData& data )
{
if ( !training.load( ) )
{
this->trainingData = data;
training.store( true );
return true;
}
return false;
}
bool isTraining ( void ) const
{
return training.load( );
}
void update ( void )
{ // To be called in your main loop
if ( doneTraining.load( ) )
{
if ( newModel )
{
std::swap( model, newModel );
} else
throw std::runtime_error( "New model was not created successfully" );
if ( doneTrainingCallback )
doneTrainingCallback( );
//printf( "Done training\n" );
doneTraining.store( false );
}
}
protected:
selectedTraining* createModel ( void )
{
// Doing this to prevent repetitive stuff when testing different models
selectedTraining* t = new selectedTraining( );
t->setNumHiddenNodes( 18 );
return t;
}
void mainThreadCallback ( void )
{ // Training thread
if ( isTraining( ) )
{
if ( newModel )
delete newModel;
newModel = createModel( );
//printf( "Started training in thread... \n" );
if ( !trainingData.trainingSet.empty( ) && trainingData.trainingSet[0].elements.size( ) > 1 )
if ( !newModel->train( trainingData ) )
throw std::runtime_error( "Training failed" );;
training.store( false );
doneTraining.store( true );
}
}
std::atomic< bool > training;
std::atomic< bool > doneTraining;
selectedTraining* newModel = nullptr;
rapidmix::trainingData trainingData;
private:
};
#endif /* RapidMixThread_h */
/*
* RingBuffer.hpp
*/
#ifndef RINGBUFFER_HPP
#define RINGBUFFER_HPP
#include <iostream>
#include <atomic>
#include <string>
#include <unistd.h>
#include <string.h> // memcpy
#include <cstring>
template <class T>
class RingBuffer
{
public:
RingBuffer();
RingBuffer(unsigned long size);
~RingBuffer();
void setup(unsigned long size);
unsigned long push(const T* data, unsigned long n);
unsigned long pop(T* data, unsigned long n);
unsigned long getWithoutPop(T* data, unsigned long n);
unsigned long items_available_for_write() const;
unsigned long items_available_for_read() const;
bool isLockFree() const;
void pushMayBlock(bool block);
void popMayBlock(bool block);
void setBlockingNap(unsigned long blockingNap);
void setSize(unsigned long size);
void reset();
private:
unsigned long array_size;
T* buffer = nullptr;
long type_size; // also depends on #channels
std::atomic<unsigned long> tail_index; // write pointer
std::atomic<unsigned long> head_index; // read pointer
bool blockingPush;
bool blockingPop;
useconds_t blockingNap = 500;
}; // RingBuffer{}
template <class T>
RingBuffer<T>::RingBuffer()
{
tail_index = 0;
head_index = 0;
blockingPush = false;
blockingPop = false;
} // RingBuffer()
template <class T>
RingBuffer<T>::RingBuffer(unsigned long array_size)
{
tail_index = 0;
head_index = 0;
blockingPush = false;
blockingPop = false;
setup(array_size);
} // RingBuffer()
template <class T>
RingBuffer<T>::~RingBuffer()
{
if (buffer)
delete [] buffer;
} // ~RingBuffer()
template <class T>
void RingBuffer<T>::setup(unsigned long array_size)
{
if (buffer)
delete [] buffer;
this->array_size = array_size;
buffer = new T [array_size](); // allocate storage
type_size = sizeof(T);
}
template <class T>
unsigned long RingBuffer<T>::items_available_for_write() const
{
long pointerspace = head_index.load()-tail_index.load(); // signed
if(pointerspace > 0) return pointerspace; // NB: > 0 so NOT including 0
else return pointerspace + array_size;
} // items_available_for_write()
template <class T>
unsigned long RingBuffer<T>::items_available_for_read() const
{
long pointerspace = tail_index.load() - head_index.load(); // signed
if(pointerspace >= 0) return pointerspace; // NB: >= 0 so including 0
else return pointerspace + array_size;
} // items_available_for_read()
template <class T>
void RingBuffer<T>::pushMayBlock(bool block)
{
this->blockingPush = block;
} // pushMayBlock()
template <class T>
void RingBuffer<T>::popMayBlock(bool block)
{
this->blockingPop = block;
} // popMayBlock()
template <class T>
void RingBuffer<T>::setBlockingNap(unsigned long blockingNap)
{
this->blockingNap = blockingNap;
} // setBlockingNap()
/*
* Try to write as many items as possible and return the number actually written
*/
template <class T>
unsigned long RingBuffer<T>::push(const T* data, unsigned long n)
{
unsigned long space = array_size, n_to_write, memory_to_write, first_chunk, second_chunk, current_tail;
//space = items_available_for_write();
if(blockingPush)
{
while((space = items_available_for_write()) < n)
{ // blocking
usleep(blockingNap);
} // while, possibly use a system level sleep operation with CVAR?
} // if
n_to_write = (n <= space) ? n : space; // limit
current_tail = tail_index.load();
if(current_tail + n_to_write <= array_size)
{ // chunk fits without wrapping
memory_to_write = n_to_write * type_size;
memcpy(buffer+current_tail, data, memory_to_write);
tail_index.store(current_tail + n_to_write);
}
else { // chunk has to wrap
first_chunk = array_size - current_tail;
memory_to_write = first_chunk * type_size;
memcpy(buffer + current_tail, data, memory_to_write);
second_chunk = n_to_write - first_chunk;
memory_to_write = second_chunk * type_size;
memcpy(buffer, data + first_chunk, memory_to_write);
tail_index.store(second_chunk);
}
return n_to_write;
} // push()
/*
* Try to read as many items as possible and return the number actually read
*/
template <class T>
unsigned long RingBuffer<T>::pop(T* data, unsigned long n)
{
unsigned long space = array_size, n_to_read, memory_to_read, first_chunk, second_chunk, current_head;
//space = items_available_for_read(); //if checking outside of thread, not necessary?
if(blockingPop)
{
while((space = items_available_for_read()) < n)
{ // blocking
usleep(blockingNap);
} // while
} // if
if(space == 0)
return 0;
n_to_read = (n <= space) ? n : space; // limit
current_head = head_index.load();
if(current_head + n_to_read <= array_size)
{ // no wrapping necessary
memory_to_read = n_to_read * type_size;
memcpy(data, buffer + current_head, memory_to_read);
head_index.store(current_head + n_to_read);
}
else { // read has to wrap
first_chunk = array_size - current_head;
memory_to_read = first_chunk * type_size;
memcpy(data, buffer + current_head, memory_to_read);
second_chunk = n_to_read - first_chunk;
memory_to_read = second_chunk * type_size;
memcpy(data + first_chunk, buffer, memory_to_read);
head_index.store(second_chunk);
}
return n_to_read;
} // pop()
/*
* Try to read as many items as possible and return the number actually read
*/
template <class T>
unsigned long RingBuffer<T>::getWithoutPop(T* data, unsigned long n)
{
unsigned long space = array_size, n_to_read, memory_to_read, first_chunk, second_chunk, current_head;
//space = items_available_for_read(); //if checking outside of thread, not necessary?
if(blockingPop)
{
while((space = items_available_for_read()) < n)
{ // blocking
usleep(blockingNap);
} // while
} // if
if(space == 0)
return 0;
n_to_read = (n <= space) ? n : space; // limit
current_head = head_index.load();
if(current_head + n_to_read <= array_size)
{ // no wrapping necessary
memory_to_read = n_to_read * type_size;
memcpy(data, buffer + current_head, memory_to_read);
//head_index.store(current_head + n_to_read);
}
else { // read has to wrap
first_chunk = array_size - current_head;
memory_to_read = first_chunk * type_size;
memcpy(data, buffer + current_head, memory_to_read);
second_chunk = n_to_read - first_chunk;
memory_to_read = second_chunk * type_size;
memcpy(data + first_chunk, buffer, memory_to_read);
//head_index.store(second_chunk);
}
return n_to_read;
} // getWithoutPop()
template <class T>
bool RingBuffer<T>::isLockFree() const
{
return (tail_index.is_lock_free() && head_index.is_lock_free());
} // isLockFree()
template <class T>
void RingBuffer<T>::setSize(unsigned long size)
{
if (buffer)
delete [] buffer;
this->array_size = array_size;
buffer = new T [array_size](); // allocate storage
type_size = sizeof(T);
tail_index.store(0);
head_index.store(0);
}
template <class T>
void RingBuffer<T>::reset()
{
tail_index.store(0);
head_index.store(0);
} // isLockFree()
#endif
//
// RingBufferAny.hpp
// Can be used as a non-blocking eventpoll/ringbuffer without having to write unions etc ahead of time
// Allowing rapid expansion of inter-thread communication with several datatypes being stored in the same queue.
// Created by James Frink on 16/12/2017.
// Copyright © 2017 James Frink. All rights reserved.
//
#ifndef RingBufferAny_h
#define RingBufferAny_h
#include <atomic>
#include <memory>
#include <typeindex>
#include <type_traits>
#include "RingBufferV.hpp"
#include "Spinlock.hpp"
// WARNING: stdlib objects that use dynamic allocation ( string, list, vector etc are not supported atm )
// If you use your own structures, make sure to use primitive types and stack allocated arrays
// Spinlocks in this are only to protect against wrongful use and when resizing the buffer
// Note that resizing the buffer to a smaller one than before might cause a complete reset of the heads
class RingBufferAny : protected RingBufferV< int8_t > {
public:
struct VariableHeader {
std::type_index type_index = typeid( nullptr );
size_t sizeOfInput = 0;
size_t valuesPassed = 0;
};
RingBufferAny ( void ) : RingBufferV< int8_t > ( ) {}
RingBufferAny ( size_t sizeOfBuffer ) : RingBufferV< int8_t > ( )
{
setup( sizeOfBuffer );
}
~RingBufferAny ( void ) {}
void setup ( size_t sizeOfBuffer )
{
resize( sizeOfBuffer );
}
void resize ( size_t size )
{
inputSpin.lock( );
outputSpin.lock( );
this->sizeOfBuffer.store( size );
this->RingBufferV< int8_t >::setSize( size );
inputSpin.unlock( );
outputSpin.unlock( );
}
size_t size ( void ) const
{
return sizeOfBuffer.load( );
}
template <class T>
void adjustSizeIfNeeded( size_t numElements )
{
size_t totalSize = numElements * sizeof( T );
if ( totalSize > sizeOfBuffer.load( ) )
{
setup( totalSize );
}
}
void reset ( void )
{
this->RingBufferV< int8_t >::reset( );
}
// TODO add vector/list serialization support
bool anyAvailableForPop ( VariableHeader& outputHeader )
{ // Return if an element is available to be popped out of the queue, as well as the header information if available
outputSpin.lock( ); // Lock
size_t avForRead = items_available_for_read( );
if ( avForRead > headerSize )
{
this->RingBufferV< int8_t >::getWithoutPop( reinterpret_cast< int8_t* > ( &outputHeader ), headerSize ); // Unlock case 1
outputSpin.unlock( );
return ( avForRead >= ( headerSize + outputHeader.sizeOfInput ) );
}
outputSpin.unlock( ); // Unlock case 2
return false;
}
template <class T>
bool push (T* inputData, size_t numValues)
{ // Push specified number of values starting at pointer inputData on to the queue
VariableHeader vh;
vh.type_index = std::type_index( typeid( T ) );
vh.valuesPassed = numValues;
vh.sizeOfInput = sizeof( *inputData ) * numValues;
inputSpin.lock( ); // Lock
if ( ( headerSize + vh.sizeOfInput ) <= items_available_for_write( ) )
{
// Write header
this->RingBufferV< int8_t >::push( reinterpret_cast< int8_t* > ( &vh ), headerSize );
// Write object(s)
this->RingBufferV< int8_t >::push( reinterpret_cast< int8_t* > ( inputData ), vh.sizeOfInput );
inputSpin.unlock( ); // Unlock case 1
return true;
}
inputSpin.unlock( ); // Unlock case 2
return false;
}
template <class T>
void pop ( T* output, size_t outputSize )
{ // Pop single or multiple values from queue
VariableHeader vh;
outputSpin.lock( ); // Lock
size_t avForRead = items_available_for_read( );
// Get Header
if ( avForRead < headerSize )
throw std::runtime_error( "Not enough data in buffer to pop header, use anyAvailableForPop to check this!" );
else
this->RingBufferV< int8_t >::pop( reinterpret_cast< int8_t* > ( &vh ), headerSize );
if ( std::type_index( typeid( T ) ) != vh.type_index )
throw std::runtime_error( "Type matching error!");
if ( vh.valuesPassed > outputSize ) // Possibility: add seperate unloading (with moveable header?)
throw std::runtime_error( "Number of values passed on push will not fit in pop structure, use the correct return structure." );
// Get Object(s)
if ( avForRead < vh.sizeOfInput )
throw std::runtime_error( "Not enough data in buffer to pop object(s), use anyAvailableForPop to check this!" );
else
this->RingBufferV<int8_t>::pop( reinterpret_cast< int8_t* > ( output ), vh.sizeOfInput );
outputSpin.unlock( ); // Unlock
}
protected:
std::atomic< size_t > sizeOfBuffer;
static const size_t headerSize = sizeof( VariableHeader );
private:
Spinlock inputSpin; // Placeholder type
Spinlock outputSpin;// ''
};
#endif /* RingBufferAny_h */
/*
* RingBufferV.hpp
*/
#ifndef RingBufferV_HPP
#define RingBufferV_HPP
#include <iostream>
#include <atomic>
#include <string>
#include <unistd.h>
#include <string.h> // memcpy
#include <cstring>
#include <vector>
// Vector implementation of RingBuffer for better resizing + allowing later implementation of own non-blocking freestore allocator without having to resort to using boost lib
template <class T>
class RingBufferV
{
public:
RingBufferV ( void );
RingBufferV ( size_t size );
~RingBufferV ( void );
void setup ( size_t size );
size_t push ( const T* data, size_t n );
size_t pop ( T* data, size_t n );
size_t getWithoutPop ( T* data, size_t n );
size_t items_available_for_write ( void ) const;
size_t items_available_for_read ( void ) const;
bool isLockFree ( void ) const;
void pushMayBlock ( bool block );
void popMayBlock ( bool block );
void setBlockingNap ( size_t blockingNap );
void setSize ( size_t size );
void reset ( void );
private:
size_t array_size;
std::vector< T > buffer;
static constexpr long type_size = sizeof( T ); // Not used atm
std::atomic< size_t > tail_index = { 0 }; // write pointer
std::atomic< size_t > head_index = { 0 }; // read pointer
bool blockingPush;
bool blockingPop;
useconds_t blockingNap = 500; // 0.5 ms
}; // RingBufferV{}
template < class T >
RingBufferV< T >::RingBufferV ( void )
{
blockingPush = false;
blockingPop = false;
} // RingBufferV( void )
template < class T >
RingBufferV< T >::RingBufferV ( size_t array_size )
{
blockingPush = false;
blockingPop = false;
setup( array_size );
} // RingBufferV( void )
template < class T >
RingBufferV< T >::~RingBufferV ( void )
{
} // ~RingBufferV( void )
template < class T >
void RingBufferV< T >::setup ( size_t array_size )
{
setSize( array_size ); // allocate storage
}
template < class T >
size_t RingBufferV< T >::items_available_for_write ( void ) const
{
long pointerspace = head_index.load( ) - tail_index.load( ); // signed
if ( pointerspace > 0 )
return pointerspace; // NB: > 0 so NOT including 0
else
return pointerspace + array_size;
} // items_available_for_write( void )
template < class T >
size_t RingBufferV< T >::items_available_for_read ( void ) const
{
long pointerspace = tail_index.load( ) - head_index.load( ); // signed
if ( pointerspace >= 0 )
return pointerspace; // NB: >= 0 so including 0
else
return pointerspace + array_size;
} // items_available_for_read( void )
template < class T >
void RingBufferV< T >::pushMayBlock ( bool block )
{
this->blockingPush = block;
} // pushMayBlock( void )
template < class T >
void RingBufferV< T >::popMayBlock ( bool block )
{
this->blockingPop = block;
} // popMayBlock( void )
template < class T >
void RingBufferV< T >::setBlockingNap ( size_t blockingNap )
{
this->blockingNap = blockingNap;
} // setBlockingNap( void )
/*
* Try to write as many items as possible and return the number actually written
*/
template < class T >
size_t RingBufferV< T >::push ( const T* data, size_t n )
{
size_t space = array_size, n_to_write, first_chunk, second_chunk, current_tail;
//space = items_available_for_write();
if ( blockingPush )
{
while ( ( space = items_available_for_write( ) ) < n )
{ // blocking
usleep( blockingNap );
} // while, Any better solutions to this?
} // if
n_to_write = ( n <= space ) ? n : space; // limit
current_tail = tail_index.load( );
if ( current_tail + n_to_write <= array_size )
{ // chunk fits without wrapping
std::copy_n( data, n_to_write, buffer.begin( ) + current_tail );
tail_index.store( current_tail + n_to_write );
} else { // chunk has to wrap
first_chunk = array_size - current_tail;
std::copy_n( data, first_chunk, buffer.begin( ) + current_tail );
second_chunk = n_to_write - first_chunk;
std::copy_n( data + first_chunk, second_chunk, buffer.begin( ) );
tail_index.store( second_chunk );
}
return n_to_write;
} // push( const T* data, size_t n )
/*
* Try to read as many items as possible and return the number actually read
*/
template < class T >
size_t RingBufferV< T >::pop ( T* data, size_t n )
{
size_t space = array_size, n_to_read, first_chunk, second_chunk, current_head;
//space = items_available_for_read(); // if checking outside of thread, not necessary?
if ( blockingPop )
{
while ( ( space = items_available_for_read( ) ) < n )
{ // blocking
usleep( blockingNap );
} // while
} // if
if ( space == 0 )
return 0;
n_to_read = ( n <= space ) ? n : space; // limit
current_head = head_index.load( );
if ( current_head + n_to_read <= array_size )
{ // no wrapping necessary
std::copy_n( buffer.begin( ) + current_head, n_to_read, data );
head_index.store( current_head + n_to_read );
} else { // read has to wrap
first_chunk = array_size - current_head;
std::copy_n( buffer.begin( ) + current_head, first_chunk, data );
second_chunk = n_to_read - first_chunk;
std::copy_n( buffer.begin( ), second_chunk, data + first_chunk );
head_index.store( second_chunk );
}
return n_to_read;
} // pop( T* data, size_t n )
/*
* Try to read as many items as possible and return the number actually read
*/
template < class T >
size_t RingBufferV< T >::getWithoutPop ( T* data, size_t n )
{
size_t space = array_size, n_to_read, first_chunk, second_chunk, current_head;
//space = items_available_for_read(); //if checking outside of thread, not necessary?
if ( blockingPop )
{
while ( ( space = items_available_for_read( ) ) < n )
{ // blocking
usleep( blockingNap );
} // while
} // if
if ( space == 0 )
return 0;
n_to_read = ( n <= space ) ? n : space; // limit
current_head = head_index.load( );
if ( current_head + n_to_read <= array_size )
{ // no wrapping necessary
std::copy_n( buffer.begin( ) + current_head, n_to_read, data );
//head_index.store(current_head + n_to_read);
} else { // read has to wrap
first_chunk = array_size - current_head;
std::copy_n( buffer.begin( ) + current_head, first_chunk, data );
second_chunk = n_to_read - first_chunk;
std::copy_n( buffer.begin( ), second_chunk, data + first_chunk );
//head_index.store(second_chunk);
}
return n_to_read;
} // getWithoutPop( T* data, size_t n )
template <class T>
bool RingBufferV<T>::isLockFree ( void ) const
{
return ( tail_index.is_lock_free( ) && head_index.is_lock_free( ) );
} // isLockFree( void )
template <class T>
void RingBufferV< T >::setSize( size_t size )
{
this->array_size = size;
buffer.resize( array_size ); // allocate storage
if ( tail_index.load( ) <= array_size || head_index.load( ) <= array_size )
{
reset( );
}
}
template <class T>
void RingBufferV<T>::reset( void )
{
tail_index.store( 0 );
head_index.store( 0 );
} // isLockFree( void )
#endif
//
// SigTools.cpp
//
//
// Created by James on 08/01/17.
//
//
#include "SigTools.hpp"
double MTOF ( unsigned char midi ) {
return pow( 2, static_cast< double >( midi-69 ) / 12.0 ) * 440.0;
}
double FTOM ( double freq ) {
return 12.0 * log2( freq / 440.0 ) + 69;
}
//
// SigTools.hpp
//
//
// Created by James on 08/01/17.
//
//
#ifndef SigTools_hpp
#define SigTools_hpp
#include <stdio.h>
#include <math.h>
double MTOF ( unsigned char midi );
double FTOM ( double freq );
#endif /* SigTools_hpp */
//
// Spinlock.h
// beladrum
//
// Created by James on 11/05/17.
// Copyright © 2017 HKU. All rights reserved.
//
#ifndef Spinlock_h
#define Spinlock_h
#include <atomic>
class Spinlock {
std::atomic_flag locked = ATOMIC_FLAG_INIT ;
public:
void lock ( void ) {
while (locked.test_and_set(std::memory_order_acquire)) { ; }
}
void unlock ( void ) {
locked.clear(std::memory_order_release);
}
};
#endif /* Spinlock_h */
//
// ThreadedProcess.h
// Bitalino
//
// Created by James on 19/11/2017.
//
//
#ifndef ThreadedProcess_h
#define ThreadedProcess_h
#include <stdio.h>
#include <stdint.h>
#include <functional>
#include <thread>
#include <atomic>
class ThreadedProcess {
public:
ThreadedProcess ( void )
{
running.store( false );
}
virtual ~ThreadedProcess ( void )
{
stopThread( );
}
bool isThreadRunning ( void ) const
{
return running.load( );
}
// ( re )Start main thread
void startThread ( uint64_t threadSleepTime = 1 )
{
this->threadSleepTime = threadSleepTime;
if ( running.load( ) )
{ // Thread is already running
stopThread( );
}
running.store( true );
processMainThread = std::thread( &ThreadedProcess::mainLoop, this ); // Spawn thread
}
// Stop main thread
void stopThread ( void )
{
if ( running.load( ) )
{
running.store( false );
processMainThread.join( );
}
}
protected:
virtual void mainThreadCallback ( void ) = 0;
std::thread processMainThread;
private:
void mainLoop ( void )
{
while ( running.load( ) )
{
mainThreadCallback( );
usleep( threadSleepTime );
}
}
std::atomic< bool > running;
uint64_t threadSleepTime = 1;
};
#endif /* ThreadedProcess_h */
//
// Timer.h
// Bitalino
//
// Created by James on 28/11/2017.
//
//
#ifndef Timer_h
#define Timer_h
#include <stdio.h>
#include <chrono>
class Timer {
public:
uint64_t getTimeElapsed ( void )
{ // Since last call of reset
return std::chrono::duration_cast< std::chrono::milliseconds >( std::chrono::high_resolution_clock::now() - start ).count( );
}
void reset ( void )
{
start = std::chrono::high_resolution_clock::now( );
}
private:
std::chrono::high_resolution_clock::time_point start;
};
#endif /* Timer_h */
//
// Tools.h
// Bitalino
//
// Created by James on 22/11/2017.
//
//
#ifndef Tools_h
#define Tools_h
#include "ofMain.h"
namespace ofxBitmapText {
enum class TextAlignment {
LEFT,
CENTER,
RIGHT
};
static inline void drawTextLabel ( std::string label, ofVec2f position, ofColor labelBackgroundColor, ofColor stringColor, TextAlignment alignment, bool drawAbove )
{
uint32_t strLenPix = label.length( )*8;
switch ( alignment ) {
case TextAlignment::LEFT:
// No exception
break;
case TextAlignment::CENTER:
position.x -= strLenPix / 2;
break;
case TextAlignment::RIGHT:
position.x -= strLenPix;
break;
default:
break;
}
ofFill( );
ofSetColor( labelBackgroundColor );
ofRectangle bmpStringRect( position.x - 2,
position.y + ( ( drawAbove ) ? -4 : 12 ) + 2,
strLenPix + 4, -12 );
ofDrawRectangle( bmpStringRect );
ofSetColor( stringColor );
ofDrawBitmapString( label, position.x, position.y + ( ( drawAbove ) ? -4 : 12 ) );
ofSetColor( 255, 255, 255, 255 );
}
}
#endif /* Tools_h */
/**
* \copyright Copyright 2014-2016 PLUX - Wireless Biosignals, S.A.
* \author Filipe Silva
* \version 2.1
* \date February 2016
*
* \section LICENSE
This program 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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*****************************************************************************/
#ifdef _WIN32 // 32-bit or 64-bit Windows
#define HASBLUETOOTH
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#include <ws2bth.h>
#else // Linux or Mac OS
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#ifdef HASBLUETOOTH // Linux only
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <stdlib.h>
#endif // HASBLUETOOTH
void Sleep(int millisecs)
{
usleep(millisecs*1000);
}
#endif // Linux or Mac OS
#include "bitalino.h"
/*****************************************************************************/
// CRC4 check function
static const unsigned char CRC4tab[16] = {0, 3, 6, 5, 12, 15, 10, 9, 11, 8, 13, 14, 7, 4, 1, 2};
static bool checkCRC4(const unsigned char *data, int len)
{
unsigned char crc = 0;
for (int i = 0; i < len-1; i++)
{
const unsigned char b = data[i];
crc = CRC4tab[crc] ^ (b >> 4);
crc = CRC4tab[crc] ^ (b & 0x0F);
}
// CRC for last byte
crc = CRC4tab[crc] ^ (data[len-1] >> 4);
crc = CRC4tab[crc];
return (crc == (data[len-1] & 0x0F));
}
/*****************************************************************************/
// BITalino public methods
BITalino::VDevInfo BITalino::find(void)
{
VDevInfo devs;
DevInfo devInfo;
#ifdef _WIN32
char addrStr[40];
WSADATA m_data;
if (WSAStartup(0x202, &m_data) != 0) throw Exception(Exception::PORT_INITIALIZATION);
WSAQUERYSETA querySet;
ZeroMemory(&querySet, sizeof querySet);
querySet.dwSize = sizeof(querySet);
querySet.dwNameSpace = NS_BTH;
HANDLE hLookup;
DWORD flags = LUP_CONTAINERS | LUP_RETURN_ADDR | LUP_RETURN_NAME | LUP_FLUSHCACHE;
bool tryempty = true;
bool again;
do
{
again = false;
if (WSALookupServiceBeginA(&querySet, flags, &hLookup) != 0)
{
WSACleanup();
throw Exception(Exception::BT_ADAPTER_NOT_FOUND);
}
while (1)
{
BYTE buffer[1500];
DWORD bufferLength = sizeof(buffer);
WSAQUERYSETA *pResults = (WSAQUERYSETA*)&buffer;
if (WSALookupServiceNextA(hLookup, flags, &bufferLength, pResults) != 0) break;
if (pResults->lpszServiceInstanceName[0] == 0 && tryempty)
{ // empty name : may happen on the first inquiry after the device was connected
tryempty = false; // redo the inquiry a second time only (there may be a device with a real empty name)
again = true;
break;
}
DWORD strSiz = sizeof addrStr;
if (WSAAddressToStringA(pResults->lpcsaBuffer->RemoteAddr.lpSockaddr, pResults->lpcsaBuffer->RemoteAddr.iSockaddrLength,
NULL, addrStr, &strSiz) == 0)
{
addrStr[strlen(addrStr)-1] = 0; // remove trailing ')'
devInfo.macAddr = addrStr+1; // remove leading '('
devInfo.name = pResults->lpszServiceInstanceName;
devs.push_back(devInfo);
}
}
WSALookupServiceEnd(hLookup);
} while (again);
WSACleanup();
#else // Linux or Mac OS
#ifdef HASBLUETOOTH
#define MAX_DEVS 255
int dev_id = hci_get_route(NULL);
int sock = hci_open_dev(dev_id);
if (dev_id < 0 || sock < 0)
throw Exception(Exception::PORT_INITIALIZATION);
inquiry_info ii[MAX_DEVS];
inquiry_info *pii = ii;
int num_rsp = hci_inquiry(dev_id, 8, MAX_DEVS, NULL, &pii, IREQ_CACHE_FLUSH);
if(num_rsp < 0)
{
::close(sock);
throw Exception(Exception::PORT_INITIALIZATION);
}
for (int i = 0; i < num_rsp; i++)
{
char addr[19], name[248];
ba2str(&ii[i].bdaddr, addr);
if (hci_read_remote_name(sock, &ii[i].bdaddr, sizeof name, name, 0) >= 0)
{
devInfo.macAddr = addr;
devInfo.name = name;
devs.push_back(devInfo);
}
}
::close(sock);
if (pii != ii) free(pii);
#else
throw Exception(Exception::BT_ADAPTER_NOT_FOUND);
#endif // HASBLUETOOTH
#endif // Linux or Mac OS
return devs;
}
/*****************************************************************************/
BITalino::BITalino(const char *address) : nChannels(0), isBitalino2(false)
{
#ifdef _WIN32
if (_memicmp(address, "COM", 3) == 0)
{
fd = INVALID_SOCKET;
char xport[40] = "\\\\.\\"; // preppend "\\.\"
strcat_s(xport, 40, address);
hCom = CreateFileA(xport, // comm port name
GENERIC_READ | GENERIC_WRITE,
0, // comm devices must be opened w/exclusive-access
NULL, // no security attributes
OPEN_EXISTING, // comm devices must use OPEN_EXISTING
0, // not overlapped I/O
NULL); // hTemplate must be NULL for comm devices
if (hCom == INVALID_HANDLE_VALUE)
throw Exception(Exception::PORT_COULD_NOT_BE_OPENED);
DCB dcb;
if (!GetCommState(hCom, &dcb))
{
close();
throw Exception(Exception::PORT_INITIALIZATION);
}
dcb.BaudRate = CBR_115200;
dcb.fBinary = TRUE;
dcb.fParity = FALSE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDtrControl = DTR_CONTROL_DISABLE;
dcb.fDsrSensitivity = FALSE;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
dcb.fNull = FALSE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
if (!SetCommState(hCom, &dcb))
{
close();
throw Exception(Exception::PORT_INITIALIZATION);
}
COMMTIMEOUTS ct;
ct.ReadIntervalTimeout = 0;
ct.ReadTotalTimeoutConstant = 5000; // 5 s
ct.ReadTotalTimeoutMultiplier = 0;
ct.WriteTotalTimeoutConstant = 5000; // 5 s
ct.WriteTotalTimeoutMultiplier = 0;
if (!SetCommTimeouts(hCom, &ct))
{
close();
throw Exception(Exception::PORT_INITIALIZATION);
}
}
else // address is a Bluetooth MAC address
{
hCom = INVALID_HANDLE_VALUE;
WSADATA m_data;
if (WSAStartup(0x202, &m_data) != 0)
throw Exception(Exception::PORT_INITIALIZATION);
SOCKADDR_BTH so_bt;
int siz = sizeof so_bt;
if (WSAStringToAddressA((LPSTR)address, AF_BTH, NULL, (sockaddr*)&so_bt, &siz) != 0)
{
WSACleanup();
throw Exception(Exception::INVALID_ADDRESS);
}
so_bt.port = 1;
fd = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
if (fd == INVALID_SOCKET)
{
WSACleanup();
throw Exception(Exception::PORT_INITIALIZATION);
}
DWORD rcvbufsiz = 128*1024; // 128k
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char*) &rcvbufsiz, sizeof rcvbufsiz);
if (connect(fd, (const sockaddr*)&so_bt, sizeof so_bt) != 0)
{
int err = WSAGetLastError();
close();
switch(err)
{
case WSAENETDOWN:
throw Exception(Exception::BT_ADAPTER_NOT_FOUND);
case WSAETIMEDOUT:
throw Exception(Exception::DEVICE_NOT_FOUND);
default:
throw Exception(Exception::PORT_COULD_NOT_BE_OPENED);
}
}
readtimeout.tv_sec = 5;
readtimeout.tv_usec = 0;
}
#else // Linux or Mac OS
if (memcmp(address, "/dev/", 5) == 0)
{
fd = open(address, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0)
throw Exception(Exception::PORT_COULD_NOT_BE_OPENED);
if (fcntl(fd, F_SETFL, 0) == -1) // remove the O_NDELAY flag
{
close();
throw Exception(Exception::PORT_INITIALIZATION);
}
termios term;
if (tcgetattr(fd, &term) != 0)
{
close();
throw Exception(Exception::PORT_INITIALIZATION);
}
cfmakeraw(&term);
term.c_oflag &= ~(OPOST);
term.c_cc[VMIN] = 1;
term.c_cc[VTIME] = 1;
term.c_iflag &= ~(INPCK | PARMRK | ISTRIP | IGNCR | ICRNL | INLCR | IXON | IXOFF | IMAXBEL); // no flow control
term.c_iflag |= (IGNPAR | IGNBRK);
term.c_cflag &= ~(CRTSCTS | PARENB | CSTOPB | CSIZE); // no parity, 1 stop bit
term.c_cflag |= (CLOCAL | CREAD | CS8); // raw mode, 8 bits
term.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOPRT | ECHOK | ECHOKE | ECHONL | ECHOCTL | ISIG | IEXTEN | TOSTOP); // raw mode
if (cfsetspeed(&term, B115200) != 0)
{
close();
throw Exception(Exception::PORT_INITIALIZATION);
}
if (tcsetattr(fd, TCSANOW, &term) != 0)
{
close();
throw Exception(Exception::PORT_INITIALIZATION);
}
isTTY = true;
}
else // address is a Bluetooth MAC address
#ifdef HASBLUETOOTH
{
sockaddr_rc so_bt;
so_bt.rc_family = AF_BLUETOOTH;
if (str2ba(address, &so_bt.rc_bdaddr) < 0)
throw Exception(Exception::INVALID_ADDRESS);
so_bt.rc_channel = 1;
fd = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
if (fd < 0)
throw Exception(Exception::PORT_INITIALIZATION);
if (connect(fd, (const sockaddr*)&so_bt, sizeof so_bt) != 0)
{
close();
throw Exception(Exception::PORT_COULD_NOT_BE_OPENED);
}
isTTY = false;
}
#else
throw Exception(Exception::PORT_COULD_NOT_BE_OPENED);
#endif // HASBLUETOOTH
#endif // Linux or Mac OS
// check if device is BITalino2
const std::string ver = version();
const std::string::size_type pos = ver.find("_v");
if (pos != std::string::npos)
{
const char *xver = ver.c_str() + pos+2;
if (atoi(xver) >= 5) isBitalino2 = true;
}
}
/*****************************************************************************/
BITalino::~BITalino(void)
{
try
{
if (nChannels != 0) stop();
}
catch (Exception) {} // if stop() fails, close anyway
close();
}
/*****************************************************************************/
std::string BITalino::version(void)
{
if (nChannels != 0) throw Exception(Exception::DEVICE_NOT_IDLE);
const char *header = "BITalino";
const size_t headerLen = strlen(header);
send(0x07); // 0 0 0 0 0 1 1 1 - Send version string
std::string str;
while(1)
{
char chr;
if (recv(&chr, sizeof chr) != sizeof chr) // a timeout has occurred
throw Exception(Exception::CONTACTING_DEVICE);
const size_t len = str.size();
if (len >= headerLen)
{
if (chr == '\n') return str;
str.push_back(chr);
}
else
if (chr == header[len])
str.push_back(chr);
else
{
str.clear(); // discard all data before version header
if (chr == header[0]) str.push_back(chr);
}
}
}
/*****************************************************************************/
void BITalino::start(int samplingRate, const Vint &channels, bool simulated)
{
if (nChannels != 0) throw Exception(Exception::DEVICE_NOT_IDLE);
unsigned char cmd;
switch (samplingRate)
{
case 1:
cmd = 0x03;
break;
case 10:
cmd = 0x43;
break;
case 100:
cmd = 0x83;
break;
case 1000:
cmd = 0xC3;
break;
default:
throw Exception(Exception::INVALID_PARAMETER);
}
char chMask;
if (channels.empty())
{
chMask = 0x3F; // all 6 analog channels
nChannels = 6;
}
else
{
chMask = 0;
nChannels = 0;
for(Vint::const_iterator it = channels.begin(); it != channels.end(); it++)
{
int ch = *it;
if (ch < 0 || ch > 5) throw Exception(Exception::INVALID_PARAMETER);
const char mask = 1 << ch;
if (chMask & mask) throw Exception(Exception::INVALID_PARAMETER);
chMask |= mask;
nChannels++;
}
}
send(cmd); // <Fs> 0 0 0 0 1 1 - Set sampling rate
// A6 A5 A4 A3 A2 A1 0 1 - Start live mode with analog channel selection
// A6 A5 A4 A3 A2 A1 1 0 - Start simulated mode with analog channel selection
send((chMask << 2) | (simulated ? 0x02 : 0x01));
}
/*****************************************************************************/
void BITalino::stop(void)
{
if (nChannels == 0) throw Exception(Exception::DEVICE_NOT_IN_ACQUISITION);
send(0x00); // 0 0 0 0 0 0 0 0 - Go to idle mode
nChannels = 0;
version(); // to flush pending frames in input buffer
}
/*****************************************************************************/
int BITalino::read(VFrame &frames)
{
if (nChannels == 0) throw Exception(Exception::DEVICE_NOT_IN_ACQUISITION);
unsigned char buffer[8]; // frame maximum size is 8 bytes
if (frames.empty()) frames.resize(100);
char nBytes = nChannels + 2;
if (nChannels >= 3 && nChannels <= 5) nBytes++;
for(VFrame::iterator it = frames.begin(); it != frames.end(); it++)
{
if (recv(buffer, nBytes) != nBytes) return int(it - frames.begin()); // a timeout has occurred
while (!checkCRC4(buffer, nBytes))
{ // if CRC check failed, try to resynchronize with the next valid frame
// checking with one new byte at a time
memmove(buffer, buffer+1, nBytes-1);
if (recv(buffer+nBytes-1, 1) != 1) return int(it - frames.begin()); // a timeout has occurred
}
Frame &f = *it;
f.seq = buffer[nBytes-1] >> 4;
for(int i = 0; i < 4; i++)
f.digital[i] = ((buffer[nBytes-2] & (0x80 >> i)) != 0);
f.analog[0] = (short(buffer[nBytes-2] & 0x0F) << 6) | (buffer[nBytes-3] >> 2);
if (nChannels > 1)
f.analog[1] = (short(buffer[nBytes-3] & 0x03) << 8) | buffer[nBytes-4];
if (nChannels > 2)
f.analog[2] = (short(buffer[nBytes-5]) << 2) | (buffer[nBytes-6] >> 6);
if (nChannels > 3)
f.analog[3] = (short(buffer[nBytes-6] & 0x3F) << 4) | (buffer[nBytes-7] >> 4);
if (nChannels > 4)
f.analog[4] = ((buffer[nBytes-7] & 0x0F) << 2) | (buffer[nBytes-8] >> 6);
if (nChannels > 5)
f.analog[5] = buffer[nBytes-8] & 0x3F;
}
return (int) frames.size();
}
/*****************************************************************************/
void BITalino::battery(int value)
{
if (nChannels != 0) throw Exception(Exception::DEVICE_NOT_IDLE);
if (value < 0 || value > 63) throw Exception(Exception::INVALID_PARAMETER);
send(value << 2); // <bat threshold> 0 0 - Set battery threshold
}
/*****************************************************************************/
void BITalino::trigger(const Vbool &digitalOutput)
{
unsigned char cmd;
const size_t len = digitalOutput.size();
if (isBitalino2)
{
if (len != 0 && len != 2) throw Exception(Exception::INVALID_PARAMETER);
cmd = 0xB3; // 1 0 1 1 O2 O1 1 1 - Set digital outputs
}
else
{
if (len != 0 && len != 4) throw Exception(Exception::INVALID_PARAMETER);
if (nChannels == 0) throw Exception(Exception::DEVICE_NOT_IN_ACQUISITION);
cmd = 0x03; // 0 0 O4 O3 O2 O1 1 1 - Set digital outputs
}
for(size_t i = 0; i < len; i++)
if (digitalOutput[i])
cmd |= (0x04 << i);
send(cmd);
}
/*****************************************************************************/
void BITalino::pwm(int pwmOutput)
{
if (!isBitalino2) throw Exception(Exception::NOT_SUPPORTED);
if (pwmOutput < 0 || pwmOutput > 255) throw Exception(Exception::INVALID_PARAMETER);
send((char) 0xA3); // 1 0 1 0 0 0 1 1 - Set analog output (1 byte follows: 0..255)
send(pwmOutput);
}
/*****************************************************************************/
BITalino::State BITalino::state(void)
{
#pragma pack(1) // byte-aligned structure
struct StateX
{
unsigned short analog[6], battery;
unsigned char batThreshold, portsCRC;
} statex;
#pragma pack() // restore default alignment
if (!isBitalino2) throw Exception(Exception::NOT_SUPPORTED);
if (nChannels != 0) throw Exception(Exception::DEVICE_NOT_IDLE);
send(0x0B); // 0 0 0 0 1 0 1 1 - Send device status
if (recv(&statex, sizeof statex) != sizeof statex) // a timeout has occurred
throw Exception(Exception::CONTACTING_DEVICE);
if (!checkCRC4((unsigned char *) &statex, sizeof statex))
throw Exception(Exception::CONTACTING_DEVICE);
State state;
for(int i = 0; i < 6; i++)
state.analog[i] = statex.analog[i];
state.battery = statex.battery;
state.batThreshold = statex.batThreshold;
for(int i = 0; i < 4; i++)
state.digital[i] = ((statex.portsCRC & (0x80 >> i)) != 0);
return state;
}
/*****************************************************************************/
const char* BITalino::Exception::getDescription(void)
{
switch (code)
{
case INVALID_ADDRESS:
return "The specified address is invalid.";
case BT_ADAPTER_NOT_FOUND:
return "No Bluetooth adapter was found.";
case DEVICE_NOT_FOUND:
return "The device could not be found.";
case CONTACTING_DEVICE:
return "The computer lost communication with the device.";
case PORT_COULD_NOT_BE_OPENED:
return "The communication port does not exist or it is already being used.";
case PORT_INITIALIZATION:
return "The communication port could not be initialized.";
case DEVICE_NOT_IDLE:
return "The device is not idle.";
case DEVICE_NOT_IN_ACQUISITION:
return "The device is not in acquisition mode.";
case INVALID_PARAMETER:
return "Invalid parameter.";
case NOT_SUPPORTED:
return "Operation not supported by the device.";
default:
return "Unknown error.";
}
}
/*****************************************************************************/
// BITalino private methods
void BITalino::send(char cmd)
{
Sleep(150);
#ifdef _WIN32
if (fd == INVALID_SOCKET)
{
DWORD nbytwritten = 0;
if (!WriteFile(hCom, &cmd, sizeof cmd, &nbytwritten, NULL))
throw Exception(Exception::CONTACTING_DEVICE);
if (nbytwritten != sizeof cmd)
throw Exception(Exception::CONTACTING_DEVICE);
}
else
if (::send(fd, &cmd, sizeof cmd, 0) != sizeof cmd)
throw Exception(Exception::CONTACTING_DEVICE);
#else // Linux or Mac OS
if (write(fd, &cmd, sizeof cmd) != sizeof cmd)
throw Exception(Exception::CONTACTING_DEVICE);
#endif
}
/*****************************************************************************/
int BITalino::recv(void *data, int nbyttoread)
{
#ifdef _WIN32
if (fd == INVALID_SOCKET)
{
for(int n = 0; n < nbyttoread;)
{
DWORD nbytread = 0;
if (!ReadFile(hCom, (char *) data+n, nbyttoread-n, &nbytread, NULL))
throw Exception(Exception::CONTACTING_DEVICE);
if (nbytread == 0)
{
DWORD stat;
if (!GetCommModemStatus(hCom, &stat) || !(stat & MS_DSR_ON))
throw Exception(Exception::CONTACTING_DEVICE); // connection is lost
return n; // a timeout occurred
}
n += nbytread;
}
return nbyttoread;
}
#endif
#ifndef _WIN32 // Linux or Mac OS
timeval readtimeout;
readtimeout.tv_sec = 5;
readtimeout.tv_usec = 0;
#endif
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
for(int n = 0; n < nbyttoread;)
{
int state = select(FD_SETSIZE, &readfds, NULL, NULL, &readtimeout);
if(state < 0) throw Exception(Exception::CONTACTING_DEVICE);
if (state == 0) return n; // a timeout occurred
#ifdef _WIN32
int ret = ::recv(fd, (char *) data+n, nbyttoread-n, 0);
#else // Linux or Mac OS
ssize_t ret = ::read(fd, (char *) data+n, nbyttoread-n);
#endif
if(ret <= 0) throw Exception(Exception::CONTACTING_DEVICE);
n += ret;
}
return nbyttoread;
}
/*****************************************************************************/
void BITalino::close(void)
{
#ifdef _WIN32
if (fd == INVALID_SOCKET)
CloseHandle(hCom);
else
{
closesocket(fd);
WSACleanup();
}
#else // Linux or Mac OS
::close(fd);
#endif
}
/*****************************************************************************/
/**
* \file
* \copyright Copyright 2014-2015 PLUX - Wireless Biosignals, S.A.
* \author Filipe Silva
* \version 2.0
* \date November 2015
*
* \section LICENSE
This program 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 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
\mainpage
The %BITalino C++ API (available at http://github.com/BITalinoWorld/cpp-api) is a cross-platform library which enables C++ applications to communicate
with a %BITalino device through a simple interface.
The API is composed of a header file (bitalino.h)
and an implementation file ([bitalino.cpp](http://github.com/BITalinoWorld/cpp-api/tree/master/bitalino.cpp)).
A sample test application in C++ ([test.cpp](http://github.com/BITalinoWorld/cpp-api/tree/master/test.cpp)) is also provided.
There are three ways to connect to a %BITalino device:
- direct Bluetooth connection using the device Bluetooth MAC address (Windows and Linux);
- indirect Bluetooth connection using a virtual serial port (all platforms);
- wired UART connection using a serial port (all platforms).
The API exposes a single class (BITalino). Each instance of this class represents a connection
to a %BITalino device. The connection is established in the constructor and released in the destructor,
thus following the RAII paradigm. An application can create several instances (to distinct devices).
The library is thread-safe between distinct instances. Each instance can be a local variable
(as in the sample application) or it can be allocated on the heap (using new and delete operators).
\section sampleapp About the sample application
The sample application ([test.cpp](http://github.com/BITalinoWorld/cpp-api/tree/master/test.cpp)) creates an instance to a %BITalino device.
Then it starts acquiring all channels on the device at 1000 Hz and enters a loop while dumping
one frame out of 100 and toggling the device green LED. Pressing the Enter key exits the loop,
destroys the instance and closes the application.
One of the provided constructor calls must be used to connect to the device.
The string passed to the constructor can be a Bluetooth MAC address (you must change the one provided)
or a serial port. The serial port string format depends on the platform.
In order to have a more compact and readable code, the sample test code uses C++11 vector initializer lists.
This new C++ feature is supported only in Visual Studio 2013 or later (on Windows), GCC 4.4 or later and Clang 3.1
or later. If you are using an older compiler, use the commented alternative code for the `start()` and
`trigger()` methods calls.
\section windows Compiling on Windows
The API was tested in Windows 7 (32-bit and 64-bit).
To compile the library and the sample application:
- create a C++ Empty Project in Visual Studio;
- copy [bitalino.cpp](http://github.com/BITalinoWorld/cpp-api/tree/master/bitalino.cpp),
[bitalino.h](http://github.com/BITalinoWorld/cpp-api/tree/master/bitalino.h) and
[test.cpp](http://github.com/BITalinoWorld/cpp-api/tree/master/test.cpp) to the project directory;
- add bitalino.cpp and test.cpp files to the project at the “Source Files” folder;
- edit test.cpp as described \ref sampleapp "above";
- add a reference to `ws2_32.lib` in Project Properties → Configuration Properties → Linker → Input → Additional Dependencies;
- build the solution and run the application.
\section linux Compiling on Linux
The API was tested in Ubuntu (32-bit and 64-bit) and Raspberry Pi (Raspbian).
To compile the library and the sample application:
- `make` and `g++` must be installed;
- packages `bluez`, `libbluetooth3` and `libbluetooth-dev` must be installed if you want to
compile the library with Bluetooth functionality (to search for Bluetooth devices and
to make direct Bluetooth connections);
- copy [bitalino.cpp](http://github.com/BITalinoWorld/cpp-api/tree/master/bitalino.cpp),
[bitalino.h](http://github.com/BITalinoWorld/cpp-api/tree/master/bitalino.h),
[test.cpp](http://github.com/BITalinoWorld/cpp-api/tree/master/test.cpp) and
[Makefile](http://github.com/BITalinoWorld/cpp-api/tree/master/Makefile) into a new directory;
- if you want to compile the library without Bluetooth functionality, disable the line in
Makefile where `LINUX_BT` is defined;
- if your compiler doesn't support vector initializer lists, remove the flag `-std=c++0x`
from the test.cpp compiling rule in Makefile;
- edit test.cpp as described \ref sampleapp "above";
- enter command `make` in the command line to build the library and the application;
- enter command `./test` in the command line to run the application.
\section macosx Compiling on Mac OS X
The API was tested in Mac OS X 10.6 and 10.9.
On Mac OS X, the %BITalino API Bluetooth functionality is not available, so it is only possible
to connect to a %BITalino device through a serial port for indirect Bluetooth connections or for wired UART connections.
To compile the library and the sample application:
- copy [bitalino.cpp](http://github.com/BITalinoWorld/cpp-api/tree/master/bitalino.cpp),
[bitalino.h](http://github.com/BITalinoWorld/cpp-api/tree/master/bitalino.h),
[test.cpp](http://github.com/BITalinoWorld/cpp-api/tree/master/test.cpp) and
[Makefile](http://github.com/BITalinoWorld/cpp-api/tree/master/Makefile) into a new directory;
- if your compiler doesn't support vector initializer lists, remove the flag `-std=c++0x` from
the test.cpp compiling rule in Makefile;
- edit test.cpp as described \ref sampleapp "above";
- enter command `make` in the command line to build the library and the application;
- enter command `./test` in the command line to run the application.
*/
#ifndef _BITALINOHEADER_
#define _BITALINOHEADER_
#include <string>
#include <vector>
#ifdef _WIN32 // 32-bit or 64-bit Windows
#include <winsock2.h>
#endif
/// The %BITalino device class.
class BITalino
{
public:
// Type definitions
typedef std::vector<bool> Vbool; ///< Vector of bools.
typedef std::vector<int> Vint; ///< Vector of ints.
/// Information about a Bluetooth device found by BITalino::find().
struct DevInfo
{
std::string macAddr; ///< MAC address of a Bluetooth device
std::string name; ///< Name of a Bluetooth device
};
typedef std::vector<DevInfo> VDevInfo; ///< Vector of DevInfo's.
/// A frame returned by BITalino::read()
struct Frame
{
/// %Frame sequence number (0...15).
/// This number is incremented by 1 on each consecutive frame, and it overflows to 0 after 15 (it is a 4-bit number).
/// This number can be used to detect if frames were dropped while transmitting data.
char seq;
/// Array of digital ports states (false for low level or true for high level).
/// On original %BITalino, the array contents are: I1 I2 I3 I4.
/// On %BITalino 2, the array contents are: I1 I2 O1 O2.
bool digital[4];
/// Array of analog inputs values (0...1023 on the first 4 channels and 0...63 on the remaining channels)
short analog[6];
};
typedef std::vector<Frame> VFrame; ///< Vector of Frame's.
/// Current device state returned by BITalino::state()
struct State
{
int analog[6], ///< Array of analog inputs values (0...1023)
battery, ///< Battery voltage value (0...1023)
batThreshold; ///< Low-battery LED threshold (last value set with BITalino::battery())
/// Array of digital ports states (false for low level or true for high level).
/// The array contents are: I1 I2 O1 O2.
bool digital[4];
};
/// %Exception class thrown from BITalino methods.
class Exception
{
public:
/// %Exception code enumeration.
enum Code
{
INVALID_ADDRESS = 1, ///< The specified address is invalid
BT_ADAPTER_NOT_FOUND, ///< No Bluetooth adapter was found
DEVICE_NOT_FOUND, ///< The device could not be found
CONTACTING_DEVICE, ///< The computer lost communication with the device
PORT_COULD_NOT_BE_OPENED, ///< The communication port does not exist or it is already being used
PORT_INITIALIZATION, ///< The communication port could not be initialized
DEVICE_NOT_IDLE, ///< The device is not idle
DEVICE_NOT_IN_ACQUISITION, ///< The device is not in acquisition mode
INVALID_PARAMETER, ///< Invalid parameter
NOT_SUPPORTED, ///< Operation not supported by the device
} code; ///< %Exception code.
Exception(Code c) : code(c) {} ///< Exception constructor.
const char* getDescription(void); ///< Returns an exception description string
};
// Static methods
/** Searches for Bluetooth devices in range.
* \return a list of found devices
* \exception Exception (Exception::PORT_INITIALIZATION)
* \exception Exception (Exception::BT_ADAPTER_NOT_FOUND)
*/
static VDevInfo find(void);
// Instance methods
/** Connects to a %BITalino device.
* \param[in] address The device Bluetooth MAC address ("xx:xx:xx:xx:xx:xx")
* or a serial port ("COMx" on Windows or "/dev/..." on Linux or Mac OS X)
* \exception Exception (Exception::PORT_COULD_NOT_BE_OPENED)
* \exception Exception (Exception::PORT_INITIALIZATION)
* \exception Exception (Exception::INVALID_ADDRESS)
* \exception Exception (Exception::BT_ADAPTER_NOT_FOUND) - Windows only
* \exception Exception (Exception::DEVICE_NOT_FOUND) - Windows only
*/
BITalino(const char *address);
/// Disconnects from a %BITalino device. If an aquisition is running, it is stopped.
~BITalino();
/** Returns the device firmware version string.
* \remarks This method cannot be called during an acquisition.
* \exception Exception (Exception::DEVICE_NOT_IDLE)
* \exception Exception (Exception::CONTACTING_DEVICE)
*/
std::string version(void);
/** Starts a signal acquisition from the device.
* \param[in] samplingRate Sampling rate in Hz. Accepted values are 1, 10, 100 or 1000 Hz. Default value is 1000 Hz.
* \param[in] channels Set of channels to acquire. Accepted channels are 0...5 for inputs A1...A6.
* If this set is empty or if it is not given, all 6 analog channels will be acquired.
* \param[in] simulated If true, start in simulated mode. Otherwise start in live mode. Default is to start in live mode.
* \remarks This method cannot be called during an acquisition.
* \exception Exception (Exception::DEVICE_NOT_IDLE)
* \exception Exception (Exception::INVALID_PARAMETER)
* \exception Exception (Exception::CONTACTING_DEVICE)
*/
void start(int samplingRate = 1000, const Vint &channels = Vint(), bool simulated = false);
/** Stops a signal acquisition.
* \remarks This method must be called only during an acquisition.
* \exception Exception (Exception::DEVICE_NOT_IN_ACQUISITION)
* \exception Exception (Exception::CONTACTING_DEVICE)
*/
void stop(void);
/** Reads acquisition frames from the device.
* This method returns when all requested frames are received from the device, or when 5-second receive timeout occurs.
* \param[out] frames Vector of frames to be filled. If the vector is empty, it is resized to 100 frames.
* \return Number of frames returned in frames vector. If a timeout occurred, this number is less than the frames vector size.
* \remarks This method must be called only during an acquisition.
* \exception Exception (Exception::DEVICE_NOT_IN_ACQUISITION)
* \exception Exception (Exception::CONTACTING_DEVICE)
*/
int read(VFrame &frames);
/** Sets the battery voltage threshold for the low-battery LED.
* \param[in] value Battery voltage threshold. Default value is 0.
* Value | Voltage Threshold
* ----- | -----------------
* 0 | 3.4 V
* ... | ...
* 63 | 3.8 V
* \remarks This method cannot be called during an acquisition.
* \exception Exception (Exception::DEVICE_NOT_IDLE)
* \exception Exception (Exception::INVALID_PARAMETER)
* \exception Exception (Exception::CONTACTING_DEVICE)
*/
void battery(int value = 0);
/** Assigns the digital outputs states.
* \param[in] digitalOutput Vector of booleans to assign to digital outputs, starting at first output (O1).
* On each vector element, false sets the output to low level and true sets the output to high level.
* If this vector is not empty, it must contain exactly 4 elements for original %BITalino (4 digital outputs)
* or exactly 2 elements for %BITalino 2 (2 digital outputs).
* If this parameter is not given or if the vector is empty, all digital outputs are set to low level.
* \remarks This method must be called only during an acquisition on original %BITalino. On %BITalino 2 there is no restriction.
* \exception Exception (Exception::DEVICE_NOT_IN_ACQUISITION)
* \exception Exception (Exception::INVALID_PARAMETER)
* \exception Exception (Exception::CONTACTING_DEVICE)
*/
void trigger(const Vbool &digitalOutput = Vbool());
/** Assigns the analog (PWM) output value (%BITalino 2 only).
* \param[in] pwmOutput Analog output value to set (0...255).
* The analog output voltage is given by: V (in Volts) = 3.3 * (pwmOutput+1)/256
* \exception Exception (Exception::INVALID_PARAMETER)
* \exception Exception (Exception::CONTACTING_DEVICE)
* \exception Exception (Exception::NOT_SUPPORTED)
*/
void pwm(int pwmOutput = 100);
/** Returns current device state (%BITalino 2 only).
* \remarks This method cannot be called during an acquisition.
* \exception Exception (Exception::DEVICE_NOT_IDLE)
* \exception Exception (Exception::CONTACTING_DEVICE)
* \exception Exception (Exception::NOT_SUPPORTED)
*/
State state(void);
private:
void send(char cmd);
int recv(void *data, int nbyttoread);
void close(void);
char nChannels;
bool isBitalino2;
#ifdef _WIN32
SOCKET fd;
timeval readtimeout;
HANDLE hCom;
#else // Linux or Mac OS
int fd;
bool isTTY;
#endif
};
#endif // _BITALINOHEADER_
#include "ofApp.h"
#include "ofAppGlutWindow.h"
//========================================================================
int main( ){
ofAppGlutWindow window;
ofSetupOpenGL( &window, 1024, 768, OF_WINDOW ); // <-------- setup the GL context
ofRunApp( new ofApp( ) );
}
#include "ofApp.h"
//--------------------------------------------------------------
void ofApp::setup ( void )
{
ofSetWindowTitle( "Rapid Bitalino Example" );
sampleRate = 44100; /* Sampling Rate */
bufferSize = 512; /* Buffer Size. you have to fill this buffer with sound using the for loop in the audioOut method */
ofSetVerticalSync( true );
ofEnableAlphaBlending( );
ofEnableSmoothing( );
ofBackground( 0, 0, 0 );
// Enter the name of your BITalino board here
bitEnvironment.setup( "/dev/cu.BITalino-DevB", ofRectangle( 0, 0, 1024, 768 ) );
ofxMaxiSettings::setup( sampleRate, 2, bufferSize );
// sets up and starts a global ofSoundStream.
ofSoundStreamSetup( 2, 0, sampleRate, bufferSize, 4 );
}
//--------------------------------------------------------------
void ofApp::update ( void )
{
bitEnvironment.update( );
}
//--------------------------------------------------------------
void ofApp::draw ( void )
{
ofBackground( 0, 0, 0 );
bitEnvironment.draw( );
}
//--------------------------------------------------------------
void ofApp::audioOut ( float *output, int bufferSize, int nChannels )
{
bitEnvironment.audioOut ( output, bufferSize, nChannels );
}
//--------------------------------------------------------------
void ofApp::exit ( void )
{
printf( "stopping...\n" );
// rapidBitalino.stop( );
bitEnvironment.stop( );
}
//--------------------------------------------------------------
void ofApp::keyPressed ( int key )
{
if ( key == '1' && !pr ) {
bitEnvironment.bst.buttonPressed( );
printf( "1 down\n" );
pr = true;
}
}
//--------------------------------------------------------------
void ofApp::keyReleased ( int key )
{
if ( key == '1' ) {
bitEnvironment.bst.buttonReleased( );
printf( "1 up\n" );
pr = false;
}
}
void ofApp::updateMouse ( int x, int y, int8_t mouseButtonState )
{
// Only really one mouse function necessary for this example:
if ( mouseButtonState != this->mouseButtonState )
{
mouseButtonChanged = true;
this->mouseButtonState = mouseButtonState;
} else
mouseButtonChanged = false;
ofVec2f mousePos( x, y );
if ( bitEnvironment.isMouseInside( mousePos ) )
{
bitEnvironment.mouseActive( mousePos, mouseButtonState, mouseButtonChanged );
}
}
//--------------------------------------------------------------
void ofApp::mouseMoved ( int x, int y )
{
updateMouse( x, y, mouseButtonState );
}
//--------------------------------------------------------------
void ofApp::mouseDragged ( int x, int y, int button )
{
updateMouse( x, y, button );
}
//--------------------------------------------------------------
void ofApp::mousePressed ( int x, int y, int button )
{
//std::cout << "BUTTON: " << button << std::endl;
updateMouse( x, y, button );
}
//--------------------------------------------------------------
void ofApp::mouseReleased ( int x, int y, int button )
{
updateMouse( x, y, -1 );
}
//--------------------------------------------------------------
void ofApp::mouseEntered( int x, int y )
{
updateMouse( x, y, mouseButtonState );
}
//--------------------------------------------------------------
void ofApp::mouseExited ( int x, int y )
{
updateMouse( x, y, mouseButtonState );
}
//--------------------------------------------------------------
void ofApp::windowResized ( int w, int h )
{
bitEnvironment.resize( ofRectangle( 0, 0, w, h ) );
}
//--------------------------------------------------------------
void ofApp::gotMessage ( ofMessage msg )
{
}
//--------------------------------------------------------------
void ofApp::dragEvent ( ofDragInfo dragInfo )
{
}
#pragma once
#include "ofMain.h"
#include "ofxMaxim.h"
#include "RapidBitalino.h"
#include "BITEnvironment.hpp"
class ofApp : public ofBaseApp{
public:
void setup ( void );
void update ( void );
void draw ( void );
void audioOut ( float * output, int bufferSize, int nChannels );
void exit ( void );
void keyPressed ( int key );
void keyReleased ( int key );
void updateMouse ( int x, int y, int8_t mouseButtonState );
void mouseMoved ( int x, int y );
void mouseDragged ( int x, int y, int button );
void mousePressed ( int x, int y, int button );
void mouseReleased ( int x, int y, int button );
void mouseEntered ( int x, int y );
void mouseExited ( int x, int y );
void windowResized ( int w, int h );
void dragEvent ( ofDragInfo dragInfo );
void gotMessage ( ofMessage msg );
private:
int8_t mouseButtonState = -1;
bool mouseButtonChanged = false;
//DEBUG
bool pr = false;
//---
BIT::Environment bitEnvironment;
int bufferSize;
int sampleRate;
};
File added
File added
File added
File added