El trastero de José Juan Valid XHTML 1.1 Valid CSS! Estilo de página alternativo
Artículo creado en 2009.
Valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración sobre 27 comentarios.

Bluetooth Remote Sensor

Bluetooth Remote Sensor

El Nokia N95 viene con un interesante sensor en su interior. Dispone de un acelerómetro de tres ejes (ortogonales entre sí) que permiten determinar las aceleraciones que se producen en el dispositivo. La pena del caso, es que no dispone de un giroscopio o "algo" similar que permita determinar la orientación respecto de lo que normalmente viene a ser nuestro sistema de referencia, el suelo. Claro que el propio acelerómetro sirve "casi" como giroscopio si se mantiene el centro de gravedad del mismo estático respecto del espacio, pero esto limita enormemente su aplicación práctica.

Ejes del acelerómetro del Nokia N95

La aplicación que se describe, se compone de los siguientes elementos:

Symbian S60

Hasta ahora los SDK para los móviles que yo venía utilizando se basaban en MIDlets programados en Java. Venía utilizando el excelente IDE NetBeans que tan buen resultado me ha dado. Java es un muy buen lenguaje de programación y las librerías disponibles son utilizadas por muchos fabricantes y una ingente cantidad de programadores que la hacen robusta y muy bien documentada.

Hace un tiempo que tenía ganas de meterle mano a los S60 de Symbian para poder programarlos en C++, que te permite un mayor control y cercanía con los dispositivos. Cual ha sido mi sorpresa al ver que la plataforma de desarrollo de Symbian (al menos para los S60) deja bastante que desear. No es que no vaya bien, pero existen una (demasiada grande) cantidad de circunstancias que la hacen bastante incómoda (al menos para mí).

No es este el sitio para documentar todos los incovenientes que le veo, pero sí los enumeraré rápidamente:

Vamos, que no estoy nada contento con el SDK, es bastante feo. Pero en fin, no todo es malo. Poco a poco, te vas "acostumbrando" a ciertas "incomodidades" (nunca vistas) y te vas centrando en lo que interesa.

Cliente Bluetooth

Debo reconocer que con mi móvil N95 debo tener algún problema (y/o en mi Windows XP), TODOS los ejemplos que he probado (código fuente o ya compilados) no me han permitido de forma satisfactoria establecer una comunicación serie. Curiosamente se supone (y normalmente lo es) que es la forma más simple de establecer comunicación. Yo (que soy muy cabezón) al final lo he conseguido, pero sólo cuando el móvil conecta con el PC (que es como "parece" que indican en una de las documentaciones).

Lo que ya no he conseguido (ni siquiera me van los MIDlet que sí me ivan en el Nokia 6288) es que el N95 despliegue un servicio Bluetooth y éste sea reconocido por el PC. En este sentido, de momento he desistido...

Por lo demás, realizar la conexión requiere de una cantidad bastante importante de incomodidades y que, dado lo patético de la API "estándar" Bluetooth y el SDK S60 lo mejor es tomar uno de los ejemplos (¡verifica primero que te funciona!) e ir "despiezando" según sea necesario, tal es lo que hice yo con el "S60CppExamples\Chat" (si hijo si, el de los 26 archivos...).

En esta misma sección tienes el artículo Bluetooth que hace que el JSR-82 alcance el estatus de deidad suprema cuando lo comparamos con establecer conectividad mediante Bluetooth en el SDK del S60 (que viene a estar en el más bajo de los inframundos).

Sensor: lectura del acelerómetro

Aun cuando la documentación al respecto es también sensiblemente rala, no ha sido difícil crear una sencilla clase que controle el sensor (y eso que fué de lo primero que hice, si, antes que el fastidioso tema del Bluetooth). Con los comentarios en el código supongo te bastará:

Este es el archivo de cabecera acelerometro.h:

/*
 ============================================================================
 Name             : acelerometro.h
 Author           : josejuan
 Version       : 1.0
 Copyright   : Do you like it? more on http://jose-juan.computer-mind.com
 Description : CAcelerometro declaration
 ============================================================================
 */
#ifndef ACELEROMETRO_H
#define ACELEROMETRO_H

#include <e32std.h>
#include <e32base.h>
#include <avkon.hrh>
#include <aknnotewrappers.h>
#include <rrsensorapi.h>

// Una sencilla clase que representa un vector en R3
#include "Vxyz.h"

// Debemos declararla antes para la clase CAcelerometroObserver
class CAcelerometro;

// Define un observador de acelerómetros, deberá implementarla
// cualquier clase que quiera "escuchar" el acelerómetro
class CAcelerometroObserver {
public:
      // Evento que será llamado cuando un acelerómetro aporte datos
      virtual void AcelerometroDatos( CAcelerometro *sensor ) = 0;
};

// La clase acelerómetro
class CAcelerometro : public CBase,
      // debe implementar MRRSensorDataListener para "escuchar" el sensor
      public MRRSensorDataListener {
public:
      static const TInt K_MUESTRAS_P2 = 3; // Debe ser:
                                                // K_MUESTRAS = 2^K_MUESTRAS_P2
      static const TInt K_MUESTRAS = 8; // Número de muestras para hacer promedio

      static const TInt K_SENSOR_ACC = 0x10273024; // id del acelerómetro
      static const float K_S2G = 0.0315691336554f; // de valor de sensor a fuerza G
                                                            // lo he tenido que calcular "a ojo".

      ~CAcelerometro();

      // Los incómodos constructores de Symbian S60
      static CAcelerometro * NewL( CAcelerometroObserver *iObserver );
      static CAcelerometro * NewLC( CAcelerometroObserver *iObserver );

      // Callback para obtener los datos del sensor
    void HandleDataEventL( TRRSensorInfo aSensor, TRRSensorEvent aEvent );

    // Debe llamarse para inicializar y tomar el control del sensor
    void GetSensor( void );

    // Libera el sensor
    void ReleaseSensor( void );

    // Indica si está listo
    TBool Ready( void );

    TInt ax( void ); // devuelve el valor ponderado del eje X.
    TInt ay( void ); // devuelve el valor ponderado del eje Y.
    TInt az( void ); // devuelve el valor ponderado del eje Z.

private:
      TInt iA; // índices del buffer circular
      TInt A[ K_MUESTRAS + 1 ][3]; // muestras para el acelerómetro y sumatorio

      // Acceso al sensor
    CRRSensorApi * iAccSensor;

    // Observador del acelerómetro
    CAcelerometroObserver *iObserver;

      // El constructor "de verdad"
      CAcelerometro( CAcelerometroObserver *iObserver );
};

#endif // ACELEROMETRO_H

Y este es el archivo de código acelerometro.cpp:

/*
 ============================================================================
 Name             : acelerometro.cpp
 Author           : josejuan
 Version       : 1.0
 Copyright   : Do you like it? more on http://jose-juan.computer-mind.com
 Description : CAcelerometro implementation
 ============================================================================
 */
#include "acelerometro.h"

CAcelerometro::CAcelerometro( CAcelerometroObserver *_iObserver ) {

      // Nos quedamos con el que quiere "observarnos"
      iObserver = _iObserver;

      // inicializamos los valores de muestra y sumatorio
      for( TInt i = 0; i <= K_MUESTRAS; i++ )
            for( TInt c = 0; c < 3; c++ )
                  A[i][c] = 0;

      // primer valor del anillo de valores
      iA = 0;
}

CAcelerometro::~CAcelerometro() {

      // Obvio
      ReleaseSensor();

}

TBool CAcelerometro::Ready( void ) {

      // Está listo si tenemos sensor
      return iAccSensor != NULL;

}

void CAcelerometro::GetSensor( void ) {

      // Lista de sensores
      RArray<TRRSensorInfo> l;

      // La solicitamos
      CRRSensorApi::FindSensorsL( l );

      // Si tenemos sensor, lo liberamos
      ReleaseSensor();

      // Para cada sensor...
      for( TInt i = 0, n = l.Count(); i < n; i++ )

            /// ...¿es el nuestro?
            if( l[i].iSensorId == K_SENSOR_ACC ) {

                  // Nos quedamos con el sensor
                  iAccSensor = CRRSensorApi::NewL( l[i] );

                  // Y nos ponemos a nosotros, a la esucha
                  iAccSensor->AddDataListener( this );

                  // No hay mas que uno
                  break;
            }
}

void CAcelerometro::ReleaseSensor( void ) {

      // Si hay sensor...
      if( iAccSensor ) {

            // ...dejamos de ecuchar...
            iAccSensor->RemoveDataListener();

            // ...liberamos el objeto...
            delete iAccSensor;

            // ...y ya no tenemos sensor.
            iAccSensor = NULL;
      }
}

// Devuelve los valores actuales de aceleración:
TInt CAcelerometro::ax( void ) {

      // El último valor contiene la suma de los anteriores,
      //   debemos dividir por el número de muestras
      return A[K_MUESTRAS][0] >> K_MUESTRAS_P2;

}
TInt CAcelerometro::ay( void ) { // idem
      return A[K_MUESTRAS][1] >> K_MUESTRAS_P2;
}
TInt CAcelerometro::az( void ) { // idem
      return A[K_MUESTRAS][2] >> K_MUESTRAS_P2;
}

// Capta un evento de muestra del acelerómetro
void CAcelerometro::HandleDataEventL( TRRSensorInfo s, TRRSensorEvent e ) {

      // ¿Es el nuestro?, no se muy bien porqué lo ponen en los ejemplos
      // yo he visto que sólo es llamado con este valor. Es probable que
      // sea por si se utiliza el mismo "escuchador" para varios sensores:
      if( s.iSensorId == K_SENSOR_ACC ) {

            // a la media le restamos el que sale y añadimos el que entra
            //   (el que entra reemplaza al que sale)
            A[K_MUESTRAS][0] += e.iSensorData1 - A[iA][0];
            A[iA][0] = e.iSensorData1;
            A[K_MUESTRAS][1] += e.iSensorData2 - A[iA][1];
            A[iA][1] = e.iSensorData2;
            A[K_MUESTRAS][2] += e.iSensorData3 - A[iA][2];
            A[iA][2] = e.iSensorData3;

            // la siguiente "celda" en la que dejaremos un valor será
            // el siguiente...
            iA++;
            // ...en módulo K_MUESTRAS...
            iA %= K_MUESTRAS;

            // Si tenemos un escuchante...
            if( iObserver != NULL )
                  // ...se lo decimos.
                  iObserver->AcelerometroDatos( this );
      }

}

// ¡Puaj!
CAcelerometro * CAcelerometro::NewLC( CAcelerometroObserver *iObserver ) {
      CAcelerometro * self = new ( ELeave ) CAcelerometro( iObserver );
      CleanupStack::PushL( self );
      return self;
}

// ¡Puaj!
CAcelerometro * CAcelerometro::NewL( CAcelerometroObserver *iObserver ) {
      CAcelerometro * self = CAcelerometro::NewLC( iObserver );
      CleanupStack::Pop(); // self;
      return self;
}

Bien, como ves, la clase que controla el acelerómetro es bien sencilla, lo único "interesante" es la forma en la que construímos el anillo de valores (yo no lo he visto por ahí) pero es bastante simple y no merece la pena detenerse.

Sensor: enviando los datos

Una vez nos hemos hecho con el intratable código para hacer de cliente Bluetooth, realizar las modificaciones pertinentes para que nos envíe los datos es muy sencilla. Como único punto a comentar es la codificación de los valores de aceleración, yo los paso como una cadena de la forma {ejeX};{ejeY};{ejeZ}{CR}. Sí, se que no es muy elegante y que además de las conversiones se pasarán bastantes más bytes de los necesarios (casi el triple) sin embargo la llamada para enviar los datos es void CBluetoothBt::SendMessageL( TDes& aText ) y por tanto la codificación de TDes (en mi compilación) es Unicode y no quería perder más tiempo con la dichosa clase. La pérdida no es grande y sólo utilizando algún Profiler podríamos realmente estimar la pérdida de rendimiento (probablemente despreciable).

Bueno, en tal caso, para usar la clase del acelerómetro sólo debemos ponernos en nuestra clase como escuchante:

class CBluetoothRemoteSensorContainerView: public CAknView,
		public MLog,

		// Escuchante del acelerómetro
		public CAcelerometroObserver {

		...

Declarar una variable para el acelerómetro (en la misma clase):

	...
	CAcelerometro *iAcelerometro;
	...

En el constructor, lo creamos (pero no lo inicializamos):

	...
	iAcelerometro = CAcelerometro::NewL( this );
	...

En el destructor, deberemos liberarlo limpiamente:

	if( iAcelerometro != NULL ) {
		delete iAcelerometro;
		iAcelerometro = NULL;
	}

Allí donde queramos iniciar la conexión (conectar con el PC y empezar a enviar los datos), iniciaremos la conexión Bluetooth y luego iniciaremos el sensor del acelerómetro:

	...
	// Conectar con el PC:
	iBluetoothBt->ConnectL();

	// Iniciar el sensor:
	iAcelerometro->GetSensor();

	...

Controlar la lectura del sensor y enviarla es sencillo:

void CBluetoothRemoteSensorContainerView::AcelerometroDatos( CAcelerometro *sensor ) {

	// Sólo si se ha terminado de enviar la última petición enviamos otra
	if( iBluetoothBt->State() == EConnected ) {

		// Codificamos datos
		TBuf<200> n;
		n.Num( iAcelerometro->ax() );
		n.Append( _L(";") );
		n.AppendNum( iAcelerometro->ay() );
		n.Append( _L(";") );
		n.AppendNum( iAcelerometro->az() );
		n.Append( _L("\r") );

		// Enviamos
		iBluetoothBt->SendMessageL( n );

	}

}

Y ya está, hemos controlado el acelerómetro y enviado los datos al PC (u otro dispositivo conectado).

PC: servidor de Bluetooth

Ya ves cómo cuando las cosas están medianamente bien hechas es todo mucho más sencillo, utilizando InTheHand, que es un wrapper para las conexiones Bluetooth bajo .NET, que viene a dejarlo como una conexión por Sockets o TCP/IP tradicional, hacer el servidor que recibe los datos desde el sensor se trivializa. He aquí el código de mi clase que con la cantidad de comentarios que tiene apenas tiene 184 línea de código.

/*
 * JJBM, 31/01/2009, BluetoothSerialPortServer.cs
 *
 */
using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using InTheHand.Net.Sockets;
using InTheHand.Net.Bluetooth;
using System.Windows.Forms;

namespace S60SensorBt {

      /// <summary>
      /// Contiene los datos generados por un evento de recepción de datos.
      /// </summary>
      public class BluetoothSerialPortServerDataEventArgs : EventArgs {

            /// <summary>
            /// Máximo de datos que se podrán recibir en un comando.
            /// </summary>
            private const int MAX_BUFF_DATA = 1024;

            /// <summary>
            /// Marca de fín de comando.
            /// </summary>
            private const byte EOF_DATA = 13;

            /// <summary>
            /// Son los datos recibidos. No todo el buffer puede estar ocupado, debe revisarse idatos.
            /// </summary>
            public byte [] datos;

            /// <summary>
            /// Indica la cantidad de datos recibidos.
            /// </summary>
            public int idatos;

            /// <summary>
            /// Devuelve los datos en forma de cadena de texto.
            /// </summary>
            public string Texto {
                  get {
                        if( idatos <= 0 )
                              return string.Empty;
                        return Encoding.ASCII.GetString( datos, 0, idatos );
                  }
            }

            public BluetoothSerialPortServerDataEventArgs() {
                  datos = new byte [ MAX_BUFF_DATA ];
                  idatos = 0;
            }

            /// <summary>
            /// Añade un byte a la entrada de datos e indica si ha llegado al fin de línea.
            /// </summary>
            /// <param name="b">byte a añadir.</param>
            /// <returns><c>True</c> si se ha llegado al fin de línea.</returns>
            public bool Put( byte b ) {
                  if( b == EOF_DATA )
                        return true;
                  datos [ idatos++ ] = b;
                  if( idatos >= MAX_BUFF_DATA )
                        return true;
                  return false;
            }
      }

      /// <summary>
      /// Se produce cuando se reciben una línea de datos de la conexión cliente. Se espera a recibir una marca de fin de línea. Si los datos superan la longitud máxima admitida, también se produce el evento.
      /// </summary>
      /// <param name="sender">Objeto que produce el evento.</param>
      /// <param name="e">Datos de la recepción de datos.</param>
      public delegate void BluetoothSerialPortServerDataEventHandler( object sender, BluetoothSerialPortServerDataEventArgs e );

      /// <summary>
      /// Establece un servicio al que se puede conectar mediante el servicio Bluetooth "SerialPort".
      /// </summary>
      class BluetoothSerialPortServer {

            /// <summary>
            /// Es el servidor que se queda a la escucha.
            /// </summary>
            private BluetoothListener bl = null;

            /// <summary>
            /// Se produce cuando se recibe una línea de datos del cliente.
            /// </summary>
            public event BluetoothSerialPortServerDataEventHandler OnDataEvent;
            private void DataEvent( BluetoothSerialPortServerDataEventArgs e ) {
                  BluetoothSerialPortServerDataEventHandler d = OnDataEvent;
                  if( d != null ) {
                        // Esta triquiñuela es lo más raro del código, debemos invocar al método
                        // enviando un mensaje en lugar de una llamada directa, esto es porque
                        // se está ejecutando en otro thread diferente al del Windows Form
                        if( d.Target is Control ) {
                              Control t = d.Target as Control;
                              t.Invoke( d, new object [] { this, e } );
                        } else {
                              d( this, e );
                        }
                  }
            }

            /// <summary>
            /// Stream de lectura/escritura privado de la única conexión disponible.
            /// </summary>
            private NetworkStream s = null;

            /// <summary>
            /// Thread del proceso de lectura de datos.
            /// </summary>
            private Thread tR = null;

            /// <summary>
            /// Única conexión cliente admitida.
            /// </summary>
            private BluetoothClient cli = null;

            /// <summary>
            /// Indica si existe una conexión de cliente activa.
            /// </summary>
            public bool Conectado {
                  get {
                        return cli != null && cli.Connected;
                  }
            }

            /// <summary>
            /// Proceso que se ejecuta en un Thread independiente.
            /// </summary>
            public void SerialPortReader() {
                  try {
                        BluetoothSerialPortServerDataEventArgs e = new BluetoothSerialPortServerDataEventArgs();
                        for( ; ; ) {
                              int b = s.ReadByte();
                              if( b < 0 )
                                    Thread.Sleep( 200 ); // ¿fin de secuencia?
                              else
                                    if( e.Put( (byte) b ) ) {
                                          DataEvent( e );
                                          e = new BluetoothSerialPortServerDataEventArgs();
                                    }
                        }
                  } catch( ThreadAbortException ) {
                  }
            }

            /// <summary>
            /// Inicia un proceso de escucha para aceptar una única conexión cliente. Es síncrono.
            /// </summary>
            public void Start() {
                  bl = new BluetoothListener( BluetoothService.SerialPort );
                  bl.Start( 1 );
                  cli = bl.AcceptBluetoothClient();
                  s = cli.GetStream();
                  tR = new Thread( new ThreadStart( SerialPortReader ) );
                  tR.Start();
            }

            /// <summary>
            /// Detiene la conexión establecida.
            /// </summary>
            public void Stop() {
                  tR.Abort();
                  cli.Close();
                  bl.Stop();
            }

            /// <summary>
            /// Envía un mensaje a la conexión establecida.
            /// </summary>
            /// <param name="msg">Mensaje a enviar.</param>
            public void Send( string msg ) {
                  byte [] buf = Encoding.ASCII.GetBytes( msg );
                  s.Write( buf, 0, buf.Length );
            }

      }

}

PC: uso de los datos del sensor

Utilizar los datos del sensor ya no es tan interesante (cuando sólo ponemos un simple ejemplo). Por eso no entro en detalles. La aplicación permite mover una bola en la pantalla mediante el móvil utilizando tres formas de control:

Receptor del sensor en ejecución
Receptor del sensor en ejecución

Abajo a la izquierda vemos un indicador para cada uno de los tres ejes. Las barras de scroll verticales permiten controlar el índice de rozamiento y el factor de escala.

PC: uso de los datos del sensor

No hace falta que instales nada en el PC, sólo necesitas los archivos S60SensorBt.exe y InTheHand.Net.Personal.dll. Para el móvil sólo tienes que instalar el BluetoothRemoteSensor.sisx. Estos archivos los puedes bajar de aquí INSTALABLES.

Cómo ejecutar el ejemplo

Código fuente

Si quieres el código fuente, sólo tienes que pedírmelo a la dirección de correo.



Opinado el 23/06/09 00:14, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    me gustaria que me enviara el codigo fuente para esperimentar gracias
Opinado el 19/03/10 20:18, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    cindypamelazuritaron@yahoo.com muy bueno el documeto...gracias
Opinado el 28/04/10 20:33, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    Muy buen documento, felicitiaciones me gustaria que me enviaras el c?o fuente para poder absolver todas mis dudas: jaylli.arteaga@gmail.com
Opinado el 16/05/10 06:05, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    Hola muy bueno el documento yo llevo varias semanas buscando como poder realizar la comunicacion serie mi idea es que el bt del celu se conecte al del pc y envie info por el puerto serial, tu dices que lo lograste me podrias guiar un poco, te lo agradeceria monton te dejo mi mail flaca_7688@hotmail.com
Opinado el 18/05/10 06:21, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
     Muy buen documento, felicitiaciones me gustaria que me enviaras el codigo fuente para poder absolver todas mis dudas: aldolopezr@hotmail.com
Opinado el 09/09/10 21:15, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    Buenísimo ejemplo me harías un favor si me enviases el código fuente tengo que realizar una aplicación con la librería InTheHand.Net.Personal.dll y tu documento me viene de perlas, noto.i@hotmail.com. Gracicas
Opinado el 28/10/10 22:54, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    Excelente aportacion, serias tan amable de mandarme el codigo a mi email: m_galfano@hotmail.com
Opinado el 11/11/10 06:17, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    Excelente post amigo!! mmm! aunque no me queda muy claro como funciona jejejeje!! necesitar?ver el c?o completo! mi correo es gustavocobos18@gmail.com gracias!! y crees que con poco cambio se podr?adaptar a windows mobile 6.1!?
Opinado el 17/11/10 22:08, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    Hola! yo estoy trabajando en tratar de conectar mi telefono movil con un sensor (interfaz bluetooth) para leer datos de temeperatura en mi celular por medio de BT, me gustaria si podrias enviarme el codigo, aunque lo estoy trabajando en JavaME creo k me serviria para resolver mis dudas de como realizar la conexion con el sensor
Opinado el 17/03/11 05:57, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    hola, muy bueno, felicidades!! podrias enviarme tu codigo fuente para guiarme!! porfa. geniusmexican@hotmail.com
Opinado el 16/07/11 16:22, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    Muy interesante la información, me podrías enviar el código fuente por favor, Muchas gracias, maythe_z@hotmail.com
Opinado el 01/08/11 10:06, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    
Opinado el 27/08/11 17:11, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    balrog-demon_of_might@hotmail.com Gracias :-)
Opinado el 28/09/11 19:49, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    cristian.sandoval.villegas@gmail.com gracias ;)
Opinado el 29/09/11 05:26, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    Muy buen documento, aporta bastante información. P
Opinado el 30/09/11 00:19, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    muy bueno tu aporte!!!
Opinado el 10/01/12 21:35, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    rmontejo19@gmail.com
Opinado el 16/01/12 17:19, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    Excelente Aporte
Opinado el 21/03/12 22:45, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    me pasas el codigo porfa ulises26@msn.com gracias
Opinado el 30/04/12 19:39, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    podras enviarme el codig omarscscruz@hotmail.com
Opinado el 11/05/12 07:32, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    enviame el codigo , gracias nteam2009@gmail.com
Opinado el 12/05/12 23:45, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    favor ayudar con codigo leonardormt1986@hotmail.es
Opinado el 29/05/13 07:17, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    aute@hotmail.com buen aporte
Opinado el 25/06/13 19:56, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    cheguevara19@gmail.com Excelente aporte. ayudame
Opinado el 14/01/14 04:40, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    markosimba@hotmail.com me puedes ayudar con el cod
Opinado el 12/10/15 14:45, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    Enviame el codigo , gracias nayeli.14@hotmail.es
Opinado el 07/03/19 17:24, valoración ValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoraciónValoración
    gaby-_-ortiz@hotmail.com podria enviarme el codigo
¿Te ha gustado? ¡aporta tu opinión!
Valoración:
 0    1    2    3    4    5    6    7    8    9    10

Comentario:
NOTA: si es una petición... ¡pon el e-mail al que responderte o no sabré a dónde escribir!

Código de verificación captcha