Trabajando con Mindwave Mobile

MindWave

MindWaveMindWave Mobile de NeuroSky es una diadema que capta las ondas EGG y el pestañeo del usuario que la porta. Como su nombre sugiere, esta pensada para usarse con dispositivos móviles(IOS y Android) y computadoras de escritorio Mac o Windows. Cuenta con varias aplicaciones compatibles en las diferentes tiendas móviles(Apptore y PlaySotre), de esta forma, podemos probar sus capacidades nada más sacarla de la caja. Estas aplicaciones también vienen incluidas en el DVD que acompaña el producto.

Lo interesante de este aparato es que como desarrolladores tenemos a un costo reducido ($129.99 dólares actualmente), un sensor EEG con interfaz para conectarse a dispositivos móviles fácilmente y poder utilizarlo en juegos y aplicaciones.

En el sitio developer.neurosky.com podemos encontrar documentos, tutoriales y aplicaciones de prueba para distintas plataformas como son Android, Ios, Unity 3d, Arduino, Raspberry Pi, .Net, entre otras.

Algo curioso es que para descargar las aplicaciones de prueba, hay que hacerlo a través de la tienda de NeuroSky como si de un producto más se tratara, incluso hay que proporcionar nuestra dirección para entrega. Eso si, el costo de estas aplicaciones esta en $0.00 dólares, por lo que no se debe preocupar uno de pagar nada.

Tienda de Neurosky

MindWave Mobile en Android

Para Android, NeuroSky nos provee de una API en Java para interactuar con la diadema, documentación, una guía en pdf y una aplicación de prueba en donde se hace uso de las funciones más comunes de esta API. El paquete se puede obtener en http://store.neurosky.com/products/developer-tools-3-android. Una vez completados los pasos de la tienda, recibiremos un correo electrónico con un enlace para descargar un archivo .zip con todo lo antes mencionado.

La aplicación de ejemplo es un text view dentro de un scroll en donde se va mostrando lo que se recibe de la diadema y un botón para conectar con el dispositivo.

Neurosky Screenshot
Así luce la aplicación de ejemplo incluida en el kit para Android

Conociendo la API

La clase básica que vamos a utilizar en cualquier aplicación es la clase TGDevice ubicada en el paquete com.neurosky.thinkgear.TGDevice

El constructor

TGDevice(BluetoothAdapter btAdapter, Handler handler)

El constructor de la clase TGDevice  recibe un adaptdor de Bluetooth y un manejador o handler, que veremos más adelante.

Los mensajes de la diadema
Antes que nada, debemos conocer las siguientes constantes que están dentro de la clase TGDevice que representan los tipos de mensajes que se pueden recibir:

MSG_ATTENTION
Indica que el mensaje recibido incluye el valor del nivel de atención del usuario(el valor recibido oscila entre 0 y 100)

MSG_BLINK
Indica que se ha producido un pestañeo. El mensaje recibido incluye un valor de 0 a 100 que es la precisión en detectar el parpadeo

MSG_HEART_RATE
Indica que el mensaje recibido es el pulso cardiaco. Nota: El producto Mindwave Mobile no es capaz de obtener el pulso cardiaco, pero viene incluido porque la API sirve para varios productos de NeuroSky

MSG_LOW_BATTERY
Es un mensaje para indicar que la batería del dispositivo tiene poca carga

MSG_MEDITATION
Indica que el mensaje recibido incluye el valor del nivel de meditación del usuario(el valor recibido oscila entre 0 y 100)

MSG_POOR_SIGNAL
Indica que la señal recibida del dispositivo es muy baja, el valor del mensaje es el valor de la señal.

MSG_RAW_DATA
Indica que el mensaje recibido contiene los datos en brutos de la lectura EEG que da el dipositivo.

MSG_STATE_CHANGE
Es un mensaje para indicar que el estado del dispositivo ha cambiado. El valor del mensaje puede ser uno de los siguientes estados del dispositivo:

Estados del dispositivo

STATE_CONNECTED
Indica que el dispositivo se encuentra correctamente conectado

STATE_CONNECTING
Indica que el dispositivo ha sido encontrado y se esta realizando la conexión

STATE_DISCONNECTED
Indica que el dispositivo se encuentra desconectado

STATE_NOT_FOUND
Indica que el dispositivo no pude ser encontrado

STATE_NOT_PAIRED
Indica que no hay ningún dispositivo pareado mediante Bluetooth

STATE_IDLE
Indica que el dispositivo esta en modo reposo

Utilizando la API

Para comprender el funcionamiento y características de la API crearemos una aplicación para probar sus funcionalidades con el siguiente aspecto:

Aspecto de la aplicación

El xml de la vista anterior:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.ejemplomindwavemobile.MainActivity$PlaceholderFragment" >

    <Button
        android:id="@+id/btnConectar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:text="Conectar" />

    <TextView
        android:id="@+id/txtEstado"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/btnConectar"
        android:layout_alignLeft="@+id/textView1"
        android:layout_alignTop="@+id/btnConectar"
        android:layout_toLeftOf="@+id/btnConectar"
        android:background="#eeeeee"
        android:paddingLeft="2dp"
        android:paddingTop="5dp"
        android:text="Estado del dispositivo" />

    <ScrollView
        android:id="@+id/scrollView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignRight="@+id/btnConectar"
        android:layout_below="@+id/TextView02"
        android:layout_marginTop="48dp" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" >

            <TextView
                android:id="@+id/txtRawdata"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:text="Data EEG en bruton--------------------" />

        </LinearLayout>
    </ScrollView>

    <TextView
        android:id="@+id/TextView02"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/scrollView1"
        android:layout_below="@+id/TextView01"
        android:layout_marginTop="22dp"
        android:text="Pestañeos" />

    <TextView
        android:id="@+id/TextView01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/TextView02"
        android:layout_below="@+id/textView1"
        android:layout_marginTop="21dp"
        android:text="Meditación:" />

    <TextView
        android:id="@+id/txtConcentracion"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/TextView01"
        android:layout_centerHorizontal="true"
        android:background="#eeeeee"
        android:text="0" />

    <TextView
        android:id="@+id/txtMeditacion"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/TextView02"
        android:layout_alignLeft="@+id/txtConcentracion"
        android:background="#eeeeee"
        android:text="0" />

    <TextView
        android:id="@+id/txtPestaneos"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/TextView02"
        android:layout_alignBottom="@+id/TextView02"
        android:layout_alignLeft="@+id/txtMeditacion"
        android:background="#eeeeee"
        android:text="0" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/TextView01"
        android:layout_below="@+id/lblEstado"
        android:layout_marginTop="48dp"
        android:text="Concentración:" />

    <CheckBox
        android:id="@+id/checkRaw"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/txtPestaneos"
        android:layout_alignParentRight="true"
        android:text="Ver raw" />

</RelativeLayout>

Lo primero que tenemos que hacer en una aplicación Android que use Mindwave Mobile es incluir la API contenida en el archivo ThinkGear.jar

Para la comunicación con la diadema, la aplicación debe solicitar el permiso para Bluetooth. En el archivo AndroidManifest.xml agregamos la siguiente linea: <uses-permission android:name=”android.permission.BLUETOOTH” />

<?xml version="1.0" encoding="utf-8" ?> 
<manifest>
    <uses-permission android:name="android.permission.BLUETOOTH" />
</manifest>

En el cuerpo de la aplicación como variables globales de la clase, tenemos que declarar un adaptador de bluetooth BluetoothAdapter para manejar la conexión, una variable del tipo TGDevice que es la que va a representar el dispositivo, un variable boleana activarRaw para indicar si queremos leer los datos en bruto o solo la concentración, meditación y pestañeos, una variable entera para llevar el conteo de los pestañeos y por último las variables para manejar la interfaz gráfica

	BluetoothAdapter bluetoothAdapter;
	com.neurosky.thinkgear.TGDevice tgDevice;
	boolean activarRaw = false;
	int pestaneos=0;

	//Variables de interfaz de usuario
	Button btnConectar;
	TextView txtEstado;
	TextView txtConcentracion;
	TextView txtMeditacion;
	TextView txtPestaneos;
	TextView txtRawdata;
	CheckBox checkRaw;

En el método onCreate de la aplicación obtenemos el bluetooth, definimos las referencias a los objetos de la interfaz gráfica y las acciones del botón y el checkBox

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		//Se obtiene un adaptador bluetooth
		bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
		//Se compruba si el bluetooth esta disponible
	    if(bluetoothAdapter == null) {
	    	//Indicar al usuario que el bluetooth no esta disponible
	    	Toast.makeText(this, "Bluetooth no disponible", Toast.LENGTH_LONG).show();
	    	finish(); //Terminar el programa
	    }else {
	    	//Crear un nuevo dispositivo con el adaptador bluetooth y el manejador
	    	tgDevice = new TGDevice(bluetoothAdapter, handler);
	    }

	    //Se obtiene la referencia a los objetos de la interfaz grafica
	    btnConectar = (Button)findViewById(R.id.btnConectar);
		txtEstado = (TextView)findViewById(R.id.txtEstado);
		txtConcentracion = (TextView)findViewById(R.id.txtConcentracion);
		txtMeditacion = (TextView)findViewById(R.id.txtMeditacion);
		txtPestaneos = (TextView)findViewById(R.id.txtPestaneos);
		txtRawdata = (TextView)findViewById(R.id.txtRawdata);
		checkRaw = (CheckBox)findViewById(R.id.checkRaw);

		//Acciones
		btnConectar.setOnClickListener(new View.OnClickListener() {
		    @Override
		    public void onClick(View v) {
		        conectar();
		    }
		});
		checkRaw.setOnCheckedChangeListener(new OnCheckedChangeListener(){
			@Override
			public void onCheckedChanged(CompoundButton arg0, boolean checked) {
				activarRaw=checked;
				tgDevice.stop();
				tgDevice.close();
				tgDevice.connect(activarRaw);
			}
		});
	}

La función conectar, que conecta la diadema y especifica si se quieren los datos en bruto o depurados ya(concentración, meditación y pestañeo). Comprueba que la diadema no este ya conectada.

    public void conectar() {
    	if(tgDevice.getState() != TGDevice.STATE_CONNECTING && tgDevice.getState() != TGDevice.STATE_CONNECTED)
    		tgDevice.connect(activarRaw);
    }

Y la parte más importante de todas, el manejador o handler, gracias a el podemos entender y comunicarnos con el dispositivo Mindwave. Su estructura es sencilla y fácil de usar.

	private final Handler handler = new Handler() {
	    @Override
	    public void handleMessage(Message msg) {
	    	switch (msg.what) {
	            case TGDevice.MSG_STATE_CHANGE:
	                switch (msg.arg1) {
		                case TGDevice.STATE_IDLE:
		                	txtEstado.setText("Dispositivo en reposo");
		                    break;
		                case TGDevice.STATE_CONNECTING:
		                	txtEstado.setText("Conectando...");
		                	break;
		                case TGDevice.STATE_CONNECTED:
		                	tgDevice.start();
		                	txtEstado.setText("Conectado");
		                    break;
		                case TGDevice.STATE_NOT_FOUND:
		                	txtEstado.setText("Dispositivo no encontrado");
		                	break;
		                case TGDevice.STATE_NOT_PAIRED:
		                	txtEstado.setText("Dispositivo no vinculado");
		                	break;
		                case TGDevice.STATE_DISCONNECTED:
		                	txtEstado.setText("Desconectado");
	                }

	                break;
	            case TGDevice.MSG_POOR_SIGNAL:
	                break;
	            case TGDevice.MSG_RAW_DATA:
	            	txtRawdata.setText(String.valueOf(msg.arg1)+"\n");
	            	break;
	            case TGDevice.MSG_HEART_RATE:
	                break;
	            case TGDevice.MSG_ATTENTION:
	            	txtConcentracion.setText(String.valueOf(msg.arg1));
	            	break;
	            case TGDevice.MSG_MEDITATION:
	            	txtMeditacion.setText(String.valueOf(String.valueOf(msg.arg1)));
	            	break;
	            case TGDevice.MSG_BLINK:
	            	pestaneos++;
	            	txtPestaneos.setText(String.valueOf(pestaneos));
	            	break;
	            case TGDevice.MSG_RAW_COUNT:
	            	break;
	            case TGDevice.MSG_LOW_BATTERY:
	            	Toast.makeText(getApplicationContext(), "¡Bateria baja!", Toast.LENGTH_LONG).show();
	            	break;
	            case TGDevice.MSG_RAW_MULTI:
	            	TGRawMulti rawM = (TGRawMulti)msg.obj;
	            default:
	            	break;
	    	}
	    }
	};

Conociendo esto ya se debe ser capaz de usar la API de Mindwave Mobile en otras aplicaciones Android o utilizar su homologa en otra tecnología como Unity o C# ya que el principio es el mismo.

El código fuente del proyecto está disponible en Github: https://github.com/notasdesoftware/EjemploMindwaveMobile

6 thoughts on “Trabajando con Mindwave Mobile

    1. Hola Andres, no está pensado para ello, además de no ser muy cómodo de usar.
      Como comentó en el artículo, para descargar el SDK nos pide datos de pago, pero no se cobra nada por qué el costo es cero. Y si, se permite “pagar” con Paypal.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *