Detección de Movimiento (acelerómetro)

Este artículo es una traducción del artículo original encontrado en: http://create.msdn.com/en-US/education/quickstarts/Detect_Movement_%28Accelerometer%29

 

 

Silverlight para Windows Phone incluye un espacio de nombres que proporciona control y monitoreo en tiempo real del acelerómetro del teléfono.  El acelerómetro mide la intensidad y la dirección de la fuerza de aceleración que experimenta el teléfono.  Para facilitar la lectura de intensidad, un valor decimal es devuelto cuyo valor oscilará entre -1.0 y 1.0.  Esta lectura de intensidad es proporcionada por los ejes X, Y y Z del teléfono, y monitorea la fuerza de aceleración que experimenta a lo ancho, a lo largo, y en la profundidad, respectivamente.  Para determinar la dirección de la fuerza de aceleración, estos valores deben ser comparados entre sí.  Aunque el tema de dirección no está cubierto por este tutorial, cubriremos cómo registrar las lecturas del acelerómetro para que estas comparaciones puedan hacerse con mayor facilidad cuando estés utilizando el acelerómetro en tu aplicación o juego.

Este tutorial es una aplicación de Silverlight para Windows Phone que recibe la entrada del dispositivo de Windows Phone 7, y grafica las lecturas visualmente en un canvas.  Este tutorial contiene las siguientes secciones.

·         Creando una interfaz de usuario para la lectura del acelerómetro.

·         Obteniendo las entradas del acelerómetro en tiempo real.

·         Trazando los datos del acelerómetro como un gráfico.

Descarga el código fuente completo de este tutorial, hospedado en la galería de código de  Silverlight para Windows Phone.

Video Tutorial

Para ver un ejemplo de este tutorial ejecutándose en un teléfono, y para construir este tutorial siguiendo un vídeo en lugar de leer este artículo, puedes ver el siguiente video.  La página de este vídeo en Channel 9 tiene varias opciones de descarga si deseas una versión de alta definición, o si deseas descargar una versión que pueda reproducirse en otros dispositivos.

Creando una interfaz de usuario para la lectura del acelerómetro


Con el fin de mostrar las lecturas actuales del acelerómetro de un modo significativo, es muy útil mostrar tanto el valor actual (en forma numérica) y los valores recientes (en forma de gráfico).  Para ello, crea una interfaz de usuario que tenga los siguientes elementos:

·         3 TextBlock con nombres significativos, para la lectura numérica de los ejes X, Y y Z.

·         Un Canvas con un nombre significativo, sobre el cual vamos a colocar pequeños rectángulos para trazar la gráfica.

·         Un Button con un nombre significativo, que se usará para iniciar y detener el acelerómetro.

·         TextBlocks según sea necesario para facilitar el etiquetado de la información de la lectura.

·         3 Rentangles que son rellenos de diferentes colores sólidos, proporcionando una leyenda visual representando los ejes de las líneas des gráfico.

Una distribución propuesta de estos elementos se indica a continuación:

clip_image002

XAML

<Grid x:Name="LayoutRoot" Background="Transparent">

<Grid.RowDefinitions>

<RowDefinition Height="Auto"/>

<RowDefinition Height="*"/>

</Grid.RowDefinitions>

<!–TitlePanel contiene el nombre de la aplicación y el título de la página–>

<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">

<TextBlock x:Name="ApplicationTitle" Text="JoMul’s Demo"

    Style="{StaticResource PhoneTextNormalStyle}"/>

<TextBlock x:Name="PageTitle" Text="Accelerometer" Margin="9,-7,0,0"

    Style="{StaticResource PhoneTextTitle1Style}"/>

</StackPanel>

<!–ContentPanel – coloque el contenido adicional aqui–>

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">

<TextBlock Height="30" HorizontalAlignment="Left" Margin="46,20,0,0"

 Name="textBlock1" Text="X:" VerticalAlignment="Top" />

<TextBlock Height="30" HorizontalAlignment="Left" Margin="46,56,0,0"

 Name="textBlock2" Text="Y:" VerticalAlignment="Top" />

<TextBlock Height="30" HorizontalAlignment="Left" Margin="47,92,0,0"

 Name="textBlock3" Text="Z:" VerticalAlignment="Top" />

<Rectangle Height="20" HorizontalAlignment="Left" Margin="18,25,0,0"

    Name="rectangle1" Fill="Red" StrokeThickness="1"

 VerticalAlignment="Top" Width="16" />

<Rectangle Fill="Blue" Height="20" HorizontalAlignment="Left"

Margin="18,60,0,0" Name="rectangle2" StrokeThickness="1"

VerticalAlignment="Top" Width="16" />

<Rectangle Fill="Green" Height="20" HorizontalAlignment="Left"

Margin="18,95,0,0" Name="rectangle3" StrokeThickness="1"

VerticalAlignment="Top" Width="16" />

<TextBlock Height="30" HorizontalAlignment="Left" Margin="82,20,0,0"

    Name="xreadout" Text="1.0" VerticalAlignment="Top" />

<TextBlock Height="30" HorizontalAlignment="Left" Margin="82,56,0,0"

     Name="yreadout" Text="1.0" VerticalAlignment="Top" />

<TextBlock Height="30" HorizontalAlignment="Left" Margin="82,92,0,0"

     Name="zreadout" Text="1.0" VerticalAlignment="Top" />

<Button Content="PAUSE" Height="97" HorizontalAlignment="Left"

Margin="143,25,0,0" Name="PlayOrPause" VerticalAlignment="Top"

Width="285" Click="PlayOrPause_Click" />

<Canvas Height="400" HorizontalAlignment="Left" Margin="18,140,0,0"

   Name="Log" VerticalAlignment="Top" Width="400"

Background="White" />

<TextBlock Height="30" HorizontalAlignment="Left" Margin="419,326,0,0"

      Name="textBlock4" Text="0" VerticalAlignment="Top" Width="30" />

<TextBlock Height="30" HorizontalAlignment="Left" Margin="419,128,0,0"

      Name="textBlock5" Text="1.0" VerticalAlignment="Top" />

<TextBlock Height="30" HorizontalAlignment="Left" Margin="419,526,0,0"

      Name="textBlock6" Text="-1.0" VerticalAlignment="Top" />

</Grid>

</Grid>

 

Obteniendo las entradas del acelerómetro en tiempo real
Silverlight para Windows Phone utiliza el ensamblado Microsoft.Devices.Sensors para manejador la entrada del acelerómetro, por lo que debe referenciarse en tu proyecto antes de utilizar cualquiera de sus tipos, eventos o métodos. 

Para agregar Microsoft.Devices.Sensors en tu aplicación:

1.       En el Solution Explorer, haz clic derecho en el nodo References  de tu proyecto, a continuación, selecciona Add Reference.

2.       Selecciona Microsoft.Devices.Sensors de la lista, y a continuación haz clic en OK.

3.       Agrega el siguiente código al principio de cualquier archivo de código fuente que utilice las clases y métodos del acelerómetro.

C#

using Microsoft.Devices.Sensors;

 

El siguiente paso consiste en agregar un miembro de datos de tipo Accelerometer a la clase cuyos métodos utilizarán los datos de entrada.

 

C#

 

public partial class MainPage : PhoneApplicationPage

{

// Constructor

Accelerometer accelerometer = new Accelerometer();

 

Este miembro de datos debe ser a nivel de clase porque al menos dos métodos deben estar involucrados en la lectura de entrada del acelerómetro:

 

·         Un manejador de evento que se llama cuando el acelerómetro dispara el evento ReadingChanged.

·         Un método que contiene una declaración para adjuntar el manejador al evento ReadingChanged.

 

Comencemos con el primero.  Todos los manejadores de eventos aceptan por lo menos dos de los siguientes argumentos: un objeto genérico que hace referencia al objeto que disparó el evento (en este caso, el miembro de datos Accelerometer), y un "event args" personalizado cuyas propiedades puedan ser interpretadas para acceder a los datos generados por el evento. Nuestro manejador para el evento ReadingChanged del acelerómetro comenzará de esta manera:

 

C#

void myHandler(object sender, AccelerometerReadingEventArgs e)

{

// TODO: Código aquí

}

 

Ahora tenemos que iniciar el acelerómetro y conectar el evento ReadingChanged a myHandler, y para los fines de este tutorial, hacerlo en la función principal está bien.

 

C#

public MainPage()

{

InitializeComponent();

accelerometer.Start();

accelerometer.ReadingChanged += new

EventHandler<AccelerometerReadingEventArgs>(myHandler);

}

 

Es aquí donde tenemos un pequeño contratiempo.  Tenemos que actualizar la interfaz de usuario con los datos de nuestro acelerómetro, sin embargo, el evento ReadingChanged se dispara en el hilo del acelerómetro, y así myHandler se ejecutará en ese hilo también.  Sin embargo, queremos que myHandler actualice la interfaz de usuario con las lecturas del acelerómetro y la interfaz de usuario se ejecuta en su propio hilo, por lo que es inaccesible.  Afortunadamente, la solución es una sola línea que utiliza el objeto Dispatcher para invocar una función en el hilo de la interfaz de usuario que puede hacer la actualización para nosotros, pasando el objeto AccelerometerReadingEventArgs que contiene las lecturas que queremos mostrar.  Vamos a llamar a esta función, updateMyScreen.  myHandler ahora se ve así:

 

C#

void myHandler(object sender, AccelerometerReadingEventArgs e)

{

Deployment.Current.Dispatcher.BeginInvoke(() => updateMyScreen(e));

}

 

Ahora estás listo para llevar las lecturas del acelerómetro a la interfaz de usuario.  El objeto AccelerometerReadingEventArgs nos permite hacer fácilmente esto a través de sus propiedades que contienen las lecturas actuales de los ejes X, Y y Z.  Terminamos esta sección mediante la implementación de una pantalla muy simple de los datos del acelerómetro: una lectura de texto en la interfaz de usuario.  updateMyScreen, que se ejecuta en el hilo de la  interfaz de usuario y que se llama cada vez que se dispara el evento ReadingChanged, sólo necesita actualizar la propiedad Text de los elementos del TextBlock.

C#

void updateMyScreen(AccelerometerReadingEventArgs e)

{

// Actualiza los Textblocks

xreadout.Text = e.X.ToString("0.00");

yreadout.Text = e.Y.ToString("0.00");

zreadout.Text = e.Z.ToString("0.00");

}

 

 

El objeto AccelerometerReadingEventArgs expresa la fuerza de aceleración que se experimenta en los ejes X, Y y Z como un valor Double (punto flotante numérico).  Estos valores se convierten en una cadena con precisión decimal de dos puntos mediante el método ToString y pasando el argumento de formato como "0.00".

 

En este punto, podrías construir la aplicación y ejecutarla para ver en la pantalla los datos del acelerómetro.  Sin duda los fundamentos han sido mostrados.  Sin embargo, este tutorial incluye código que traza los datos en una gráfico usando los elementos Rectangle y Canvas que has puesto en la interfaz de usuario anteriormente, lo que te ayudará a ver realmente lo que el acelerómetro está haciendo cuando intentes utilizar los datos en tus aplicaciones.  Es altamente recomendable que continúes, sin embargo, si sólo quieres tener acceso a los datos en bruto, puedes detenerte aquí.

 

Trazado de los datos del acelerómetro como un gráfico
El elemento Rectangle de la interfaz de usuario está listo para ser usado como un pixel, lo que hace que esta tarea sea más sencilla de lo que se podría esperar.  Un gráfico en este caso simplemente será una serie de rectángulos colocados cuidadosamente que se le asignan colores mediante el uso de la propiedad Fill.  Vamos a "pintar" estos rectángulos en el gran Canvas blanco que colocamos en la interfaz de usuario en la primera sección.  Las propiedades Top y Left del rectángulo determinarán su posición en el Canvas, e incluso podemos hacerlas relativas al Canvas referenciando al Canvas en el método SetValue del rectángulo.

 

En el gráfico, trazamos tres líneas de colores diferentes, cada una representando un eje del acelerómetro.  Estas líneas se representan como elementos adyacentes visuales del Rectangle. Para cada uno de estos rectángulos, se utilizan dos piezas de datos para trazar la posición del rectángulo: la cantidad de tiempo que ha pasado, que vamos a rastrear con un iterador genérico que incrementaremos en 1, y por supuesto la lectura del  acelerómetro que obtenemos con el objeto AccelerometerReadingEventArgs.

 

El evento ReadingChanged maneja de la progresión de la gráfica, tal como lo llevó a la representación textual de estos datos en la sección anterior.  El evento ReadingChanged se dispara solo si mientras el estado del acelerómetro sea "started" (iniciado), después de llamar al la función Start del Accelerometer.  Esto sucede en sincronización con el ciclo nativo de actualización de la aplicación (30 cuadros por segundo, por defecto), y es así muy fiable, como el gráfico demostrará visualmente.

 

Como es de esperar, el trazado del gráfico necesita realizarse en el hilo de la interfaz de usuario, por lo que queremos extender updateMyScreen.  Antes de hacer eso, vamos a empezar con la parte fácil: establecer el iterador.  Obviamente este iterador tendrá que estar fuera del ámbito de updateMyScreen, por lo que hacerlo un miembro de datos para la clase, junto con el objeto Accelerometer, será suficiente:

C#

public partial class MainPage : PhoneApplicationPage

{

// Constructor

Accelerometer accelerometer = new Accelerometer();

double iterator = 0;

 

Hacemos el iterador de tipo Double para evitar el desperdicio de los ciclos del CPU del teléfono y convertirlo en una posición en el Canvas más tarde.  Se seguirá iterando en 1, que es todo lo se requiere.  A medida que se incrementa en 1, la posición horizontal que asignamos al elemento Rectangle en el gráfico se desplaza a la derecha por 1 pixel, permitiendo la colocación de los pixeles en la pantalla para trazarlos de izquierda a derecha en la pantalla con el paso del tiempo, haciendo una línea.

 

Nota                       
De aquí en adelante, es importante tener en cuenta que se asume un Canvas de 400×400.

 

Ahora extendemos updateMyScreen para hacer el dibujo. Esto se hace en algunas fases:

1.  Convierte la lectura del acelerómetro (que va desde -1.0 a 1.0 y tiene un punto medio de 0) en una posición vertical en el Canvas para el Rectangle en uso (que va desde el bajo de 400 a lo alto de 0 y tiene un punto medio de 200).  Con lo que respecta a iterator, el valor resultante se mantendrá como un doble para que el objeto Canvas pueda utilizarlo.

2.       Los elementos Rectangle que se utilizarán como los pixeles que constituyen las líneas del gráfico necesitan ser creados, con un tamaño de un pixel y asignado colores únicos, antes de que sus posiciones sean trazadas.  Tenemos tres rectángulos nuevos por cada actualización de ReadingChanged, cada uno representando uno de los tres ejes del acelerómetro.

3.       El valor del acelerómetro convertido en el paso 1 se utiliza para la posición vertical del Rectangle en el Canvas, y el iterator se utiliza para la posición horizontal del Rectangle en el Canvas.  Estos son pasados usando el método SetValue del rectángulo.

4.       Los tres nuevos rectángulos son pintados sobre el Canvas haciendo los rectángulos "hijos" en el Canvas.  Esto se hace con la función Add de la propiedad Children del Canvas.

5.       Dependiendo de si estás fuera del espacio horizontal en el Canvas (es decir, iterator ha alcanzado el valor de 399), ya sea que borres el Canvas y comiences de nuevo desde el lado izquierdo (limpiando la propiedad Children del Canvas y estableciendo iterator de nuevo a cero), o simplemente incrementas el iterador en 1.

El updateMyScreen resultante tiene este aspecto:

C#

void updateMyScreen(AccelerometerReadingEventArgs e)

{

// Actualiza los textblocks

xreadout.Text = e.X.ToString("0.00");

yreadout.Text = e.Y.ToString("0.00");

zreadout.Text = e.Z.ToString("0.00");

//Dibuja en el canvas:

double currentXOnGraph = Math.Abs((e.X * 200) – 200);

double currentYOnGraph = Math.Abs((e.Y * 200) – 200);

double currentZOnGraph = Math.Abs((e.Z * 200) – 200);

Rectangle xPoint = new Rectangle();

Rectangle yPoint = new Rectangle();

Rectangle zPoint = new Rectangle();

xPoint.Fill = new SolidColorBrush(Colors.Red);

yPoint.Fill = new SolidColorBrush(Colors.Blue);

zPoint.Fill = new SolidColorBrush(Colors.Green);

// Establece el tamaño de los pixeles

xPoint.Width = 1;

xPoint.Height = 2;

yPoint.Width = 1;

yPoint.Height = 2;

zPoint.Width = 1;

zPoint.Height = 2;

/ / Estos pixeles se "pega n" en el Canvas mediante el ajuste de su posición
/ / según los valores-gráfico CurrentX / Y / Z. Para fijar su posición
/ / relativo al Canvas, pasar la propiedades Canvas de a través de los pixeles
/ / del método SetValue. Utilice un iterador genérico para determinar la distancia
/ / de los píxel que debe estar desde el lado izquierdo del Canvas.

xPoint.SetValue(Canvas.LeftProperty, iterator);

xPoint.SetValue(Canvas.TopProperty, currentXOnGraph);

yPoint.SetValue(Canvas.LeftProperty, iterator);

yPoint.SetValue(Canvas.TopProperty, currentYOnGraph);

zPoint.SetValue(Canvas.LeftProperty, iterator);

zPoint.SetValue(Canvas.TopProperty, currentZOnGraph);

// Finalmente, asocial cada pixel con el Canvas

Log.Children.Add(xPoint);

Log.Children.Add(yPoint);

Log.Children.Add(zPoint);

if (iterator == 399)

{

Log.Children.Clear();

iterator = 0;

} else {

iterator++;

}

}

 

Por último, ya que el gráfico progresará sin fin, sin darte la oportunidad de observar los datos, debemos conectar un manejador de eventos para ese botón Play/Pause.  En Visual Studio, puedes simplemente hacer doble clic en el botón del diseñador de la interfaz de usuario y el evento OnClick será conectado a una función con nombre apropiado.  También puedes escribirlo tú mismo.  De cualquier manera, ya que el evento ReadingChanged está haciendo la actualización del gráfico por nosotros, todo lo que necesitamos hacer para implementar la funcionalidad de Play/Pause es iniciar y detener el acelerómetro.  La función resultante tiene este aspecto:

C#

private void PlayOrPause_Click(object sender, RoutedEventArgs e)

{

if (PlayOrPause.Content.ToString() == "PAUSE")

{

PlayOrPause.Content = "PLAY";

accelerometer.Stop();

} else if (PlayOrPause.Content.ToString() == "PLAY")

{

accelerometer.Start();

PlayOrPause.Content = "PAUSE";

}

}

 

Ejecutando este código se muestra la actividad de un gráfico de 3 líneas trazando la actividad del acelerómetro y una lectura de texto en tiempo real.  Naturalmente, para ver realmente cómo este código responde a la entrada del acelerómetro, necesitas desplegarlo en un dispositivo Windows Phone.  Sin embargo, hay maneras de simular la entrada del acelerómetro si esto no es posible.