aprsp3.jpg    aprs_i1.jpg

APRS-IS paikkatietosovellus (1) lähettää käyttäjän sijainnin datayhteyden kautta APRS-IS palvelimelle (2).
Ohjelman käyttöliittymä perustuu Pivot käyttöliittymämalliin.

Sovellus koostuu neljästä sivusta (MainPage.xaml). Pääsivulla (Location, GPS) näytetään sijainti, nopeus, suunta, korkeus & paikannuksen tarkkuus), painike jolla aloitetaan/lopetetaan tietojen näyttäminen ja lähettäminen APRS-IS palvelimelle. Näytön yläosassa näytetään APRS-IS tietopaketin tiedot.

Configuration sivulla annetaan asetustiedot: radioamatöörikutsu, server passcode (3), palvelimen IP-osoite ja porttinumero, nimi & info teksti ja pollaus tiheys (1-60 minuuttia), jolla asetetaan se, kuinka usein paketti lähetetään APRS-IS palvelimelle. Tiedot talletetaan isolated storage alueelle.(IsoStoHandler.cs)

APRS tietopaketti lähetetään TCP:llä (* TCP NO_DELAY *) määriteltyyn APRS-IS palvelimelle. Paketin rakenne on:
1>APRS,TCPIP*:!latitude/longitude23/45
1 = radioamatöörikutsu
2 = APRS symboli
3 = kompassisuunta
4 = nopeus, maileina/tunti, (dSpeed * 3.6) / 1.852 = muunnos mp/h => km/h)
5 = nimi/info

1. http://www.aprs.fi
2. http://wiki.ham.fi/APRS-IS (teknistä tietoa kehittäjille: http://www.aprs-is.net)
3. http://aprs.ham.fi/laskuri

-------------------------------------------------------------------------------------------------------------------------------------------------
MainPage.xaml.cs

// APRS-IS client for Windows Phone by OH1GIU
// Developed and tested (in WP emulator) with Windows Phone 7.1 (Mango) SDK
// THIS SOURCE CODE IS FREE
// You can freely use this source code in your own projects and applications released in the Windows Phone marketplace.
//
// When building/deploying for real device change gensets.TEST_MODE value to false (gensets.cs)
// Tnx to OH1KO & OH7LZB for help and advices !!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Info;
using System.Device.Location;
using System.Net.Sockets;
using System.Windows.Threading;
using System.Threading;

namespace wpAPRS
{
    public partial class MainPage : PhoneApplicationPage
    {
        private GeoCoordinateWatcher watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
        private double dLat = 0;
        private double dLon = 0;
        private double dCourse = Double.NaN;
        private double dSpeed = Double.NaN;
        private int iTcpPort;
        private bool bConnection = false;
        private bool bPos = false;
        Socket socketAPRS = null;
        static ManualResetEvent done = new ManualResetEvent(false);
        private string aprsMsg = string.Empty;
        private DispatcherTimer timer = new DispatcherTimer();
        private System.Text.StringBuilder sb = new System.Text.StringBuilder();
        private string sendData = string.Empty;

        private bool bSlider = false;
        private bool bIsAprsOn = false;
        private IsoStoHandler isoSto;
        private bool bStart = true;
        private string[] sAprs_data = new string[8] {"","","","","","","",""};

        private string aprsSym = ">";  // APRS symboli http://wiki.ham.fi/APRS_symboli
        private gpsutil gutil = null;
        private string strLogin = string.Empty;
        private bool bShowLoginStr = true;
        private bool bSocketError = false;

        // Constructor
        public MainPage()
        {
            InitializeComponent();

            DataContext = App.ViewModel;
            this.Loaded += new RoutedEventHandler(MainPage_Loaded);

            ClearLocTexts();
            GetAboutInfo();
            bSlider = true;
            textBlockLocSpc.Text = gensets.APRS_SERVER_NO_CONN;
            watcher.PositionChanged += new EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>>(watcher_PositionChanged);
            this.timer.Tick += new EventHandler(timer_Tick);
            this.Loaded += new RoutedEventHandler(timer_Tick);
            gutil = new gpsutil();
        }

        // Load data for the ViewModel Items
        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            if (!App.ViewModel.IsDataLoaded)
            {
                App.ViewModel.LoadData();
            }

            if (bStart)
            {
                bStart = false;
                isoSto = new IsoStoHandler();

                // for testing in emulator
                // stores test values in isolated storage
                // when building for real device change gensets.TEST_MODE value to false
                if (gensets.TEST_MODE)
                {
                    sAprs_data[0] = "OH1xxx-7"; // callsign
                    sAprs_data[1] = "0000"; // passcode
                    sAprs_data[2] = "finland.aprs2.net:14580";
                    sAprs_data[3] = "windows phone test";
                    sAprs_data[4] = "1"; // polling interval in minutes
                    sAprs_data[5] = "finland.aprs2.net";
                    sAprs_data[6] = "14580";
                    sAprs_data[7] = aprsSym; // aprs symbol, http://wa8lmf.net/aprs/APRS_symbols.htm
                    isoSto.CreateDataFile(sAprs_data);
                }

                if (isoSto.ReadDataFile(out sAprs_data[0], out sAprs_data[1], out sAprs_data[2], out sAprs_data[3], out sAprs_data[4], out sAprs_data[5], out sAprs_data[6], out sAprs_data[7]))
                {
                    textBoxConfCallsign.Text = sAprs_data[0];
                    textBoxConfAPRSpasscode.Text = sAprs_data[1];
                    textBoxConfAPRS.Text = sAprs_data[2];
                    textBoxConfName.Text = sAprs_data[3].Trim();
                    double d = 0;
                    if (double.TryParse(sAprs_data[4], out d))
                    {
                        if (d > 0)
                        {
                            sliderConfPollingInt.Value = d;
                        }
                        else
                        {
                            sliderConfPollingInt.Value = gensets.POLL_INT;
                        }
                    }
                    else
                        sliderConfPollingInt.Value = gensets.POLL_INT;
                    Int32.TryParse(sAprs_data[6], out iTcpPort);
                }
            }
        }

        private void GetAboutInfo()
        {
            textBlockAboutCap.Text = gensets.APP_CAP;
            textBlockAboutVer.Text = gensets.APP_VERSION;
            textBlockAboutC.Text = gensets.APP_COPYRIGHT;
            textBlockAboutFirmware.Text = gensets.DEV_FW + DeviceStatus.DeviceFirmwareVersion;
            textBlockAboutHW.Text = gensets.DEV_HW + DeviceStatus.DeviceHardwareVersion;
            textBlockAboutDevName.Text = gensets.DEV_NAME + DeviceStatus.DeviceName;
            textBlockAboutManu.Text = gensets.DEV_MANU + DeviceStatus.DeviceManufacturer;
            textBlockAboutOSversion.Text = Environment.OSVersion.ToString();
            textBlockAboutCLR.Text = gensets.DEV_CLR + Environment.Version.Major.ToString() + "." + Environment.Version.Minor.ToString() +
            gensets.DEV_REV + Environment.Version.Revision.ToString() + gensets.DEV_BUILD + Environment.Version.Build.ToString();
        }

        private void ClearLocTexts()
        {
            textBlockLocLat.Text = "";
            textBlockLocLon.Text = "";
            textBlockLocSpeed.Text = "";
            textBlockLocCourse.Text = "";
            textBlockLocAlt.Text = "";
            textBlockLocTs.Text = "";
            textBlockLocHacc.Text = "";
            textBlockLocSpc.Text = " ";
            textBlockLocSpc2.Text = " ";
        }

        private void textBoxConfCallsign_TextChanged(object sender, TextChangedEventArgs e)
        {
            int cursorLocation = textBoxConfCallsign.SelectionStart;
            textBoxConfCallsign.Text = textBoxConfCallsign.Text.ToUpper();
            textBoxConfCallsign.SelectionStart = cursorLocation;
        }

        private void slider1_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (bSlider)
                textBlockConfPollingInt.Text = "Polling interval (" + Math.Round(e.NewValue).ToString() + " minutes)";
        }

        private void buttonConfSave_Click(object sender, RoutedEventArgs e)
        {
            if (string.IsNullOrEmpty(textBoxConfCallsign.Text.Trim()))
            {
                MessageBox.Show(gensets.ERR_FLD_MAND);
                textBoxConfCallsign.Focus();
                return;
            }
            if (string.IsNullOrEmpty(textBoxConfAPRSpasscode.Text.Trim()))
            {
                MessageBox.Show(gensets.ERR_FLD_MAND);
                textBoxConfAPRSpasscode.Focus();
                return;
            }
            if (string.IsNullOrEmpty(textBoxConfAPRS.Text.Trim()))
            {
                MessageBox.Show(gensets.ERR_FLD_MAND);
                textBoxConfAPRS.Focus();
                return;
            }

            string sTcpPort = string.Empty;
            string sAddress = string.Empty;
            string sAtcp = textBoxConfAPRS.Text.Trim();
            int p = sAtcp.IndexOf(":");
            if (p > 0)
            {
                int l = sAtcp.Length;
                if (l > p)
                {
                    sTcpPort = sAtcp.Substring(p + 1, l - p - 1);
                    sAddress = sAtcp.Substring(0, p);
                }
            }

            if (string.IsNullOrEmpty(sTcpPort))
            {
                MessageBox.Show(gensets.ERR_NO_TCP_PORT);
                textBoxConfAPRS.Focus();
                return;
            }

            int iTempPort;
            if (Int32.TryParse(sTcpPort, out iTempPort) == false)
            {
                MessageBox.Show(gensets.ERR_TCP_PORT_NOT_NUMERIC);
                textBoxConfAPRS.Focus();
                return;
            }

            if (string.IsNullOrEmpty(sAddress))
            {
                MessageBox.Show(gensets.ERR_NO_ADDRESS);
                textBoxConfAPRS.Focus();
                return;
            }

            iTcpPort = iTempPort;
            sAprs_data[0] = textBoxConfCallsign.Text.Trim();
            sAprs_data[1] = textBoxConfAPRSpasscode.Text.Trim();
            sAprs_data[2] = textBoxConfAPRS.Text.Trim();
            if (string.IsNullOrEmpty(textBoxConfName.Text.Trim()) == false)
                sAprs_data[3] = textBoxConfName.Text.Trim();
            else
                sAprs_data[3] = " ";
            sAprs_data[4] = Math.Round(sliderConfPollingInt.Value).ToString();
            sAprs_data[5] = sAddress;
            sAprs_data[6] = sTcpPort;
            sAprs_data[7] = aprsSym;

            isoSto.CreateDataFile(sAprs_data);
            MessageBox.Show(gensets.DATA_SAVED);
        }

        private void buttonLocStartStop_Click(object sender, RoutedEventArgs e)
        {
            ClearLocTexts();
            pivotMain.Title = gensets.APP_TITLE;

            bool bSettingsOk = true;
            if (bIsAprsOn == false)
            {
                if (string.IsNullOrEmpty(sAprs_data[0]) || string.IsNullOrEmpty(sAprs_data[1]) || string.IsNullOrEmpty(sAprs_data[4]) || string.IsNullOrEmpty(sAprs_data[5]) || string.IsNullOrEmpty(sAprs_data[6]))
                {
                    bSettingsOk = false;
                    MessageBox.Show(gensets.APRS_NO_SETTINGS);
                }
            }

            CloseConnection();
            StopTimer();
            if (bIsAprsOn)
            {
                watcher.Stop();
                InitGpsDs();
                bPos = false;
                bIsAprsOn = false;
                buttonLocStartStop.Content = gensets.CONF_START;
            }
            else
            {
                bPos = false;
                InitGpsDs();
                if (watcher.TryStart(false, TimeSpan.FromMilliseconds(gensets.GEO_TIMEOUT)) == true)
                {
                    bIsAprsOn = true;
                    buttonLocStartStop.Content = gensets.CONF_STOP;
                    if (bSettingsOk == true)
                    {
                        sb.Clear();
                        sb.Append(gensets.APRS_MSG_SEND_USER);
                        sb.Append(sAprs_data[0]);
                        sb.Append(gensets.APRS_MSG_SEND_PASS);
                        sb.Append(sAprs_data[1]);
                        sb.Append(gensets.APRS_MSG_SEND_SW);
                        sb.Append(Environment.NewLine);
                        strLogin = sb.ToString();
                        StartTimer();
                        //CreateConnection();
                        //if (bConnection == true)
                        //{
                        //    textBlockLocSpc.Text = gensets.APRS_SERVER_CONN;
                        //    sb.Clear();
                        //    sb.Append(gensets.APRS_MSG_SEND_USER);
                        //    sb.Append(sAprs_data[0]);
                        //    sb.Append(gensets.APRS_MSG_SEND_PASS);
                        //    sb.Append(sAprs_data[1]);
                        //    sb.Append(gensets.APRS_MSG_SEND_SW);
                        //    sb.Append(Environment.NewLine);
                        //    sendData = sb.ToString();

                        //    if (AprsSend(sendData) == true)
                        //    {
                        //        textBlockLocSpc2.Text = sendData;
                        //        StartTimer();
                        //    }
                        //    else
                        //    {
                        //        bConnection = false;
                        //        textBlockLocSpc2.Text = gensets.APRS_SERVER_SEND_ERR_TIMEOUT;
                        //    }
                        //}
                    }
                }
                else
                {
                    MessageBox.Show(gensets.GEO_ERR_TIMEOUT);
                }
            }
        }

        private void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
        {
            dLat = e.Position.Location.Latitude;
            dLon = e.Position.Location.Longitude;
            dSpeed = e.Position.Location.Speed;
            dCourse = e.Position.Location.Course;

            //dSpeed = 9;
            //dCourse = 9;

            bPos = true;
            textBlockLocLat.Text = dLat.ToString();
            textBlockLocLon.Text = dLon.ToString();
            textBlockLocSpeed.Text = Math.Round(dSpeed, 2).ToString() + gensets.LOC_SPEED + "    " + Math.Round(dSpeed * 3.6, 2).ToString() + gensets.LOC_SPEED2;
            textBlockLocAlt.Text = gensets.LOC_ALT_TXT + Math.Round(e.Position.Location.Altitude).ToString() + gensets.LOC_ALT + "  " + gensets.LOC_VACC + Math.Round(e.Position.Location.VerticalAccuracy).ToString() + gensets.LOC_ALT;
            textBlockLocCourse.Text = dCourse.ToString() + gensets.LOC_COURSE;
            textBlockLocHacc.Text = gensets.LOC_HACC + Math.Round(e.Position.Location.HorizontalAccuracy).ToString() + gensets.LOC_ALT;
            textBlockLocTs.Text = e.Position.Timestamp.ToString("dd.MM.yyyy hh:mm:ss");
        }

        private void CreateConnection()
        {
            bool bSockConn = true;

            CloseConnection();

            socketAPRS = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socketAPRS.NoDelay = true;
            
            SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
            socketEventArg.RemoteEndPoint = new DnsEndPoint(sAprs_data[5], iTcpPort);
            socketEventArg.Completed += new
            EventHandler<SocketAsyncEventArgs>(delegate(object o, SocketAsyncEventArgs e)
            {
                if (e.SocketError != SocketError.Success)
                {
                    bSockConn = false;
                    textBlockLocSpc2.Text = gensets.SOCK_ERR + e.SocketError.ToString();
                }
                done.Set();
            });
            done.Reset();
            socketAPRS.ConnectAsync(socketEventArg);
            if (done.WaitOne(gensets.APRS_CONNECT_TIMEOUT))
            {
                bConnection = true;
            }
            else
            {
                bSockConn = false;
                textBlockLocSpc.Text = gensets.APRS_SERVER_CONN_ERR_TIMEOUT;
            }
            if (bSockConn == false)
                bConnection = false;
        }

        private void InitGpsDs()
        {
            dCourse = Double.NaN;
            dSpeed = Double.NaN;
            dLat = 0;
            dLon = 0;
        }

        private void CloseConnection()
        {
            bConnection = false;
            if (socketAPRS != null)
            {
                socketAPRS.Close();
                socketAPRS = null;
            }
        }

        private void StartTimer()
        {
            StopTimer();
            bShowLoginStr = true;
            timer.Interval = TimeSpan.FromSeconds(double.Parse(sAprs_data[4]) * 60);
            timer.Start();
        }

        private void StopTimer()
        {
            timer.Stop();
        }

        void timer_Tick(Object sender, EventArgs args)
        {
            if (bPos == true)
            {
                CreateConnection();
                if (bConnection == true)
                {
                    textBlockLocSpc.Text = gensets.APRS_SERVER_CONN;

                    if (AprsSend(strLogin) == true)
                    {
                        if (bShowLoginStr)
                        {
                            bShowLoginStr = false;
                            pivotMain.Title = strLogin.Replace(Environment.NewLine, "");
                        }
                    }
                    else
                    {
                        bConnection = false;
                        if (bSocketError == false)
                        {
                            textBlockLocSpc2.Text = gensets.APRS_SERVER_SEND_ERR_TIMEOUT;
                        }
                    }
                }

                if (bConnection == true)
                {
                    sb.Clear();
                    sb.Append(sAprs_data[0]);
                    sb.Append(gensets.APRS_MSG_SEND_LOC);
                    sb.Append(gutil.toAprsLat(dLat));
                    sb.Append("/");
                    sb.Append(gutil.toAprsLon(dLon));
                    sb.Append(sAprs_data[7]);
                    if (dSpeed.Equals(Double.NaN) == false && dCourse.Equals(Double.NaN) == false)
                    {
                        sb.Append(gutil.fmtnum(dCourse));
                        sb.Append("/");
                        sb.Append(gutil.fmtnum((dSpeed * 3.6) / 1.852));
                    }
                    sb.Append(sAprs_data[3]);
                    sb.Append(Environment.NewLine);
                    sendData = sb.ToString();

                    if (AprsSend(sendData) == true)
                    {
                        textBlockLocSpc2.Text = sendData.Replace(Environment.NewLine, "");
                    }
                    else
                    {
                        if (bSocketError == false)
                        {
                            textBlockLocSpc2.Text = gensets.APRS_SERVER_SEND_ERR_TIMEOUT;
                        }
                    }
                }
                CloseConnection();
            }
        }

        private bool AprsSend(string strToAprs)
        {
            textBlockLocSpc2.Text = " ";
            bSocketError = false;
            if (socketAPRS != null)
            {
                SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs();
                socketEventArg.RemoteEndPoint = socketAPRS.RemoteEndPoint;
                socketEventArg.UserToken = null;

                socketEventArg.Completed += new
                EventHandler<SocketAsyncEventArgs>(delegate(object o, SocketAsyncEventArgs e)
                {
                    if (e.SocketError != SocketError.Success)
                    {
                        bSocketError = true;
                        textBlockLocSpc2.Text = gensets.SOCK_ERR + e.SocketError.ToString();
                    }
                    done.Set();
                });

                byte[] byteArr = gutil.StringToAscii(strToAprs);
                socketEventArg.SetBuffer(byteArr, 0, byteArr.Length);
                done.Reset();
                socketAPRS.SendAsync(socketEventArg);

                return done.WaitOne(gensets.APRS_SEND_TIMEOUT);
            }
            return false;
        }

    }
}

-------------------------------------------------------------------------------------------------------------------------------------------------
IsoStoHandler.cs

using System;
using System.Net;
using System.IO;
using System.IO.IsolatedStorage;
using System.Windows.Resources;
using System.Windows;

namespace wpAPRS
{
    public class IsoStoHandler
    {
        private const string FileName = "wpAPRS_settings.txt";
        private const string FolderName = "wpAPRS_app_data";
        private string FilePath = System.IO.Path.Combine(FolderName, FileName);

        private string[] isoStrData = new string[gensets.ISOSTR_DATA_ROWS];

        public void CreateDataFile(string[] strData)
        {
            CreateNewFile(FilePath, strData);
        }

        public bool ReadDataFile(out string s1, out string s2, out string s3, out string s4, out string s5, out string s6, out string s7, out string s8)
        {
            bool b = false;
            b = ReadFile(FilePath);
            s1 = isoStrData[0];
            s2 = isoStrData[1];
            s3 = isoStrData[2];
            s4 = isoStrData[3];
            s5 = isoStrData[4];
            s6 = isoStrData[5];
            s7 = isoStrData[6];
            s8 = isoStrData[7];
            return b;
        }

        public void removeIsoStr()
        {
            using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
            {
                store.Remove();
            }
        }

        private void CreateNewFile(string filePath, string[] fdata)
        {
            //StreamResourceInfo streamResourceInfo = Application.GetResourceStream(new Uri(filePath, UriKind.Relative));

            using (IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
            {
                string directoryName = System.IO.Path.GetDirectoryName(filePath);
                if (!string.IsNullOrEmpty(directoryName) && !myIsolatedStorage.DirectoryExists(directoryName))
                {
                    myIsolatedStorage.CreateDirectory(directoryName);
                }

                if (myIsolatedStorage.FileExists(filePath))
                {
                    myIsolatedStorage.DeleteFile(filePath);
                }

                using (IsolatedStorageFileStream fileStream = myIsolatedStorage.OpenFile(filePath, FileMode.Create, FileAccess.Write))
                {
                    using (StreamWriter writer = new StreamWriter(fileStream))
                    {
                        for (int i = 0; i < gensets.ISOSTR_DATA_ROWS; i++)
                        {
                            writer.WriteLine(fdata[i]);
                        }
                    }
                }
            }
        }

        private bool ReadFile(string filePath)
        {
            int i;
            for (i = 0; i < gensets.ISOSTR_DATA_ROWS; i++)
            {
                isoStrData[i] = string.Empty;
            }
            bool b = false;
            using (IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
            {
                if (myIsolatedStorage.FileExists(filePath))
                {
                    using (IsolatedStorageFileStream fileStream = myIsolatedStorage.OpenFile(filePath, FileMode.Open, FileAccess.Read))
                    {
                        using (StreamReader reader = new StreamReader(fileStream))
                        {
                            for (i = 0; i < gensets.ISOSTR_DATA_ROWS; i++)
                            {
                                isoStrData[i] = reader.ReadLine();
                            }
                            b = true;
                        }
                    }
                }
                //else
                //{
                //    MessageBox.Show(FilePath + " " + GenSets.ERR_FILE_NOT_FOUND);
                //}
            }
            return b;
        }

    }
}

-------------------------------------------------------------------------------------------------------------------------------------------------
gpsutil.cs
 

using System;
using System.Net;

namespace wpAPRS
{
    public class gpsutil
    {
        public string toAprsLat(double lat)
        {
            return toAprsLoc(lat, gensets.LOC_LAT, gensets.LOC_LAT_LEN);
        }

        public string toAprsLon(double lon)
        {
            return toAprsLoc(lon, gensets.LOC_LON, gensets.LOC_LON_LEN);
        }

        // Convert double decimal location to APRS (NMEA) type location

        private string toAprsLoc(double loc, string nsew, int len)
        {
            if (loc >= 0.0)
            {
                nsew = nsew.Substring(0, 1);
            }
            else
            {
                loc = loc * -1;
                nsew = nsew.Substring(1, 1);
            }

            loc += 0.00005;
            int locint = Convert.ToInt32(loc);

            double locdec = loc - locint;
            loc = (locint) * 100 + locdec * 60.0;

            string locstr = Convert.ToString(loc) + "0";
            int i = locstr.IndexOf(".");
            locstr = locstr.Substring(0, i + 3) + nsew;

            if (locstr.Length < len)
            {
                locstr = "0000".Substring(0, len - locstr.Length) + locstr;
            }
            return locstr;
        }

        public string fmtnum(double d)
        {
            d = Math.Floor(d + 0.5);
            string s = Convert.ToString(d);
            int l = s.Length;

            if (l >= 0 && l < 4 && d >= 0)
            {
                s = "000".Substring(0, 3 - l) + s;
            }
            else
            {
                s = "---";
            }
            return s;
        }

        public byte[] StringToAscii(string s)
        {
            byte[] retval = new byte[s.Length];
            for (int ix = 0; ix < s.Length; ++ix)
            {
                char ch = s[ix];
                if (ch <= 0x7f) retval[ix] = (byte)ch;
                else retval[ix] = (byte)'?';
            }
            return retval;
        }

    }
}

-------------------------------------------------------------------------------------------------------------------------------------------------
gensets.cs

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace wpAPRS
{
    public static class gensets
    {
        public const bool TEST_MODE = true; // when building for real device change gensets.TEST_MODE value to false
        public const double GEO_TIMEOUT = 2000; //ms
        public const string GEO_ERR_TIMEOUT = "GeoCoordinateWatcher timed out on start";
        public const int APRS_CONNECT_TIMEOUT = 30000; //ms
        public const int APRS_SEND_TIMEOUT = 30000; //ms

        public const string APRS_MSG_SEND_USER = "user ";
        public const string APRS_MSG_SEND_PASS = " pass ";
        public const string APRS_MSG_SEND_SW = " vers wpAPRS 1.0";

        public const string APRS_MSG_SEND_LOC = ">APRS,TCPIP*:!";

        public const string APRS_SERVER_CONN = "Connected to APRS-IS server";
        public const string APRS_SERVER_NO_CONN = "No connection";
        public const string APRS_SERVER_CONN_ERR_TIMEOUT = "Connection timeout error";
        public const string APRS_SERVER_SEND_ERR_TIMEOUT = "Send to APRS-IS server timeout";
        public const string APRS_NO_SETTINGS = "No APRS-IS settings found. Application merely shows GPS data";

        public const string SOCK_ERR = "Socket error: ";

        public const string APP_TITLE = "APRS";
        public const string APP_CAP = "wpAPRS";
        public const string APP_VERSION = "Version 1.0";
        public const string APP_COPYRIGHT = "(c)";
        public const string DEV_FW = "Firmware version ";
        public const string DEV_HW = "Hardware version ";
        public const string DEV_NAME = "Name: ";
        public const string DEV_MANU = "Manufacturer: ";
        public const string DEV_CLR = "CLR version ";
        public const string DEV_BUILD = " build ";
        public const string DEV_REV = " revision ";
        public const string LOC_LAT = "NS";
        public const string LOC_LON = "EW";
        public const int LOC_LAT_LEN = 8;
        public const int LOC_LON_LEN = 9;
        public const string LOC_SPEED = " m/s";
        public const string LOC_SPEED2 = " km/h ";
        public const string LOC_ALT = " m";
        public const string LOC_HACC = "Horiz. acc. ";
        public const string LOC_VACC = "Vert. acc. ";
        public const string LOC_ALT_TXT = "Alt. ";
        public const string LOC_COURSE = "°";
        public const string CONF_START = "Start";
        public const string CONF_STOP = "Stop";
        public const string ERR_FLD_MAND = "Mandatory field value is missing";
        public const string ERR_NO_TCP_PORT = "No TCP port number in APRS-IS server address";
        public const string ERR_TCP_PORT_NOT_NUMERIC = "TCP port is not numeric";
        public const string ERR_NO_ADDRESS = "Invalid APRS-IS server";
        public const string DATA_SAVED = "Configuration settings saved";

        public const int ISOSTR_DATA_ROWS = 8;
        public const double POLL_INT = 5;
    }
}

------------------------------------------------------------------------------------------------------------------------------------

MainPage.xaml
 

<phone:PhoneApplicationPage
    x:Class="wpAPRS.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:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="PortraitOrLandscape"  Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <!--Pivot Control-->
        <controls:Pivot Title="APRS" Name="pivotMain">
            <!--Pivot item one-->
            <controls:PivotItem Header="Location">
                <!--Double line list with text wrapping-->
                <StackPanel Height="560" HorizontalAlignment="Left" Margin="10,10,2,2" Name="stackPanelLoc" VerticalAlignment="Top" Width="430">
                    <TextBlock Height="Auto" Name="textBlockLocLat" Text="lat" HorizontalAlignment="Left" FontSize="40" />
                    <TextBlock Height="Auto" Name="textBlockLocLon" Text="lon" HorizontalAlignment="Left" FontSize="40" />
                    <TextBlock Height="Auto" Name="textBlockLocSpeed" Text="spd" HorizontalAlignment="Left" FontSize="32" />
                    <TextBlock Height="Auto" Name="textBlockLocCourse" Text="course" HorizontalAlignment="Left" FontSize="32" />
                    <TextBlock Height="Auto" Name="textBlockLocAlt" Text="alt" HorizontalAlignment="Left" FontSize="32" />
                    <TextBlock Height="Auto" Name="textBlockLocHacc" Text="-" HorizontalAlignment="Left" FontSize="32" />
                    <TextBlock Height="Auto" Name="textBlockLocTs" Text="-" FontSize="24" />
                    <TextBlock Height="Auto" Name="textBlockLocSpc2" Text="-" HorizontalAlignment="Left" FontSize="24" TextWrapping="Wrap" />
                    <TextBlock Height="Auto" Name="textBlockLocSpc" Text="-" HorizontalAlignment="Left" FontSize="24" />
                    <Button Content="Start" Height="72" Name="buttonLocStartStop" Width="160" HorizontalAlignment="Center" Click="buttonLocStartStop_Click" />
                </StackPanel>
            </controls:PivotItem>
 
            <!--Pivot item two-->
            <controls:PivotItem Header="Configuration" Height="Auto">
                <!--Triple line list no text wrapping-->
                <StackPanel Height="590" HorizontalAlignment="Left" Margin="2,2,2,2" Name="stackPanelConf" VerticalAlignment="Top" Width="448">
                    <TextBlock Height="30" Name="textBlockConfCallsign" Text="Callsign (*)" HorizontalAlignment="Left" />
                    <TextBox Height="72" Name="textBoxConfCallsign" Text="" Width="256" HorizontalAlignment="Left" MaxLength="32" FontSize="25.333" TextChanged="textBoxConfCallsign_TextChanged" />
                    <TextBlock Height="30" Name="textBlock3" Text="APRS-IS server passcode (*)" HorizontalAlignment="Left" />
                    <TextBox Height="72" Name="textBoxConfAPRSpasscode" Text="" InputScope="Number" Width="256" HorizontalAlignment="Left" MaxLength="24" FontSize="25.333" />
                    <TextBlock Height="30" Name="textBlock4" Text="APRS-IS server IP address:port number (*)" HorizontalAlignment="Left" />
                    <TextBox Height="72" Name="textBoxConfAPRS" Text="" InputScope="Url" Width="440" MaxLength="256" HorizontalAlignment="Left" FontSize="25.333" TextWrapping="Wrap" />
                    <TextBlock Height="30" Name="textBlock5" Text="Name/comment" HorizontalAlignment="Left" />
                    <TextBox Height="72" Name="textBoxConfName" Text="" Width="440" HorizontalAlignment="Left" MaxLength="48" TextWrapping="Wrap" FontSize="25.333" />
                    <TextBlock Height="30" Name="textBlockConfPollingInt" Text="Polling interval (5 minutes)" HorizontalAlignment="Left" />
                    <Slider Height="86" Name="sliderConfPollingInt" Width="440" Minimum="1" Maximum="60" SmallChange="1" HorizontalAlignment="Left" Value="5" ValueChanged="slider1_ValueChanged" FontSize="20" LargeChange="5" />
                    <Button Content="Save" Height="72" Name="buttonConfSave" Width="160" Click="buttonConfSave_Click" />
                </StackPanel>
            </controls:PivotItem>

            <!--Pivot item three-->
            <controls:PivotItem Header="About">
                <StackPanel Height="500" HorizontalAlignment="Left" Margin="10,10,10,10" Name="stackPanelAbout" VerticalAlignment="Top" Width="430">
                    <TextBlock Height="Auto" Name="textBlockAboutCap" Text="APRS for Windows Phone" HorizontalAlignment="Left" FontSize="32" />
                    <TextBlock Height="Auto" Name="textBlockAboutVer" Text="Version 1.0" FontSize="32" HorizontalAlignment="Left" />
                    <TextBlock Height="Auto" Name="textBlockAboutC" Text="(c) " HorizontalAlignment="Left" FontSize="32" TextWrapping="Wrap" />
                    <TextBlock Height="30" Name="textBlock2" Text="    " HorizontalAlignment="Left" />
                    <TextBlock Height="30" Name="textBlock1" Text="Device information" HorizontalAlignment="Left" />
                    <TextBlock Height="30" Name="textBlockAboutOSversion" Text="os version" />
                    <TextBlock Height="30" Name="textBlockAboutCLR" Text="clr" HorizontalAlignment="Left" />
                    <TextBlock Height="30" Name="textBlockAboutFirmware" Text="fw" HorizontalAlignment="Left" />
                    <TextBlock Height="30" Name="textBlockAboutHW" Text="hw" HorizontalAlignment="Left" />
                    <TextBlock Height="30" Name="textBlockAboutDevName" Text="name" HorizontalAlignment="Left" />
                    <TextBlock Height="30" Name="textBlockAboutManu" Text="manu" HorizontalAlignment="Left" />
                </StackPanel>
            </controls:PivotItem>

            <!--Pivot item four-->
            <controls:PivotItem Header="APRS symbols">
                <StackPanel Height="500" HorizontalAlignment="Left" Margin="10,10,10,10" Name="stackPanelSymbols" VerticalAlignment="Top" Width="430">
                    <Image Height="450" Name="image1" Stretch="Fill" Width="404" Source="/wpAPRS;component/Images/aprsimg.jpg" />
                </StackPanel>
            </controls:PivotItem>

        </controls:Pivot>
    </Grid>
 
    <!--Sample code showing usage of ApplicationBar-->
    <!--<phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <shell:ApplicationBarIconButton IconUri="appbar.upload.rest.png" Text="Start" x:Name="iconButtonStart" />
            <shell:ApplicationBarIconButton IconUri="appbar.stop.rest.png" Text="Stop" x:Name="iconButtonStop" />
            <shell:ApplicationBar.MenuItems>
                <shell:ApplicationBarMenuItem Text="Get APRS-IS passcode" x:Name="getty" />
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>-->

</phone:PhoneApplicationPage>