Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
|
In this tutorial we will demonstrate how GPS data can be collected from a serial GPS module. We are using an Andoer u-Blox NEO-6M GPS module, which has excellent GPS capabilities. One of the NEMA sentences that it collects includes the GPGGA sentence which provides essential fix data. We'll show you how to develop an application that will collect the serial data in the form of a sentence, which is then parsed using our custom parser to extract the essential GPS data.
Come along and let's do some Regex!!
First of all, connect the GPS breakout board to the Raspberry Pi 3B, as shown in the schematics. Then, launch a Visual Studio instance and create a Blank Application (Universal Windows).
For our purposes, we'll choose the UWP version.
Your brand new UWP application will launch for edit in Visual Studio. You'll notice an empty design palette to begin with that is MainPage.xaml. If you look in the code behind file you'll see the MainPage class which has a default constructor. Basically, this is where the program enters for execution when launched. Now let's add some design elements into the MainPage and make it look nice. Below is the design we put together for use in this tutorial:
You'll want to enable Windows IoT Capabilities by right-clicking the References from the Solution Explorer and choosing 'Add Reference'. Then, select Universal Windows >> Extensions >> Windows IoT Extensions for UWP, and finally click 'OK'.
Don't forget to provide Serial Capabilities to the UWP app by opening the Package.appmanifest file and adding these lines:
<Capabilities>
<DeviceCapability Name="serialcommunication">
<Device Id="any">
<Function Type="name:serialPort" />
</Device>
</DeviceCapability>
</Capabilities>
Now your program is ready to have some very nice programming logic to receive GPS data.
We have included the code for our fully working example with design interface, too. The code itself is well commented, and we have high hopes that you'll be able to embed it into your project without any difficulty. The fully working program will look something like this:
When the whole tutorial put together, Visual Studio should look like this:
And that's it! Don't forget to look through our code and try to find some use for it. Feel free to embed it in your code! You could also check out our other tutorial series where we have made use of this technique to extract GPS data and stream it to the IoT Hub.
001 - How to Setup Azure for IoT
Bye for now!
MainPage.xaml.cs
C#////The MIT License(MIT)
////Copyright(c) 2016 BardaanA
////Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
////The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
////THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace GPSSerialGPGGA
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
// Here once again we make use of Containment/Delegation model
// Encapsulation at its best.
// This is our customized SerialReceiver class
private SerialReceiver serialReceiver;
// Main timer to initiate a onTick event.
private DispatcherTimer mainTimer;
private GPGGA gpgga;
private GPSData gpsData;
private string receivedMessage = "";
public MainPage()
{
this.InitializeComponent();
SetupGPGGA();
SetupSerial();
// Here we setup a timer to tick every 2 seconds. The GPS module we're using is programmed to
// poll data every 1 second. So this two seconds might seem too slow at the beginning but over time
// for GPS data we found it to be more than sufficient.
SetupTimer(2000);
}
// This is where we define the main program logic, inside the onTick method for the main timer.
// So the code inside this block will execute every timer tick. In this case every two seconds.
private async void MainTimerOnTick(object sender, object e)
{
if(serialReceiver.IsInitialized) // Serial port initialized check.
{
receivedMessage = await serialReceiver.ReadAsync(1024); // We're reading 1024 Bytes of data at one read
textBoxNEMASentence.Text = receivedMessage;
// This is where our custom defined Regular Expression class is made use of. The GPS data values are parsed using Regex and
// packaged into our custom GPSData type for easier handling and transport.
gpsData = gpgga.TryParse(receivedMessage);
textBlockGPSFixStatusStatus.Text = gpsData.Status.ToString();
if(gpsData.Status == GPSStatus.Active) // GPS Fix data check
{
textBoxTime.Text = gpsData.Time;
textBoxLatitude.Text = gpsData.Latitude.ToString();
textBoxLongitude.Text = gpsData.Longitude.ToString();
textBoxNumberOfSatellites.Text = gpsData.NumberOfSatellites.ToString();
textBoxAltitude.Text = gpsData.Altitude.ToString();
}
}
else
{
textBlockGPSFixStatusStatus.Text = "Inactive";
textBlockSerialStatusStatus.Text = "Inactive";
}
}
// Setting up our Regex and Match for faster execution when called
private void SetupGPGGA()
{
gpsData = new GPSData();
gpgga = new GPGGA();
}
// Setting up our SerialReceiver class
private async void SetupSerial()
{
serialReceiver = new SerialReceiver();
buttonSerialClose.IsEnabled = false;
}
// Setting up the main timer and assigning the event handler method to the Tick
private void SetupTimer(int interval)
{
// We like using this particular timer class in our UWP applications
// We have found it to be somewhat useful
mainTimer = new DispatcherTimer();
mainTimer.Interval = TimeSpan.FromMilliseconds(interval);
mainTimer.Start();
mainTimer.Tick += MainTimerOnTick;
}
// Initializing our already setup SerialReceiver class
private async void buttonSerialOpen_Click(object sender,RoutedEventArgs e)
{
await serialReceiver.Initialize();
if(serialReceiver.IsInitialized)
{
textBlockSerialStatusStatus.Text = "Active";
buttonSerialClose.IsEnabled = true;
buttonSerialOpen.IsEnabled = false;
}
}
// Close the ZSerial Port and save the valuable resources if necessary.
private void buttonSerialClose_Click(object sender,RoutedEventArgs e)
{
serialReceiver.Close();
buttonSerialClose.IsEnabled = false;
buttonSerialOpen.IsEnabled = true;
}
}
}
GPGGA.cs
C#////The MIT License(MIT)
////Copyright(c) 2016 BardaanA
////Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
////The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
////THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
namespace GPSSerialGPGGA
{
// This class contains methods used to parse the NEMA sentences and extract GPS data from it.
class GPGGA
{
private GPSData gpsData;
Match regexGPGGAMatch;
// We've been using these StringBuilder object for a while, we kind of like their flexibility.
StringBuilder outputString;
string[] parseResult;
Regex regexGPGGA;
// We're certain there is a better way of dealing with these data types. Putting it on hold for now!
int status;
string time;
double latitude;
char polarity;
double longitude;
char alignment;
int numberOfSatellites;
double altitude;
double geoid;
public GPGGA()
{
gpsData = new GPSData();
outputString = new StringBuilder();
// Regular expression used to parse out the GPS data from a GPGGA sentence
regexGPGGA = new Regex(@"\$GPGGA,-*\d*\.*\d*,-*\d*\.*\d*,[N]*,-*\d*\.*\d*,[W]*,-*\d*,-*\d*,-*\d*\.*\d*,-*\d*\.*\d*,[M]*,-*\d*\.*\d*,[M]*,",RegexOptions.Compiled);
// Setting initial value to zero, so that when the GPS data is lost it will report zeros than NULL. Nasty NULL!! ;)
status = 0;
time = "";
latitude = 0.0;
polarity = ' ';
longitude = 0.0;
alignment = ' ';
numberOfSatellites = 0;
altitude = 0.0;
geoid = 0.0;
}
public GPSData TryParse(string input)
{
if(input.Length != 0)
{
// GPS data parsing function
regexGPGGAMatch = regexGPGGA.Match(input.ToString());
if(regexGPGGAMatch.Success) // if Match found
{
outputString = new StringBuilder(regexGPGGAMatch.Value.ToString());
// This is where the string is split into components seperated by commas
parseResult = Regex.Split(outputString.ToString(),@",");
int.TryParse(parseResult[6],out status);
if(status != 0) // If the status is inactive then all the data is invalid
{
gpsData.Status = GPSStatus.Active;
time = parseResult[1];
double.TryParse(parseResult[2],out latitude);
char.TryParse(parseResult[3],out polarity);
double.TryParse(parseResult[4],out longitude);
char.TryParse(parseResult[5],out alignment);
int.TryParse(parseResult[7],out numberOfSatellites);
double.TryParse(parseResult[11],out geoid);
if(geoid != 0) // If this Geoid value is not available then the Altitude value becomes invalid!
{
double.TryParse(parseResult[9],out altitude);
}
gpsData.Time = time;
gpsData.Latitude = TransformGPSValue(latitude,polarity);
gpsData.Longitude = TransformGPSValue(longitude,alignment);
gpsData.Altitude = altitude;
gpsData.NumberOfSatellites = numberOfSatellites;
outputString.Clear(); // Clear the StringBuilder object
}
else
{
gpsData.Status = GPSStatus.Inactive;
}
}
}
return gpsData;
}
// Oh! Well! This part was tricky. Eventhough it looks like plain arithmetic. Oh, Boy!
private double TransformGPSValue(double val,char direction)
{
double result = 0.0;
double result_hundreth = 0.0;
int result_integer = 0;
double result_remainder = 0.0;
if(val != 0)
{
result_hundreth = val / 100.0;
result_integer = (int)result_hundreth;
result_remainder = (result_hundreth - (double)result_integer) / 60.0;
result = result_integer + result_remainder * 100;
}
if(direction == 'W')
{
return -result;
}
return result;
}
}
}
GPSData.cs
C#////The MIT License(MIT)
////Copyright(c) 2016 BardaanA
////Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
////The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
////THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GPSSerialGPGGA
{
public enum GPSStatus : int { Inactive = 0, Active = 1 };
// Custom GPS Datatype desined for easy transport. We hope you like it! ():)
class GPSData
{
public GPSStatus Status { get; set; }
public string Time { get; set; }
public int NumberOfSatellites { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
public double Altitude { get; set; }
public GPSData()
{
Status = GPSStatus.Inactive;
Time = "";
NumberOfSatellites = 0;
Latitude = 0.0;
Longitude = 0.0;
Altitude = 0.0;
}
// These ToString() methods comes in handy while debugging. You gotta love em.
public override string ToString()
{
return $"GPS :: Status: {Status.ToString()}, Time: {Time}, Latitude: {Latitude}, Longitude: {Longitude}, Altitude: {Altitude}";
}
}
}
GPSException.cs
C#////The MIT License(MIT)
////Copyright(c) 2016 BardaanA
////Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
////The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
////THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GPSSerialGPGGA
{
// Just following the exceptional convention ;)
class GPSException : Exception
{
private string message;
public GPSException(string msg)
{
message = msg;
}
public override string Message
{
get
{
return $"GPS fail! " + message + " " + base.Message;
}
}
}
}
SerialReceiver.cs
C#////The MIT License(MIT)
////Copyright(c) 2016 BardaanA
////Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
////The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
////THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//using System.Diagnostics;
using Windows.Devices.Enumeration;
using Windows.Devices.SerialCommunication;
using Windows.Foundation;
using Windows.Storage.Streams;
namespace GPSSerialGPGGA
{
// Here we use the containment/delegation model.
// Encapsulation at its best.
class SerialReceiver
{
// Raspberry pi's builtin serial device, where the GPS module is hooked up.
private SerialDevice serialPort;
// DataReader for reading serial port stream
private DataReader dataReader;
// Yada, yada, yada. We mean just standard procedure. Thank you Windows IOT team for making this so rediculously awesome.
private DeviceInformationCollection deviceInformationCollection;
// IsInitialized flag, a check system, that we've been carrying since C/Linux System Programming days. We absolutely love this technique!!
// To be honest we haven't done the CIL profiling to see the impact, but we've seen it work like a charm in UNIX based systems.
// We might stop using it once we find out... ??
private bool initialized;
public bool IsInitialized
{
get
{
return initialized;
}
}
public async Task Initialize()
{
initialized = false;
try
{
// Standard procedure to enable serial
string aqs = SerialDevice.GetDeviceSelector();
deviceInformationCollection = await DeviceInformation.FindAllAsync(aqs);
// Here you might encounter a problem with the setting if you have more than one serial device
// connected to your board. Just read the error message and change the index value
// for the serial device if necessary.
DeviceInformation selectedDevice = deviceInformationCollection[0];
serialPort = await SerialDevice.FromIdAsync(selectedDevice.Id);
serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
serialPort.BaudRate = 9600;
serialPort.Parity = SerialParity.None;
serialPort.StopBits = SerialStopBitCount.One;
serialPort.DataBits = 8;
serialPort.Handshake = SerialHandshake.XOnXOff;
if(serialPort != null)
{
dataReader = new DataReader(serialPort.InputStream);
initialized = true;
//Debug.WriteLine("SerialPort successfully initialized!");
}
}
catch(Exception ex)
{
//Debug.WriteLine("SerialReceiver failed to initialize!" + ex.Message);
throw new GPSException(ex.Message);
}
}
// This function returns a string which will then be passed on to the parser to extract GPS data!
public async Task<string> ReadAsync(uint bufferLength)
{
try
{
// We did this to avoid our program from crashing with some horrible errors?? Any suggestions will be highly appreciated! Thanks in advance :)
// My best guess is that the unfinished thread was competing with its shadow?? We might end up writing a book about "Shadow of a Thread". LOL
//var bytesRead = await this.dataReader.LoadAsync(bufferLength);
IAsyncOperation<uint> newTaskLoad = dataReader.LoadAsync(bufferLength);
newTaskLoad.AsTask().Wait();
var bytesRead = newTaskLoad.GetResults();
if(bytesRead > 0)
{
return dataReader.ReadString(bytesRead);
}
}
catch(Exception ex)
{
//Debug.WriteLine("Reading serial data failed!" + ex.Message);
throw new GPSException(ex.Message);
}
// Once again horrible production code we know, but hey it helps avoid so many problems while testing.
// Don't worry it will be taken care of when the system is production ready...?
return $"";
}
public void Close()
{
if(serialPort != null)
{
serialPort.Dispose();
initialized = false;
serialPort = null;
}
}
}
}
<Page
x:Class="GPSSerialGPGGA.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:GPSSerialGPGGA"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="#FFD2E6FF" BorderBrush="#FFF7C0C0">
<Button x:Name="buttonSerialOpen" Content="Open Serial" HorizontalAlignment="Left" Margin="730,14,0,0" VerticalAlignment="Top" Height="32" Width="127" Click="buttonSerialOpen_Click"/>
<Button x:Name="buttonSerialClose" Content="Close Serial" HorizontalAlignment="Left" Margin="880,14,0,0" VerticalAlignment="Top" Click="buttonSerialClose_Click"/>
<TextBlock x:Name="textBlockSerialStatus" HorizontalAlignment="Left" Margin="42,14,0,0" TextWrapping="Wrap" Text="Serial Port Status: " VerticalAlignment="Top" Height="32" Width="186" FontSize="22"/>
<TextBox x:Name="textBoxNEMASentence" HorizontalAlignment="Left" Margin="42,51,0,0" TextWrapping="Wrap" Text="..." VerticalAlignment="Top" Height="320" Width="934" FontSize="12"/>
<TextBlock x:Name="textBlockGPSFixStatus" HorizontalAlignment="Left" Margin="42,376,0,0" TextWrapping="Wrap" Text="GPS GPGGA Fix Status: " VerticalAlignment="Top" Height="34" Width="252" FontSize="24"/>
<TextBox x:Name="textBoxTime" HorizontalAlignment="Left" Margin="184,432,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="342" FontSize="24"/>
<TextBlock x:Name="textBlockTime" HorizontalAlignment="Left" Margin="60,432,0,0" TextWrapping="Wrap" Text="Time: " VerticalAlignment="Top" Height="44" Width="119" FontSize="24"/>
<TextBox x:Name="textBoxLatitude" HorizontalAlignment="Left" Margin="184,498,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="342" FontSize="24"/>
<TextBlock x:Name="textBlockLatitude" HorizontalAlignment="Left" Margin="60,498,0,0" TextWrapping="Wrap" Text="Latitude: " VerticalAlignment="Top" Height="44" Width="119" FontSize="24"/>
<TextBox x:Name="textBoxLongitude" HorizontalAlignment="Left" Margin="184,568,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="342" FontSize="24"/>
<TextBlock x:Name="textBlockLongitude" HorizontalAlignment="Left" Margin="60,568,0,0" TextWrapping="Wrap" Text="Longitude: " VerticalAlignment="Top" Height="44" Width="119" FontSize="24"/>
<TextBox x:Name="textBoxNumberOfSatellites" HorizontalAlignment="Left" Margin="318,636,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="208" FontSize="24"/>
<TextBlock x:Name="textBlockNumberOfSatellites" HorizontalAlignment="Left" Margin="60,636,0,0" TextWrapping="Wrap" Text="Number of Satellites: " VerticalAlignment="Top" Height="44" Width="253" FontSize="24"/>
<TextBox x:Name="textBoxAltitude" HorizontalAlignment="Left" Margin="184,704,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="342" FontSize="24"/>
<TextBlock x:Name="textBlockAltitude" HorizontalAlignment="Left" Margin="60,704,0,0" TextWrapping="Wrap" Text="Altitude: " VerticalAlignment="Top" Height="44" Width="119" FontSize="24"/>
<Image x:Name="image" HorizontalAlignment="Left" Height="316" Margin="605,432,0,0" VerticalAlignment="Top" Width="371" Source="Assets/GPSConstellation.jpg"/>
<TextBlock x:Name="textBlockGPSFixStatusStatus" HorizontalAlignment="Left" Margin="306,376,0,0" TextWrapping="Wrap" Text="..." VerticalAlignment="Top" Height="34" Width="670" FontSize="24"/>
<TextBlock x:Name="textBlockSerialStatusStatus" HorizontalAlignment="Left" Margin="226,14,0,0" TextWrapping="Wrap" Text="..." VerticalAlignment="Top" Height="32" Width="468" FontSize="22"/>
</Grid>
</Page>
Comments