Dieser Artikel ist Tag #4 der Serie 31 Tage Mango von Jeff Blankenburg.
Der Originalartikel befindet sich hier: Day #4: Compass.
Heute möchte ich einen Sensor diskutieren, der in jedem derzeitigen Windows Phone Gerät vorhanden ist: den Kompass. Der Kompass war auch schon in den Geräten der ersten Generation eingebaut — als App Entwickler hatte man nur keinen API Zugriff darauf. Mit dem Windows Phone 7.5 Update ist der Kompass ein optionaler Bestandteil der Hardware geworden. Dafür hat er eine reichhaltige API bekommen, die der API für den Accelerometer ähnelt.
Wenn Sie die Anwendung dieses Artikels auf Ihrem Gerät ausprobieren möchten, können Sie diese vom Windows Phone Marketplace herunterladen!
Bevor ich dazu komme, wie die Kompass API verwendet werden kann, lassen Sie uns kurz darüber reden, was ein Kompass eigentlich ist.
Was ist ein Kompass?
Ein Kompass dient traditionell dazu, die Richtung des magnetischen Nordpols der Erde zu bestimmen. Wenn man das Wort Kompass hört, denkt man üblicherweise an so ein Gerät:
Ein Handy enthält natürlich nicht so ein Gerät. Mobiltelefone verwenden etwas, das als Magnetometer bezeichnet wird. Ein Magnetometer kann ebenfalls die Richtung des magnetischen Nordpols bestimmen. Zudem kann er aber die Rotation des Geräts relativ zum magnetischen Nordpol ermitteln. Weiterhin ist er in der Lage, magnetische Felder rund um das Gerät zu erkennen (die oft die anderen Berechnungen stören können). Um diese Effekte vorzuführen, habe ich ein kleines Video erstellt, in welchem ich einen Magneten zusammen mit meinem Windows Phone verwende.
Die X, Y, und Z Werte, die Sie auf dem Bildschirm sehen, werden gemessen in Mikrotesla. Diese Einheit gibt die Stärke eines Magnetfeldes an. Die beiden weißen Werte oberhalb der X, Y und Z Werte sind die magnetische und die tatsächliche Peilung — jeweils gemessen in Grad. MagneticHeading benutzt den magnetischen Nordpol als Referenzpunkt während TrueHeading den geographischen Nordpol verwendet. Jetzt, wo wir die Hintergründe zum Kompass Sensor kennen, lassen Sie uns die Anwendung aus dem Video realisieren.
Den Windows Phone Kompass benutzen
Wir beginnen mit der Benutzeroberfläche. Falls Sie das Video nicht angeschaut haben, so soll die Benutzeroberfläche aussehen:
Wenn Sie die Anwendung fertig gestellt haben, können Sie den Trick mit dem Magneten aus dem Video selbst ausprobieren. Hier ist das XAML für die Benutzerschnittstelle:
<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="compass" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/> </StackPanel> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <TextBlock Height="30" HorizontalAlignment="Left" Margin="20,73,0,0" Text="MAGNETIC" VerticalAlignment="Top" Foreground="White" FontSize="28" FontWeight="Bold"/> <TextBlock Height="30" HorizontalAlignment="Right" Margin="0,74,47,0" Text="TRUE" VerticalAlignment="Top" Foreground="Gray" FontSize="28" FontWeight="Bold"/> <TextBlock Height="30" HorizontalAlignment="Left" Margin="20,100,0,0" Name="magneticValue" Text="1.0" VerticalAlignment="Top" Foreground="White" FontSize="28" FontWeight="Bold" Width="147" TextAlignment="Center" /> <TextBlock Height="30" HorizontalAlignment="Right" Margin="0,100,20,0" Name="trueValue" Text="1.0" VerticalAlignment="Top" Foreground="Gray" FontSize="28" FontWeight="Bold" Width="123" TextAlignment="Center" /> <TextBlock Height="30" HorizontalAlignment="Left" Margin="20,140,0,0" Name="xBlock" Text="X: 1.0" VerticalAlignment="Top" Foreground="Red" FontSize="28" FontWeight="Bold"/> <TextBlock Height="30" HorizontalAlignment="Center" Margin="0,140,0,0" Name="yBlock" Text="Y: 1.0" VerticalAlignment="Top" Foreground="Green" FontSize="28" FontWeight="Bold"/> <TextBlock Height="30" HorizontalAlignment="Right" Margin="0,140,20,0" Name="zBlock" Text="Z: 1.0" VerticalAlignment="Top" Foreground="Blue" FontSize="28" FontWeight="Bold"/> <Line x:Name="magneticLine" X1="240" Y1="350" X2="240" Y2="270" Stroke="White" StrokeThickness="4"></Line> </Grid> </Grid>
Wie Sie sehen, besteht die Benutzeroberfläche im Wesentlichen aus einer Reihe von TextBlock Elementen und einer Line. Die Line werden wir verwenden, um das Verhalten eines traditionellen Kompasses zu imitieren. Sie soll immer Richtung Norden zeigen. In Wirklichkeit zeigt sie immer in Richtung des stärksten Magnetfeldes. Deshalb hat sie sich so stark ablenken lassen als wir den Magneten in die Nähe des Telefons gehalten haben.
Die meiste Arbeit findet in der Code-behind Datei statt — inklusive leicht fortgeschrittener Mathematik, um die Linie richtig zu bewegen. Beginnen wir mit der Initialisierung und Erkennung des Kompasses auf unserem Gerät.
using System; using Microsoft.Phone.Controls; using Microsoft.Devices.Sensors; using Microsoft.Xna.Framework; namespace Day4_Compass { public partial class MainPage : PhoneApplicationPage { Compass compass; public MainPage() { InitializeComponent(); if (Compass.IsSupported) { //DO SOMETHING } } } }
Wie Sie sehen, mussten wir eine Referenz auf das Microsoft.Devices.Sensors Assembly hinzufügen (wir haben zudem eine Referenz zum Microsoft.Xna.Framework Assembly hinzugefügt, aber dazu später). Damit sind wir in der Lage, mit dem Kompass Sensor zu interagieren und zu überprüfen, ob das Gerät des Anwenders überhaupt einen Kompass unterstützt. Dort wo ich den „DO SOMETHING“ Kommentar hinterlassen habe, werden wir den Kompass initialisieren und einen Event Handler erstellen, der die Daten des Kompasses verarbeiten wird.
using System; using Microsoft.Phone.Controls; using Microsoft.Devices.Sensors; using Microsoft.Xna.Framework; namespace Day4_Compass { public partial class MainPage : PhoneApplicationPage { Compass compass; public MainPage() { InitializeComponent(); if (Compass.IsSupported) { compass = new Compass(); compass.TimeBetweenUpdates = TimeSpan.FromMilliseconds(1); compass.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<CompassReading>>(compass_CurrentValueChanged); compass.Start(); } } void compass_CurrentValueChanged(object sender, SensorReadingEventArgs<CompassReading> e) { //MAKE THIS THREAD-SAFE } } }
Im obigen Beispiel haben wir die TimeBetweenUpdates Eigenschaft mit einem Wert belegt um zu begrenzen, wie oft wir neue Daten vom Sensor bekommen. Hier habe ich diesen Wert so gesetzt, dass die Anwendung so viele Aktualisierungen bekommt wie möglich. Da die Aktualiserungshäufigkeit Einfluss auf die Akkulaufzeit hat, sollten Sie nicht öfter aktualisieren als es für Ihre Anwendung erforderlich ist. Seltenere Aktualisierungen erlauben zudem eine flüssigere Bewegung der „Kompassnadel“. Im Video oben werden Sie festgestellt haben, dass die Kompassnadel ziemlich wild herumspringt. Das ist eine direkte Folge der geringen TimeBetweenUpdates.
Weiterhin habe ich einen CurrentValueChanged Event Handler erstellt, welcher uns die neuen Werte des Kompasses liefern wird wenn diese sich geändert haben. Bei einem empfindlichen Sensor wie dem Kompass (oder dem Gyroskop, welches wir morgen behandeln werden) ist es sehr wahrscheinlich, dass Sie einen konstanten Strom an Daten erhalten werden. Die Verarbeitung dieses konstanten Stroms sollte natürlich für Ihre Anwendung sinnvoll sein (Anm. leitning: Beispielsweise sollten Sie nicht bei jeder Änderung der Daten komplizierte und lang dauernde Berechnungen durchführen).
Zu guter Letzt rufen wir die Methode Start(), welche dem Kompass mitteilt, dass er jetzt Daten schicken soll. Diese Daten werden von unserer Event Handler Methode compass_CurrentValueChanged in Form eines CompassReading Objekts empfangen. Der Event Handler wird auf einem separaten Thread aufgerufen. Wenn wir also versuchen würden, die Benutzeroberfläche direkt in diesem Event Handler zu aktualisieren, würden wir eine UnauthorizedAccessException wegen „invalid cross-thread access“ bekommen. An der Stelle, wo oben der Kommentar „MAKE THIS THREAD SAFE“ steht, werden wir das CompassReading Objekt an den UI Thread übergeben. Damit ergibt sich der unten stehende Code:
using System; using Microsoft.Phone.Controls; using Microsoft.Devices.Sensors; using Microsoft.Xna.Framework; namespace Day4_Compass { public partial class MainPage : PhoneApplicationPage { Compass compass; public MainPage() { InitializeComponent(); if (Compass.IsSupported) { compass = new Compass(); compass.TimeBetweenUpdates = TimeSpan.FromMilliseconds(1); compass.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<CompassReading>>(compass_CurrentValueChanged); compass.Start(); } } void compass_CurrentValueChanged(object sender, SensorReadingEventArgs<CompassReading> e) { Dispatcher.BeginInvoke(() => UpdateUI(e.SensorReading)); } private void UpdateUI(CompassReading compassReading) { magneticValue.Text = compassReading.MagneticHeading.ToString("0.00"); trueValue.Text = compassReading.TrueHeading.ToString("0.00"); magneticLine.X2 = magneticLine.X1 - (200 * Math.Sin(MathHelper.ToRadians((float)compassReading.MagneticHeading))); magneticLine.Y2 = magneticLine.Y1 - (200 * Math.Cos(MathHelper.ToRadians((float)compassReading.MagneticHeading))); xBlock.Text = "X: " + compassReading.MagnetometerReading.X.ToString("0.00"); yBlock.Text = "Y: " + compassReading.MagnetometerReading.Y.ToString("0.00"); zBlock.Text = "Z: " + compassReading.MagnetometerReading.Z.ToString("0.00"); } } }
Die Methode Dispatcher.BeginInvoke erlaubt uns, Daten an den UI Thread zu übergeben. Innerhalb der Methode UpdateUI können wir also gefahrlos die Benutzeroberfläche mit den neuen Werten des Kompasses aktualisieren.
Zunächst weisen wir den entsprechenden TextBlocks die Werte MagneticHeading und TrueHeading zu. Ganz unten in der Methode weisen wir zudem die X, Y und Z Werte den entsprechenden Elementen der Benutzeroberfläche zu. Um die Richtung der Linie zu ermitteln, benötigen wir leicht fortgeschrittene Mathematik. Hierzu bedienen wir uns der MathHelper Klasse aus dem Microsoft.Xna.Framework Assembly.
Bei den beiden Werten von magneticLine bestimme ich zunächst die X und die Y Koordinate des Endpunktes der Linie. Da der Startpunkt der Linie immer die Mitte des Bildchirms ist, können wir unsere Berechnung ausgehend von diesem Punkt machen. Ich beginne deshalb mit den Werten magneticLine.X1 und magneticLine.Y1. Der Wert „200“ ist die Länge der Linie. Die Linie ist in jeder Position genau 200 Pixel lang. Der Rest der Berechnung nutzt die Radians des MagneticHeading Wertes um zu bestimmen, wo auf der Kurve der Endpunkt der Linie liegt. Dafür nutze ich die Sin und Cos Funktionen.
Mit dem XAML von oben und dem C# Code, den wir gerade behandelt haben, können Sie leicht eine eindrucksvolle Kompass App entwickeln.
Zusammenfassung
Der Kompass ist ein mächtiger Sensor in unseren Windows Phones, mit dessen Hilfe wir die aktuelle Richtung des Geräts ermitteln können und mit dem wir Magnetfelder in der Umgebung messen können. In den nächsten Tagen werden wir uns mit dem Gyroskop einen weiteren Sensor anschauen und zum Schluss die Motion Klasse betrachten, die alle Sensoren in einer API vereint. Dadurch erhalten wir noch präzisere Informationen über die Orientierung des Geräts.
Wenn Sie eine lauffähige Solution zur Arbeit mit dem Windows Phone Kompass herunterladen möchten, klicken Sie auf das Download Code Symbol.
Morgen werde ich über einen weiteren coolen Sensor schreiben: das Gyroskop. Damit können Sie die Orientierung des Geräts feststellen. Das Gyroskop ist eines der Themen, mit denen Sie auf jeden Fall herumexperimentieren sollten. Bis dahin!