This article is compatible with the latest version of Silverlight for Windows Phone 7.
This is part is the final, 8th part from the article series on Windows Phone 7.
Like many modern devices, your Windows Phone 7 is provided with a series of sensors that helps it to give an effective experience to the user. These sensors are strong hardware requirements, defined by Microsoft to make a device compliant with this Operating System, and many of them are used by the phone to provide some useful features. If you try to place or answer to a call you will observe that once you bring the device near the face the screen goes black. This happen thanks to the proximity sensor that is used to detect when you are speaking at the mike and disable the screen to preserve the battery. Again, when you rotate the phone, the system raise an OrientationChanged event that lets the applications to adapt the layout to the orientation detected by the accelerometer sensor.
When you write your software using Silverlight some of these sensors may be used programmatically from the application, to create innovative interactions with the user. So for instance you can use the acceletometer to create a simple game or the Assisted GPS to detect the location where the user is and launch a position specific search. Thanks to the simplicity of Silverlight use these sensors is really straightforward.
The Accelerometer
The concept behind the Accelerometer is fairly simple. The chip inside your phone is able to detect two things: the orientation of the device and the movement onto the three dimensions. If you watch at your device, with the screen in front of you, it is possible to trace some immaginary lines that converge to the center of the device and follow the three spatial dimensions. One from the top to the bottom, another from the left to the right and the last one from your eyes to the back of the device. These three lines are the axes where the accelerometer sensor works. When the device is stationary, let say it is flat on the table the axis return a value that inform about the current position. In this case the X and Y equals to 0 but the Z is equal to -1. Reversing the phone you will observe that the Z axis assume the value of 1. So probably you will not read anything due the fact the screen it is leaned to the table surface :)
Out of the jokes, remember these directions is simple if you keep in mind that the right side, the top side and the face of the screen returns -1 on the corrisponding axis when this side is up. They also returns 1 when the direction is upside down. These values are perfect if you desire to understand the spatial position of the phone but this is only half of the problem. These values are valid once your phone is static but when you move the device you got something that describe the direction of the movement. If you take the phone and move it you will get a variable value that is the measure of the force you are applying to the device in a given direction. With a simple application of the Pythagorean theorem you can calculate the magnitude of a vector that describe the force applied onto the direction of the vector.
double f = Math.Sqrt(x*x + y*y + z*z);
SIlverlight give you an useful tool to detect the changes on the axes. It is a class named Accelerometer. After you create an instance of this class you can find in the System.Device.Sensors namespace, you can call the Start method and begin to listen a flow of informations that the class notify to you continuously, raising the ReadingChanged event.
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
this.Accelerometer = new Accelerometer();
this.Accelerometer.ReadingChanged +=
new EventHandler<AccelerometerReadingEventArgs>(Accelerometer_ReadingChanged);
this.Accelerometer.Start();
}
private void Accelerometer_ReadingChanged(object sender, AccelerometerReadingEventArgs e)
{
// HANDLE HERE ACCELEROMETER DATA
}
Using the accelerometer to create a Seismometer
Interpreting the incoming data is not an easy task. It imply the manipulation of a great number of events and often dealing with spurious data. If you are interested in how to handle these events you can read this article from the Windows Phone team that explains the problem in a great detail. For the sake of this article I've prepared a little example that shows one interesting application of the Accelerometer. The idea is to detect the oscillations of the earth in both the usual directions for an earthquake. You probably know that an earthquake can have two forms: Undulatory means the Earth move horizontally and Vibrating means the earth moves up and down vertically. If you put the phone flat on a table this means that a vibrating earthquake will give changes on the Z axis and an undulatory earthquake changes both the X and Y axes.
As a normal seismometer my application simulates the pen writing on the paper roll but the track is made by a polyline centered in a Grid. The Polyline is a perfect object for this purpose. It I described by a sequence of Points. Every time a new read push some data I create an instance of the Point structure and save it into a Queue. Then I dequeue the old Point instances to purge the segments of the Polyline that falled out of the screen. Once the queue has been updated I generate the polyline peeking the points from the queue and adding them to the PointCollection.
private void Accelerometer_ReadingChanged(object sender, AccelerometerReadingEventArgs e)
{
if (!calibrated)
this.Calibrate(e);
else
this.Update(e);
}
private void Update(AccelerometerReadingEventArgs e)
{
double x = e.X - xcal;
double y = e.Y - ycal;
double z = e.Z - zcal;
double hrm = Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2)) * Math.Sign(x + y);
double vrm = z;
this.Type = Math.Abs(hrm) > Math.Abs(vrm) ? EarthquakeType.Undulatory : EarthquakeType.Vibrating;
double magnitude = this.Type == EarthquakeType.Undulatory ? hrm : vrm;
lock (_lockObj)
{
double xd, yd;
if (ts == DateTimeOffset.MinValue)
yd = 0;
else
yd = (e.Timestamp - ts).TotalSeconds * 140;
ts = e.Timestamp;
xd = magnitude * 1000;
if (xd > 235) xd = 235;
if (xd < -235) xd = -235;
this.Samples.Enqueue(new Point { X = xd, Y = yd });
this.totalHeight += yd;
while (totalHeight > 800)
{
Point dq = this.Samples.Dequeue();
totalHeight -= dq.Y;
}
}
Deployment.Current.Dispatcher.BeginInvoke(
() =>
{
this.Magnitude.Value = magnitude;
this.UpdateChart();
});
}
private void UpdateChart()
{
lock (_lockObj)
{
double offset = 0;
PointCollection points = new PointCollection();
foreach (Point point in this.Samples.Reverse())
{
offset += point.Y;
points.Add(new Point { X = point.X, Y = offset });
}
this.Chart.Points = points;
this.EQType.Text = this.Type.ToString();
}
}
Interesting is that the Update method pull data directly from the Z axis for the Vibrating direction but is generates a vector with X and Y to calculate the magnitude of the Undulatory direction. Then the resulting value is amplified multiplying it by 1000. This make the seismometer more sensible to the oscillations.
The last part of the example I want to explain is the calibration. When you get data from the Accelerometer the values from phone orientation and from movements are mixed into the three axes values. So the orientation of the phone matter when I'm detecting the oscillations. So I need to calibrate the seismometer reading the initial values from the axes and subtracting them every time before calculating the oscillation. Give a try to the running seismometer putting it flat on the table and then knocking the table with a hand. It is not a precise tool but may be funny to be showed to your friends.
Using the GPS
One of the most important technologies included in your phone today is the GPS sensor. It is able to detect the position of the satellites in the sky and calculate with incredible precision your position on the earth surface. The sensor on board of your device is called aGPS where the "A" is for "Assisted". Usually a common GPS cannot calculate the position when it is in closed spaces and in particular condition the error (usually it is called "drift") in the positioning is high. The GPS in your phone is able to correct the drift of the traditional GPS using the position of wi-fi and cellular emitters so it is really reliable and fast also when you are closed in a room.
There is really a lot of applications for GPS. Crawling the marketplace you will find personal trackers, twitter clients, photograpy, compasses, and an huge number of other. This is because using the GPS in Silverlight is easy, almost easy as using the accelerometer. The following snippet show how to initialize the GeoCoordinateWatcher, a simple and effective class that let you to monitor the GPS sensor and being notified about the change of the position:
this.Watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
this.Watcher.MovementThreshold = 10;
this.Watcher.PositionChanged +=
new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(watcher_PositionChanged);
this.Watcher.Start();
As you probably noticed the GeoCoordinateWatcher is pretty similar to the Accelerometer class. Here we have only a couple of additional parameters to specify but the paradigm is almost the same. The GeoPositionAccuracy parameter you can pass to the constructor defines the accuracy used by the phone to calculate the position. If you do not specify the GeoPositionAccuracy you will get a Default value but you can use High to ask a more accurate precision. In this case the sensor will consume much more resources but the resulting measure is very reliable. The MovementThresold property is used to specify the minimum distance between two updates. The is not any warranty you get the exact distance between two samples but it is a good way to get updates on a regular basis. From a bunch of experiments I made I found that 10 meters is a good compromise but nothing prevent to ask for a more accurate and intensive measure.
When you get the PositionChanged event you get also a number of informations and not only the coordinates of your position on the earth surface. Here is a table that resume the parameters and their meaning:
Position.Timestamp |
The date and time when the position has measured |
Position.Location.Latitude |
The Latitude of the geographic position expressed using the WGS84 datum |
Position.Location.Longitude |
The Longitude of the geographic position expressed using the WGS84 datum |
Position.Location.Altitude |
The elevation from the sea of your position. |
Position.Location.Course |
The direction expressed in degree. 0 means North, 90 means East and so on. |
Position.Location.IsUnknown |
This parameter tells if the read you get is valid or not. It is important to test is before reading anything from the arguments to avoid present dirty informations. |
Position.Location.HorizontalAccuracy |
Define the horizontal precision of the measure expressen in meters |
Position.Location.VerticalAccuracy |
Define the vertical precision of the measure expressen in meters |
Position.Location.Speed |
Define the speed of the movement between two sequential reads. It is expressed in m/s. To get a measure in km/h you have to multiply it by 3.6 |
This is not a complete set of informations like what you can usually get from a GPS sensor. For instance you miss the number of satellites that determined your position. Probably this information is used internally to calculate the horizontal and vertical accuracy and definitely is not really important for the use of the sensor.
In the attached example you can see the sensor in action. I've created a simple page where I present the most important values on a grid and on a map. The use of the Bing map control is very easy because the GeoCoordinate class is used also by this library and you can simply assign the resulting value to the Center property.
private void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
if (!e.Position.Location.IsUnknown)
{
this.Map.Center = e.Position.Location;
this.txtLatitude.Text = this.FormatDouble(e.Position.Location.Latitude, "0.00000");
this.txtLongitude.Text = this.FormatDouble(e.Position.Location.Longitude, "0.00000");
this.txtAltitude.Text = this.FormatDouble(e.Position.Location.Altitude, "0.00");
this.txtCourse.Text = this.FormatDouble(e.Position.Location.Course, "0.00");
this.txtSpeed.Text = this.FormatDouble(e.Position.Location.Speed * 3.6, "0.00");
if (this.Last != null)
this.Distance += e.Position.Location.GetDistanceTo(this.Last);
this.txtDistance.Text = this.FormatDouble(this.Distance / 1000, "0.00");
this.Last = e.Position.Location;
}
}
private string FormatDouble(double value, string format)
{
if (double.IsNaN(value))
return "---";
return value.ToString(format);
}
Additionally I use the GetDistanceTo method in the GeoCoordinate class to calculate the distance you runned from the start of the application. You can reset this information pressing the button on the toolbar as you can do on your car tachimeter.
Download the source code
Conclusion
This article is the last of eight parts I wrote on the great capabilities of the Windows Phone 7. I hope you enjoyed my words and my examples I often attached to the articles. If you believe there is some unexplored parts on this operating system please feel free to add a comment to this article or send an email to the editor to ask more articles I will be very happy to write.
About the Author
Andrea Boschin is 41 years old from Italy and currently lives and works in Treviso, a beautiful town near Venice. He started to work in the IT relatively late after doing some various jobs like graphic designer and school teacher. Finally he started to work into the web and learned by himself to program in VB and ASP and later in C# and ASP.NET. Since the start of his work, Andrea found he likes to learn new technologies and take them into the real world. This happened with ASP.NET, the source of his first two MVP awards, and recently with Silverlight, that he started to use from the v1.0 in some real projects.