Georg fährt extra nach Berlin um Steve Ballmer zu treffen

31 Tage Mango | Tag #6: Motion

Dieser Artikel ist Tag #6 der Serie 31 Tage Mango von Jeff Blankenburg.

Der Originalartikel befindet sich hier: Day #6: Motion.

Heute erfahren wir, wie wir die Informationen der vergangenen Tage mit Hilfe der Motion Klasse wesentlich einfacher handhaben können. Die Motion Klasse vereint die Daten des Accelerometers, des Kompasses und des Gyroskops hinter einer einheitlichen Schnittstelle.

Roll-, Nick- und Gier-Winkel

NICKEN GIEREN ROLLEN

Wie Sie in den animierten Bildern oben sehen können, sind der Roll-, der Nick- und der Gier-Winkel wichtige Daten in der Luftfahrt. Sie lassen sich aber auch übertragen auf unsere Windows Phone Geräte. Mit dem Accelerometer und dem Gyroskop waren wir in der Lage, Daten entlang der drei Achsen X, Y und Z im Raum zu erhalten. Rollen, Nicken und Gierung beschreiben die Rotation um die drei Achsen im Raum. Diese Winkel werden durch etwas Mathematik unter der Haube der Motion Klasse berechnet (mehr Informationen über die Roll-, Nick- und Gier-Winkel können Sie auf Wikipedia nachlesen).

Die Motion Klasse benutzen

In den vorangegangenen Beispielen zum Gyroskop und zum Kompass mussten wir erst überprüfen, ob die Hardware die Sensoren überhaupt unterstützt bevor wir Daten von ihnen empfangen konnten. Die Prüfung der einzelnen Sensoren ist bei der Motion Klasse nicht notwendig. Allerdings müssen wir sicherstellen, dass Motion generell unterstützt wird.

Bevor wir in die Einzelheiten des Codes für die Motion Klasse einsteigen, entwerfen wir eine Benutzerschnittstelle. In diesem Beispiel werden wir einen XAML Stern auf dem Telefon anzeigen. Die Benutzeroberfläche sieht dann in etwa so aus:

Basierend auf dem Gierungswinkel, den uns die Motion Klasse liefert, sind wir in der Lage, die Orientierung des Sterns zu verändern. Wenn der Benutzer das Telefon dreht, wird der Stern aus Sicht des Benutzers unbeweglich erscheinen. Zur Illustration des Effekts ist hier ein kleines Video:

Hier ist das XAML um die Benutzeroberfläche zu realisieren:

<phone:PhoneApplicationPage
   x:Class="Day6_Motion.MainPage"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
   xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
   xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:es="clr-namespace:Microsoft.Expression.Shapes;assembly=Microsoft.Expression.Drawing"
   mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
   FontFamily="{StaticResource PhoneFontFamilyNormal}"
   FontSize="{StaticResource PhoneFontSizeNormal}"
   Foreground="{StaticResource PhoneForegroundBrush}"
   SupportedOrientations="Portrait" Orientation="Portrait"
   shell:SystemTray.IsVisible="True">

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="31 DAYS OF MANGO" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="motion" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <es:RegularPolygon x:Name="Star" InnerRadius="0.47211" Margin="100,175,100,175" PointCount="5" Stretch="Fill" Stroke="White" UseLayoutRounding="False" StrokeThickness="6">
                <es:RegularPolygon.Fill>
                    <SolidColorBrush Color="{StaticResource PhoneAccentColor}"/>
                </es:RegularPolygon.Fill>
                <es:RegularPolygon.RenderTransform>
                    <RotateTransform CenterX="100" CenterY="128"></RotateTransform>
                </es:RegularPolygon.RenderTransform>
            </es:RegularPolygon>

            <TextBlock x:Name="yawValue" Text="YAW = 34.567" FontSize="40" Width="400" Height="100" TextAlignment="Center" Margin="28,503,28,4" />
        </Grid>
    </Grid>
</phone:PhoneApplicationPage> 

Wie Sie sehen, erstellen wir den Stern, indem wir ein neues Assembly von Expression Blend verwenden. Wir müssen Blend allerdings nicht benutzen, um die Controls aus dem Assembly zu verwenden. Sie müssen eine Referenz zum Microsoft.Expression.Drawing Assembly hinzufügen und den entsprechenden XML Namespace in der XAML Seite bekannt machen. Wenn Sie damit noch nicht herumexperimentiert haben, können Sie ja mal den Wert der Eigenschaft PointCount des RegularPolygon verändern. Sie können Sterne mit beliebig vielen Punkten erstellen (wobei Sie etwa ab 60 keine großen Unterschiede mehr feststellen werden).

Sie werden zwei weitere Änderungen am Stern feststellen. Die erste ist die Fill Farbe. Im Beispiel habe ich einen SolidColorBrush definiert, der die vom Benutzer eingestellte Akzentfarbe verwendet. Die Akzentfarben und Farbeinstellungen von Windows Phone wurden in Day #5 of the 31 Days of Windows Phone behandelt.

Um den Stern zu bewegen, nutzen wir ähnliche Konzepte wie bei der Verwendung der einzelnen Sensoren. Zunächst überprüfen wir die Verfügbarkeit von Motion:

using System;
using System.Windows.Media;
using Microsoft.Phone.Controls;
using Microsoft.Devices.Sensors;
using Microsoft.Xna.Framework;

namespace Day6_Motion
{
   public partial class MainPage : PhoneApplicationPage
   {
      public MainPage()
      {
         InitializeComponent();

         if (Motion.IsSupported)
         {
            //DO SOMETHING
         }
      }
   }
} 

Dort wo der „DO SOMETHING“ Kommentar steht, initialisieren wir unser Motion Objekt und erstellen einen Event Handler, der dessen Daten empfangen wird.

using System;
using System.Windows.Media;
using Microsoft.Phone.Controls;
using Microsoft.Devices.Sensors;
using Microsoft.Xna.Framework;

namespace Day6_Motion
{
   public partial class MainPage : PhoneApplicationPage
   {
      Motion motion;
        
      public MainPage()
      {
         InitializeComponent();

         if (Motion.IsSupported)
         {
            motion = new Motion();
            motion.TimeBetweenUpdates = TimeSpan.FromMilliseconds(20);
            motion.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<MotionReading>>(motion_CurrentValueChanged);
            motion.Start();
         }
      }

      void motion_CurrentValueChanged(object sender, SensorReadingEventArgs<MotionReading> e)
      {
         //MAKE THIS THREAD SAFE.
      }
   }
} 

Zu guter Letzt müssen wir noch etwas Threadsicherheit üben. Da der CurrentValueChanged Handler nicht auf dem UI Thread aufgerufen wird, müssen wir erst wieder zurück auf den UI Thread springen, bevor wir die Benutzeroberfläche aktualisieren können. Das Aufrufen einer Methode auf dem UI Thread machen wir mit der Methode Dispatcher.BeginInvoke(). im folgenden Beispiel ist der entsprechende Aufruf hinzugefügt. Außerdem haben wir in der UpdateUI Methode bereits die Drehung des Sterns abhängig von den Sensordaten eingebaut (Anm. leitning: dieser Absatz lautet im Orginalartikel anders. Ich habe hier geschrieben, warum meiner Meinung nach Dispatcher.BeginInvoke aufgerufen wird und lasse mich gerne eines Besseren belehren).

using System;
using System.Windows.Media;
using Microsoft.Phone.Controls;
using Microsoft.Devices.Sensors;
using Microsoft.Xna.Framework;

namespace Day6_Motion
{
   public partial class MainPage : PhoneApplicationPage
   {
      Motion motion;
        
      public MainPage()
      {
         InitializeComponent();

         if (Motion.IsSupported)
         {
            motion = new Motion();
            motion.TimeBetweenUpdates = TimeSpan.FromMilliseconds(20);
            motion.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<MotionReading>>(motion_CurrentValueChanged);
            motion.Start();
         }
      }

      void motion_CurrentValueChanged(object sender, SensorReadingEventArgs<MotionReading> e)
      {
         Dispatcher.BeginInvoke(() => UpdateUI(e.SensorReading));
      }

      private void UpdateUI(MotionReading e)
      {
         ((RotateTransform)Star.RenderTransform).Angle = MathHelper.ToDegrees(e.Attitude.Yaw);
         yawValue.Text = "YAW = " + e.Attitude.Yaw.ToString();
      }
   }
}

Wie Sie sehen, verwenden wir den Gierungswinkel, um den RotateTransform Winkel unseres Sterns zu verändern. Wenn Sie ein Windows Phone Gerät zur Hand haben, probieren Sie die Beispielanwendung doch mal aus. Das Verhalten wird sich höchstwahrscheinlich stark unterscheiden zwischen Geräten, die ein Gyroskop verbaut haben und solchen, die keins haben. Wie ich gestern erwähnt habe, liefert das Gyroskop wesentlich „glattere“ und genauere Daten. Der Stern wird sich daher wesentlich weicher bewegen wenn ein Gyroskop vorhanden ist.

Weitere interessante Daten…

Im obigen Beispiel habe ich eigentlich nur den Gierungswinkel der Motion Klasse benutzt. Die Motion Klasse liefert natürlich eine Reihe weiterer nützlicher Daten. In der Windows Phone Solution, die Sie unten herunterladen können, habe ich Variablen für alle anderen Daten der Motion Klasse eingefügt. Das sieht dann so aus:

float pitch = e.Attitude.Pitch;
float yaw = e.Attitude.Yaw;
float roll = e.Attitude.Roll;

float accelerometerX = e.DeviceAcceleration.X;
float accelerometerY = e.DeviceAcceleration.Y;
float accelerometerZ = e.DeviceAcceleration.Z;

float gyroscopeX = e.DeviceRotationRate.X;
float gyroscopeY = e.DeviceRotationRate.Y;
float gyroscopeZ = e.DeviceRotationRate.Z;

float gravityX = e.Gravity.X;
float gravityY = e.Gravity.Y;
float gravityZ = e.Gravity.Z;

DateTimeOffset timestamp = e.Timestamp; 

Wie Sie sehen, haben wir auch in der Motion Klasse Zugriff auf die Daten der einzelnen Sensoren. Weiterhin erhalten wir einen TimeStamp, so dass wir immer genau wissen, wann der Datensatz empfangen wurde.

Zusammenfassung

Das war’s zur Motion Klasse. Diese API sollte der bevorzugte Weg sein, wie Sie mit den Daten des Gyroskops, des Kompasses und des Accelerometers interagieren, da diese Klasse die Mathematik rund um die Daten und deren Verarbeitung wesentlich erleichtert. Wenn Ihnen nicht sowieso schon zahlreiche Anwendungsfälle einfallen, hier ist ein großartiges Beispiel:

Ich habe eine tolle Anwendung des Accelerometers in einer Anwendung für Fahrradfahrer gesehen. Die wesentliche Funktion der Anwendung war, zu ermitteln, ob der Benutzer gerade Fahrrad fährt. Als kleines Sicherheitsfeature konnte man eine Telefonnummer für Notfälle einspeichern. Sobald die Anwendung ein ungewöhnlich aprupten Halt feststellte, gefolgt von wenig bis keiner Bewegung, schickte die Anwendung automatisch eine SMS mit den aktuellen GPS Koordinaten an die eingespeicherte Notfalltelefonnummer. Danach kam eine Aufforderung zum Anruf von 110. Diese Anwendung ist ein Beispiel für eine kreative Nutzung von Accelerometer- und Standort-Daten, die die Sicherheit von Fahrradfahrern verbessern kann.

Wenn Sie den Source Code der Solution zu diesem Artikel herunterladen möchten, klicken Sie auf den Download Code Button:

Morgen wenden wir uns einer weiteren nützlichen API zu: der Kamera. Seit Mango haben wir die Möglichkeit, die Rohdaten der Kamera zu verarbeiten. Wir werden besprechen, wie man die Daten bekommt und was man damit machen kann.

Bis dahin!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.