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

31 Tage Mango | Tag #6: Motion

Die­ser Arti­kel ist Tag #6 der Serie 31 Tage Man­go von Jeff Blan­ken­burg.

Der Ori­gi­nal­ar­ti­kel befin­det sich hier: Day #6: Moti­on.

Heu­te erfah­ren wir, wie wir die Infor­ma­tio­nen der ver­gan­ge­nen Tage mit Hil­fe der Moti­on Klas­se wesent­lich ein­fa­cher hand­ha­ben kön­nen. Die Moti­on Klas­se ver­eint die Daten des Acce­le­ro­me­ters, des Kom­pas­ses und des Gyro­skops hin­ter einer ein­heit­li­chen Schnitt­stel­le.

Roll‑, Nick- und Gier-Winkel

NICKEN GIEREN ROLLEN

Wie Sie in den ani­mier­ten Bil­dern oben sehen kön­nen, sind der Roll‑, der Nick- und der Gier-Win­kel wich­ti­ge Daten in der Luft­fahrt. Sie las­sen sich aber auch über­tra­gen auf unse­re Win­dows Pho­ne Gerä­te. Mit dem Acce­le­ro­me­ter und dem Gyro­skop waren wir in der Lage, Daten ent­lang der drei Ach­sen X, Y und Z im Raum zu erhal­ten. Rol­len, Nicken und Gie­rung beschrei­ben die Rota­ti­on um die drei Ach­sen im Raum. Die­se Win­kel wer­den durch etwas Mathe­ma­tik unter der Hau­be der Moti­on Klas­se berech­net (mehr Infor­ma­tio­nen über die Roll‑, Nick- und Gier-Win­kel kön­nen Sie auf Wiki­pe­dia nach­le­sen).

Die Motion Klasse benutzen

In den vor­an­ge­gan­ge­nen Bei­spie­len zum Gyro­skop und zum Kom­pass muss­ten wir erst über­prü­fen, ob die Hard­ware die Sen­so­ren über­haupt unter­stützt bevor wir Daten von ihnen emp­fan­gen konn­ten. Die Prü­fung der ein­zel­nen Sen­so­ren ist bei der Moti­on Klas­se nicht not­wen­dig. Aller­dings müs­sen wir sicher­stel­len, dass Moti­on gene­rell unter­stützt wird.

Bevor wir in die Ein­zel­hei­ten des Codes für die Moti­on Klas­se ein­stei­gen, ent­wer­fen wir eine Benut­zer­schnitt­stel­le. In die­sem Bei­spiel wer­den wir einen XAML Stern auf dem Tele­fon anzei­gen. Die Benut­zer­ober­flä­che sieht dann in etwa so aus:

Basie­rend auf dem Gie­rungs­win­kel, den uns die Moti­on Klas­se lie­fert, sind wir in der Lage, die Ori­en­tie­rung des Sterns zu ver­än­dern. Wenn der Benut­zer das Tele­fon dreht, wird der Stern aus Sicht des Benut­zers unbe­weg­lich erschei­nen. Zur Illus­tra­ti­on des Effekts ist hier ein klei­nes Video:

Hier ist das XAML um die Benut­zer­ober­flä­che zu rea­li­sie­ren:

<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, erstel­len wir den Stern, indem wir ein neu­es Assem­bly von Expres­si­on Blend ver­wen­den. Wir müs­sen Blend aller­dings nicht benut­zen, um die Con­trols aus dem Assem­bly zu ver­wen­den. Sie müs­sen eine Refe­renz zum Microsoft.Expression.Drawing Assem­bly hin­zu­fü­gen und den ent­spre­chen­den XML Name­space in der XAML Sei­te bekannt machen. Wenn Sie damit noch nicht her­um­ex­pe­ri­men­tiert haben, kön­nen Sie ja mal den Wert der Eigen­schaft Point­Count des Regu­lar­Po­ly­gon ver­än­dern. Sie kön­nen Ster­ne mit belie­big vie­len Punk­ten erstel­len (wobei Sie etwa ab 60 kei­ne gro­ßen Unter­schie­de mehr fest­stel­len wer­den).

Sie wer­den zwei wei­te­re Ände­run­gen am Stern fest­stel­len. Die ers­te ist die Fill Far­be. Im Bei­spiel habe ich einen Solid­Co­lor­Brush defi­niert, der die vom Benut­zer ein­ge­stell­te Akzent­far­be ver­wen­det. Die Akzent­far­ben und Farb­ein­stel­lun­gen von Win­dows Pho­ne wur­den in Day #5 of the 31 Days of Win­dows Pho­ne behan­delt.

Um den Stern zu bewe­gen, nut­zen wir ähn­li­che Kon­zep­te wie bei der Ver­wen­dung der ein­zel­nen Sen­so­ren. Zunächst über­prü­fen wir die Ver­füg­bar­keit von Moti­on:

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“ Kom­men­tar steht, initia­li­sie­ren wir unser Moti­on Objekt und erstel­len einen Event Hand­ler, der des­sen Daten emp­fan­gen 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üs­sen wir noch etwas Thread­si­cher­heit üben. Da der Cur­rentVa­lue­Ch­an­ged Hand­ler nicht auf dem UI Thread auf­ge­ru­fen wird, müs­sen wir erst wie­der zurück auf den UI Thread sprin­gen, bevor wir die Benut­zer­ober­flä­che aktua­li­sie­ren kön­nen. Das Auf­ru­fen einer Metho­de auf dem UI Thread machen wir mit der Metho­de Dispatcher.BeginInvoke(). im fol­gen­den Bei­spiel ist der ent­spre­chen­de Auf­ruf hin­zu­ge­fügt. Außer­dem haben wir in der UpdateUI Metho­de bereits die Dre­hung des Sterns abhän­gig von den Sen­sor­da­ten ein­ge­baut (Anm. leit­ning: die­ser Absatz lau­tet im Orgi­nal­ar­ti­kel anders. Ich habe hier geschrie­ben, war­um mei­ner Mei­nung nach Dispatcher.BeginInvoke auf­ge­ru­fen wird und las­se mich ger­ne eines Bes­se­ren beleh­ren).

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, ver­wen­den wir den Gie­rungs­win­kel, um den Rota­te­Trans­form Win­kel unse­res Sterns zu ver­än­dern. Wenn Sie ein Win­dows Pho­ne Gerät zur Hand haben, pro­bie­ren Sie die Bei­spiel­an­wen­dung doch mal aus. Das Ver­hal­ten wird sich höchst­wahr­schein­lich stark unter­schei­den zwi­schen Gerä­ten, die ein Gyro­skop ver­baut haben und sol­chen, die keins haben. Wie ich ges­tern erwähnt habe, lie­fert das Gyro­skop wesent­lich „glat­te­re“ und genaue­re Daten. Der Stern wird sich daher wesent­lich wei­cher bewe­gen wenn ein Gyro­skop vor­han­den ist.

Weitere interessante Daten…

Im obi­gen Bei­spiel habe ich eigent­lich nur den Gie­rungs­win­kel der Moti­on Klas­se benutzt. Die Moti­on Klas­se lie­fert natür­lich eine Rei­he wei­te­rer nütz­li­cher Daten. In der Win­dows Pho­ne Solu­ti­on, die Sie unten her­un­ter­la­den kön­nen, habe ich Varia­blen für alle ande­ren Daten der Moti­on Klas­se ein­ge­fü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 Moti­on Klas­se Zugriff auf die Daten der ein­zel­nen Sen­so­ren. Wei­ter­hin erhal­ten wir einen TimeS­tamp, so dass wir immer genau wis­sen, wann der Daten­satz emp­fan­gen wur­de.

Zusammenfassung

Das war’s zur Moti­on Klas­se. Die­se API soll­te der bevor­zug­te Weg sein, wie Sie mit den Daten des Gyro­skops, des Kom­pas­ses und des Acce­le­ro­me­ters inter­agie­ren, da die­se Klas­se die Mathe­ma­tik rund um die Daten und deren Ver­ar­bei­tung wesent­lich erleich­tert. Wenn Ihnen nicht sowie­so schon zahl­rei­che Anwen­dungs­fäl­le ein­fal­len, hier ist ein groß­ar­ti­ges Bei­spiel:

Ich habe eine tol­le Anwen­dung des Acce­le­ro­me­ters in einer Anwen­dung für Fahr­rad­fah­rer gese­hen. Die wesent­li­che Funk­ti­on der Anwen­dung war, zu ermit­teln, ob der Benut­zer gera­de Fahr­rad fährt. Als klei­nes Sicher­heits­fea­ture konn­te man eine Tele­fon­num­mer für Not­fäl­le ein­spei­chern. Sobald die Anwen­dung ein unge­wöhn­lich aprup­ten Halt fest­stell­te, gefolgt von wenig bis kei­ner Bewe­gung, schick­te die Anwen­dung auto­ma­tisch eine SMS mit den aktu­el­len GPS Koor­di­na­ten an die ein­ge­spei­cher­te Not­fall­te­le­fon­num­mer. Danach kam eine Auf­for­de­rung zum Anruf von 110. Die­se Anwen­dung ist ein Bei­spiel für eine krea­ti­ve Nut­zung von Acce­le­ro­me­ter- und Stand­ort-Daten, die die Sicher­heit von Fahr­rad­fah­rern ver­bes­sern kann.

Wenn Sie den Source Code der Solu­ti­on zu die­sem Arti­kel her­un­ter­la­den möch­ten, kli­cken Sie auf den Down­load Code But­ton:

Mor­gen wen­den wir uns einer wei­te­ren nütz­li­chen API zu: der Kame­ra. Seit Man­go haben wir die Mög­lich­keit, die Roh­da­ten der Kame­ra zu ver­ar­bei­ten. Wir wer­den bespre­chen, 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