Sans les mains ! (mais avec le cerveau…) : Contrôler la lumière par la pensée

IMG_20150217_170350

Un jour pluvieux de décembre je suis tombé sur ce site :

http://neurosky.com/products-markets/eeg-biosensors/

Ma première réaction fût : « Ouah, cela fait peur… » Je dois dire que ne n’ai jamais été trop transhumaniste.

La seconde réaction fût : « Ouah, ils fournissent la doc pour faire communiquer leur engin avec l’Arduino »

Et la troisième fût : « Ouah, les possibilités sont infinies… »

C’était trop tard, j’était contaminé.

Note importante du 12/12/17 : ce projet à été réalisé en 2015, il se peut (c’est même probable) que beaucoup de choses aient changées depuis. Aussi il y a peu de chances que le code fourni fonctionne « tel quel », et des ajustements seront sûrement nécessaires. Je m’occuperai de mettre à jour le projet dès que j’aurai un peu de temps.

J’ai donc commandé la bête :

IMG_20150217_170140

Mais concrètement, le Neurosky Mindwave, c’est quoi ?

En fait, c’est un électroencéphalographe portatif.

Le casque, à l’aide d’un algorithme propriétaire ( nul n’est parfait…) renvoie donc, par bluetooth, toute une série de valeurs intéressantes, dont voici la liste :

* « Poor Signal Quality » : une valeur entière comprise entre 0 et 200. Toute valeur != de 0 indique qu’une perturbation (bruit) est détectée. La valeur 200 est une valeur spéciale : elle signifie que les contacts du casque ne touchent pas la peau.

* « Attention » qui est plus ou moins la même chose que « concentration », et « méditation », qui équivaut à « relaxation ». Ces deux valeurs varient (sont significatives ?) entre 0 et 100. Leur signification est :

– entre 1 et 20 : très réduite

– entre 21 et 40 : réduite

– entre 40 et 60 : neutre (correspond aux lignes de bases d’un EEG)

– entre 60 et 80 : significativement élevée, peut être considéré comme supérieur à la normale

– entre 80 et 100 : élevée.

* « Raw » : c’est un entier codé sur 16 bits (-32768 à 32767), correspond à des données brutes

* « Blink » : intensité du dernier clignement d’oeil, varie entre 0 et 255, valeur renvoyée à chaque fois qu’un clignement d’oeil est détecté.

* les ondes EEC : « ASIC_EEG_POWER », qui représentent le niveau de 8 ondes cérébrales EEG, on peut récupérer 1 envoi / seconde :

– delta (0,5 – 2,75 Hz)

– theta (3,5 – 6,75 Hz)

– low alpha (7,5 – 9,25 Hz)

– high alpha (10 – 11, 75 Hz)

– low beta (13 – 16,75 Hz)

– high beta (18, 29,75 Hz)

– low gama (31 – 39,75 Hz)

– mid gama (41 – 49,75 Hz)

Les rackets envoyés par le Mindwave sont de la forme :

[sync] [sync] [plength] [payload] [chksum]

1 bit     1 bit     1 bit     0 – 169 bit  1 bit

Un paquet fait donc entre 4 et 173 bits.

Voilà pour la théorie, place maintenant à ce qui nous intéresse le plus : interfacer cela avec un Arduino

Le but : Faire varier la couleur d’une led RGB en fonction de mon état de concentration ou de relaxation.

Plus la Led tend vers le bleu, plus je suis relaxé, plus elle tend vers le rouge, plus je suis concentré.

Le matériel nécessaire :

– 1 casque Mindwave Neurosky

– 1 Arduino (Uno)

– 1 led RGB

– 1 carte bluetooth

– 1 boîte en carton trouvée au magasin de loisirs créatifs du coin

Le montage :

IMG_20150217_170317IMG_20150217_170350

Rien de bien compliqué… Cela s’est compliqué (pour moi) avec la partie bluetooth.

Donc, un grand merci à :

http://developer.neurosky.com/

https://learn.sparkfun.com/tutorials/hackers-in-residence—hacking-mindwave-mobile

http://forum.arduino.cc/ (forcément…)

Sans lesquels je serais devenu fou (un comble pour qui veut récupérer des ondes cérébrales…).

Note : attention, avant de commencer, il est nécessaire de réaliser quelques manipulations sur la carte BT afin qu’elle puisse accepter les connexions sans authentification : ces manipulations sont décrites ici : https://learn.sparkfun.com/tutorials/hackers-in-residence—hacking-mindwave-mobile à la section « configuring the bluetooth module »

Ce qui m’a permis d’écrire le code suivant :


#include <ChainableLED.h> // lib pour la led RGB grove
#include <SoftwareSerial.h>
#include <ThinkGearStreamParser.h>
#define RxD 6 // pin reception
#define TxD 7 // pin transmition
#define LED 13 // led intégrée

#define DEBUGOUTPUT 0 //jamais utilise
#define SYNC 170 // longueur des paquets < 170 pour etres valides
#define EXCODE 85 // jamais utilise
#define WAITING_FOR_SYNC1 0 // premier etat de syncro, attend le « 170 » sync signal
#define WAITING_FOR_SYNC2 1 // second etat de syncro, attend encore « 170 » sync signal
#define WAITING_FOR_PLENGTH 2 // recupere la longueur du « payload » (doit toujours etre 0x20)
#define GETTING_PAYLOAD 3 // recupere tous les bytes d’information
#define WAITING_FOR_CHECKSUM 4 // verifie que le paquet est correctement reçu
#define SIGNAL_QUALITY 0x02 // verifie la qualite du signal
#define ATTENTION 0x04 // renvoie un nombre representant l’attention
#define MEDITATION 0x05 // renvoie un nombre representant le niveau de mediation
#define BLINK_STR 0x16 // envoye seulement si un clignement des yeux (blink) est detecte
#define RAW_WAVE 0x80 // libre, non utilise
#define ASIC_EGG_POWER 0x83 // libre, non utilise
#define DEBUG_ENABLED 0 // jamais utilise

ChainableLED leds(2, 3, 1); // led RVB Grove sur pin 2/3

String retSymb = « +RTINQ= »; //symbole de debut quand il n’y a aucun retour
String slaveName= »;MindWave Mobile »; // Attention, le « ; » doit etre inclus
int nameIndex = 0; // utilise pour la connexion serie
int addrIndex = 0; // utilise pour la connexion serie
String recvBuf; // utilise pour la connexion serie
String slaveAddr; // utilise pour la connexion serie
String connectCmd = « \r\n+CONN= »;

// Variables d’etat de l’arduino
byte generatedChecksum = 0;
byte checksum = 0;
int payloadLength = 0;
int state = WAITING_FOR_SYNC1; // init a l’etat correct
byte payload[256] = { 0 }; // cree un grand tableau de 0 pour stocker l’information
byte poorSignal = 0;
byte attention = 0;
byte meditation = 0;
byte blinkstr = 0;

// Variables systeme
long lastReceivedPacket = 0;
boolean bigPacket = false;

SoftwareSerial blueToothSerial = SoftwareSerial(RxD,TxD);

void setup() {
Serial.begin(115200);
pinMode(RxD, INPUT);
pinMode(TxD, OUTPUT);
pinMode(LED, OUTPUT);
digitalWrite(LED, LOW);

leds.setColorRGB(0, 0, 0, 0);

setupBlueToothConnection();
delay(1000); // attente d’1 sec pour viter le buffer
Serial.flush();
blueToothSerial.flush();
delay(3000);
}

// fontion de lecture bluetooth
byte ReadOneByte() { // fonction qui retourne 1 byte depuis la connection serie bluetooth
byte byteRead;
byteRead = blueToothSerial.read();
return byteRead;
}

// fonction de reception bluetooth
void loop() {
byte byteRead; // variable temporaire pour stocker les données reçues depuis le bluetooth
int checksum = 0; //
while(1) {
byteRead = ReadOneByte();
switch(state) {
case(WAITING_FOR_SYNC1):
if(byteRead == SYNC) { // si bluetooch envoie ‘170’, c’est correct
state = WAITING_FOR_SYNC2; // on va a l’etat suivant
}
break;
case(WAITING_FOR_SYNC2): // double veriffication
if(byteRead == SYNC) { // si bluetooth envoie un second ’17°’, la syncro est correcte
state = WAITING_FOR_PLENGTH; // on y va
} else { state = WAITING_FOR_SYNC1; } // sinon, on retourne en arriere
break;
case(WAITING_FOR_PLENGTH):
if(byteRead == 0x20) { // paquet valide reçu
Serial.println(« eSense packet reçu ! »);
state = GETTING_PAYLOAD;
payloadLength = byteRead; // doit toujours etre 0x20
} else { state = WAITING_FOR_SYNC1; } // si bruit de fond, non valide, retour au debut
break;
case(GETTING_PAYLOAD):
{
int i = 1; // il faut faire la première iteration manuelle
generatedChecksum = byteRead; // lit le bluetooth et sauve la checksum
payload[0] = byteRead;
for(i; i<payloadLength; i++) { // lit 32 bits depuis le bluetooth serial buffer
byteRead = ReadOneByte();
payload[i] = byteRead;
generatedChecksum += byteRead; // generatedChecksum = somme des bits du payload
}
}
// inversion de 8 bits faibles de la somme de controle generee
generatedChecksum = ~generatedChecksum;
state = WAITING_FOR_CHECKSUM;
break;
case(WAITING_FOR_CHECKSUM):
checksum = byteRead;
state = WAITING_FOR_SYNC1; // retour au debut
if(checksum == generatedChecksum) { // si la checksum est OK, on parse le payload
parsepayload();
}
break;
}
}
}

// fonction de parsing du payload
void parsepayload() { // traduit le payload en données utilisables
int i = 0;
// int excodeCounter = 0; // jamais utilise
byte codeByte;
while(i < payloadLength) {
byte currentByte = payload[i];
i++;
// le bit suivant le excode est le code byte
codeByte = currentByte;
currentByte = payload[i];
switch(codeByte) {
case(SIGNAL_QUALITY):
Serial.println(« signal quality: « );
Serial.println(currentByte);
poorSignal = currentByte;
if(poorSignal == 200) {
digitalWrite(LED, HIGH);
} else { digitalWrite(LED, LOW);}
break;

case(ATTENTION):
Serial.println(« Attention : « );
Serial.println(currentByte);
attention = currentByte;

// mettre ici l’action a effectuer (leds…)

if(attention > 30) {
int colorR = map(attention,0,100,0,255); // plus on fait attention, plus on tends vers le rouge
int colorG = 255 – colorR;
int colorB = 20;

leds.setColorRGB(0, colorR, colorG, colorB);

} else {

}

break;

case(MEDITATION): // valeur entre 0 et 100
Serial.println(« Mediation : « );
Serial.println(currentByte);
meditation = currentByte;

// mettre ici l’action a effectuer (leds…)
if(meditation > 30) {
int colorR = 20;
int colorB = map(meditation,0,100,0,255); // plus on fait attention, plus on tend vers le bleu
int colorG = 255 – colorG;

leds.setColorRGB(0, colorR, colorG, colorB);

} else { }
break;

case(BLINK_STR):
Serial.println(« Blink strenght: « );
Serial.println(currentByte);
blinkstr = currentByte;
// mettre ici l’action a effectuer (leds…)
break;

case(RAW_WAVE):
i+=2; // On efface 2 bytes
break;

case(ASIC_EGG_POWER):
i+=23; // On efface 23 bytes
break;
}
}
}

// bluetooth setup
void setupBlueToothConnection() {
blueToothSerial.begin(38400); //On met le taux de transmission à 38400
blueToothSerial.print(« \r\n+STBD=57600\r\n »);
blueToothSerial.begin(57600);
blueToothSerial.print(« \r\n+STWMOD=1\r\n »); // mets le bluetooth en mode maitre
blueToothSerial.print(« \r\n+STNA=SeedBTMaster\r\n »); //set le nom bluetooth a « SeedBTMaster »
blueToothSerial.print(« \r\n+STAUTO=1\r\n »); // auto-connection interdite
delay(2000); // requis
blueToothSerial.flush();
blueToothSerial.print(« \r\n+INQ=1\r\n »); // Le maitre recherche
Serial.println(« Recherche… ! »);
delay(2000); //requis
int connectOK = 0;

// cherche la cible esclave
char recvChar;
while(1){
if(blueToothSerial.available()){
recvChar = blueToothSerial.read();
recvBuf += recvChar;
Serial.println(recvChar);
}
nameIndex = recvBuf.indexOf(slaveName); // trouve la position du nom de l’esclave
//nameIndex -=1; //decroit le « ; » devant le nom de l’esclave, pour avoir la position du la fin de l’adresse de l’esclave
if(nameIndex != -1) {
Serial.println(recvBuf);
addrIndex = recvBuf.indexOf(retSymb,(nameIndex – retSymb.length() – 18) + retSymb.length()); // trouve le debut le l’adresse de l’esclave
slaveAddr = recvBuf.substring(addrIndex, nameIndex); // prend la chaine de l’adresse esclave

// creation de la commande de connexion
connectCmd += slaveAddr;
connectCmd += « \r\n »;
Serial.println(« Connexion a l’esclave « );
Serial.println(slaveAddr);
Serial.println(slaveName);
break;

}

if(recvBuf.indexOf(« CONNECT:OK ») != -1) {
connectOK = 1;
Serial.println(« Connecté ! »);
break;
}
}
Serial.println( » « ); // essaye de se connecter jusqu’a ce qu’il soient connectés
while(0==connectOK){
blueToothSerial.print(connectCmd); // envoie la commande de connection
recvBuf= » »;
while(1) {
if(blueToothSerial.available()) {
recvChar = blueToothSerial.read();
recvBuf += recvChar;
if(recvBuf.indexOf(« CONNECT:OK ») != -1) {
connectOK = 1;
Serial.println(« Connecté ! »);
break;
} else if(recvBuf.indexOf(« CONNECT:FAIL ») != -1) {
Serial.println( » Re-tentative de connexion « );
break;
}
}
}
}
}


Normalement, les commentaires parlent d’eux mêmes.

En gros, on se débrouille pour connecter l’Arduino au Mindwave, sachant que ce dernier se comporte en maître. Puis, on récupère les valeurs intéressantes (Poor signal quality, Blink, attention, méditation).

Les valeurs « attention » et « méditation » font varier la couleur de la led RGB.

Une petite vidéo pour mieux comprendre (cliquez sur la photo) :

IMG_20150217_170350

La suite ?

J’envisage d’utiliser ce montage pour mieux apprendre à me connaître, mieux utiliser mes capacités mentales. Ensuite, le graal : voir dans quelle mesure je peux contrôler des actionneurs, et pourquoi pas un robot. Que du bonheur en perspective !

Edit du 19/02/17 :

Les librairies fournies par Grove utilisées dans ce projet (dont ChainableLED.h) génèrent une erreur sous les dernières versions de l’IDE Arduino. il est nécessaire d’utiliser la version 1.0.6 téléchargeable ici. Si quelqu’un à des news par rapport à ce problème, qu’il n’hésite pas à me contacter !

Comme toujours, pour tout commentaire / question, c’est par ici :


5 réflexions sur “Sans les mains ! (mais avec le cerveau…) : Contrôler la lumière par la pensée

  1. salut en ce moment je m’intéresse aux prothèses et je me demandait si cela serait faisable d’en commander une grâce a cet objet. Je te laisse mon adresse mail si mon idée t’intéresse car honnêtement je ne m’y connait pas du tout en programmation et onde cérébrale … ^^ Auguste.castellan@gmail.com

    J’aime

    • Bonjour, à priori, je ne pense pas que les données récupérées avec le casque soient suffisamment précises pour pouvoir actionner une prothèse. Ceci dit, je ne suis pas forcément un « pro » de ce système.
      Pour plus d’infos sur ce qu’il est possible d’en faire, tu peux aller voir sur http://neurosky.com/ Tu auras alors un meilleur aperçu des possibilités de l’engin.
      Bonne journée,
      Vincent.

      J’aime

  2. Pingback: Top projets Arduino WTF - Guillaume HARARI

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.