Add early beginnings of NTRIP tab

This commit is contained in:
Morten Nielsen 2020-07-28 21:07:53 -07:00
parent 9db4db4212
commit b035adda26
9 changed files with 478 additions and 2 deletions

View file

@ -75,7 +75,7 @@
<TextBlock Text="Serial port:" />
<ComboBox x:Name="serialPorts" />
<TextBlock Text="Baud rate:" />
<ComboBox x:Name="baudRates" SelectedIndex="3">
<ComboBox x:Name="baudRates" SelectedIndex="5">
<ComboBoxItem>1200</ComboBoxItem>
<ComboBoxItem>2400</ComboBoxItem>
<ComboBoxItem>4800</ComboBoxItem>
@ -91,6 +91,9 @@
</StackPanel>
</Grid>
</TabItem>
<TabItem Header="NTRIP">
<local:NtripView />
</TabItem>
</TabControl>
<Grid.ColumnDefinitions>

View file

@ -14,7 +14,7 @@ namespace SampleApp.WinDesktop
public partial class MainWindow : Window
{
private Queue<string> messages = new Queue<string>(101);
private NmeaParser.NmeaDevice currentDevice;
public static NmeaParser.NmeaDevice currentDevice;
//Dialog for browsing to nmea log files
private Microsoft.Win32.OpenFileDialog nmeaOpenFileDialog = new Microsoft.Win32.OpenFileDialog()
{

View file

@ -0,0 +1,23 @@
// *******************************************************************************
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// ******************************************************************************
namespace NmeaParser.Gnss.Ntrip
{
public enum Carrier : int
{
No = 0,
L1 = 1,
L1L2 = 2
}
}

View file

@ -0,0 +1,59 @@
// *******************************************************************************
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// ******************************************************************************
using System.Globalization;
using System.Net;
namespace NmeaParser.Gnss.Ntrip
{
public class Caster : NtripSource
{
internal Caster (string[] d)
{
var a = d[1].Split(':');
Address = IPAddress.Parse(a[0]);
Port = int.Parse(a[1]);
Identifier = d[3];
Operator = d[4];
SupportsNmea = d[5] == "1";
CountryCode = d[6];
Latitude = double.Parse(d[7], CultureInfo.InvariantCulture);
Longitude = double.Parse(d[8], CultureInfo.InvariantCulture);
FallbackAddress = IPAddress.Parse(d[9]);
}
public Caster(IPAddress address, int port, string identifier, string _operator, bool supportsNmea, string countryCode, double latitude, double longitude, IPAddress fallbackkAddress)
{
Address = address;
Port = port;
Identifier = identifier;
Operator = _operator;
SupportsNmea = supportsNmea;
CountryCode = countryCode;
Latitude = latitude;
Longitude = longitude;
FallbackAddress = fallbackkAddress;
}
public IPAddress Address { get; }
public int Port { get; }
public string Identifier { get; }
public string Operator { get; }
public bool SupportsNmea { get; }
public string CountryCode { get; }
public double Latitude { get; }
public double Longitude { get; }
public IPAddress FallbackAddress { get; }
}
}

View file

@ -0,0 +1,140 @@
// *******************************************************************************
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// ******************************************************************************
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace NmeaParser.Gnss.Ntrip
{
public class Client : IDisposable
{
private readonly string _host;
private readonly int _port;
private string? _auth;
private Socket? sckt;
private bool connected;
private Task? runningTask;
public Client(string host, int port)
{
_host = host;
_port = port;
}
public Client(string host, int port, string username, string password) : this(host, port)
{
_auth = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(username + ":" + password));
}
public IEnumerable<NtripSource> GetSourceTable()
{
string data = "";
byte[] buffer = new byte[1024];
using (var sck = Request(""))
{
int count;
while ((count = sck.Receive(buffer)) > 0)
{
data += System.Text.Encoding.UTF8.GetString(buffer, 0, count);
}
}
var lines = data.Split('\n');
List<NtripSource> sources = new List<NtripSource>();
foreach (var item in lines)
{
var d = item.Split(';');
if (d.Length == 0) continue;
if (d[0] == "ENDSOURCETABLE")
break;
if (d[0] == "CAS")
{
sources.Add(new Caster(d));
}
else if (d[0] == "STR")
{
sources.Add(new NtripStream(d));
}
}
return sources;
}
private Socket Request(string path)
{
var sckt = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sckt.Blocking = true;
sckt.Connect(_host, _port);
string msg = $"GET /{path} HTTP/1.1\r\n";
msg += "User-Agent: NTRIP ntripclient\r\n";
if (_auth != null)
{
msg += "Authorization: Basic " + _auth + "\r\n";
}
msg += "Accept: */*\r\nConnection: close\r\n";
msg += "\r\n";
byte[] data = System.Text.Encoding.ASCII.GetBytes(msg);
sckt.Send(data);
return sckt;
}
public void Connect(string strName)
{
if (sckt != null) throw new Exception("Connection already open");
sckt = Request(strName);
connected = true;
runningTask = Task.Run(ReceiveThread);
}
private async Task ReceiveThread()
{
byte[] buffer = new byte[65536];
while (connected && sckt != null)
{
int count = sckt.Receive(buffer);
if (count > 0)
{
DataReceived?.Invoke(this, buffer.Take(count).ToArray());
}
await Task.Delay(10);
}
sckt?.Shutdown(SocketShutdown.Both);
sckt?.Dispose();
sckt = null;
}
public Task CloseAsync()
{
if (runningTask != null)
{
connected = false;
var t = runningTask;
runningTask = null;
return t;
}
return Task.CompletedTask;
}
public void Dispose()
{
_ = CloseAsync();
}
public event EventHandler<byte[]>? DataReceived;
}
}

View file

@ -0,0 +1,23 @@
// *******************************************************************************
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// ******************************************************************************
namespace NmeaParser.Gnss.Ntrip
{
public class NtripSource
{
protected NtripSource()
{
}
}
}

View file

@ -0,0 +1,52 @@
// *******************************************************************************
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// ******************************************************************************
using System;
using System.Globalization;
namespace NmeaParser.Gnss.Ntrip
{
public class NtripStream : NtripSource
{
internal NtripStream(string[] d)
{
Mountpoint = d[1];
Identifier = d[2];
Format = d[3];
FormatDetails = d[4];
if (int.TryParse(d[5], out int carrier))
Carrier = (Carrier)carrier;
else
{
}
Network = d[7];
CountryCode = d[8];
Latitude = double.Parse(d[9], CultureInfo.InvariantCulture);
Longitude = double.Parse(d[10], CultureInfo.InvariantCulture);
SupportsNmea = d[11] == "1";
}
public string Mountpoint { get; }
public string Identifier { get; }
public string Format { get; }
public string FormatDetails { get; }
public Carrier Carrier { get; }
public string Network { get; }
public string CountryCode { get; }
public double Latitude { get; }
public double Longitude { get; }
public bool SupportsNmea { get; }
}
}

View file

@ -0,0 +1,74 @@
<UserControl x:Class="SampleApp.WinDesktop.NtripView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SampleApp.WinDesktop"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="80" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Text="Server: " />
<TextBox Text="esricaster.esri.com" Grid.Column="1" x:Name="host" />
<TextBlock Text="Port: " Grid.Row="1" />
<TextBox Text="2101" Grid.Row="1" Grid.Column="1" x:Name="port" />
<TextBlock Text="Username: " Grid.Row="2" />
<TextBox Text="" Grid.Row="2" Grid.Column="1" x:Name="username" />
<TextBlock Text="Password: " Grid.Row="3" />
<PasswordBox Grid.Row="3" Grid.Column="1" x:Name="password" />
<Button Content="Get Available Streams" Grid.Row="4" Grid.ColumnSpan="2" HorizontalAlignment="Left" Margin="5" Click="Button_Click"/>
<TextBlock Text="Available streams:" Grid.Row="5" Grid.ColumnSpan="2" />
<ListView x:Name="sourceList" Grid.Row="6" >
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Identifier}" FontWeight="Bold" />
<TextBlock Text=" (" />
<TextBlock Text="{Binding CountryCode}" FontWeight="Bold" />
<TextBlock Text=")" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Format}" />
<TextBlock Text=" - " />
<TextBlock Text="{Binding Carrier}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Grid Grid.Row="6" Grid.Column="1" DataContext="{Binding SelectedItem, ElementName=sourceList}">
<StackPanel>
<TextBlock Text="{Binding Identifier}" FontWeight="Bold" FontSize="14" />
<TextBlock Text="{Binding CountryCode, StringFormat='Country code: {0}'}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Location: " />
<TextBlock Text="{Binding Latitude}" />
<TextBlock Text="," />
<TextBlock Text="{Binding Longitude}" />
</StackPanel>
<TextBlock Text="{Binding Carrier, StringFormat='Carrier: {0}'}" />
<TextBlock Text="{Binding Format, StringFormat='Format: {0}'}" />
<TextBlock Text="{Binding FormatDetails, StringFormat='Format details: {0}'}" />
<TextBlock Text="{Binding Mountpoint, StringFormat='Mount point: {0}'}" />
<TextBlock Text="{Binding Network, StringFormat='Network: {0}'}" />
<TextBlock Text="{Binding SupportsNmea, StringFormat='Supports NMEA: {0}'}" />
<Button Content="Connect" Click="Connect_Click" />
</StackPanel>
</Grid>
<TextBlock Text="Not connected" x:Name="ntripstatus" Grid.Column="1" Grid.Row="9" />
</Grid>
</UserControl>

View file

@ -0,0 +1,102 @@
using NmeaParser.Gnss.Ntrip;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace SampleApp.WinDesktop
{
/// <summary>
/// Interaction logic for NtripView.xaml
/// </summary>
public partial class NtripView : UserControl
{
public NtripView()
{
InitializeComponent();
}
NmeaParser.Gnss.Ntrip.Client client;
private void Button_Click(object sender, RoutedEventArgs e)
{
sourceList.ItemsSource = null;
if (!int.TryParse(port.Text, out int portNumber))
{
MessageBox.Show("Invalid port number");
return;
}
client = new NmeaParser.Gnss.Ntrip.Client(host.Text, portNumber, username.Text, password.Password);
client.DataReceived += Client_DataReceived;
List<NtripStream> sources;
try
{
sources = client.GetSourceTable().OfType<NtripStream>().ToList();
}
catch(System.Exception ex)
{
MessageBox.Show("Failed to connect: " + ex.Message);
return;
}
sourceList.ItemsSource = sources.OrderBy(s=>s.CountryCode);
}
Func<Task> stop;
private async void Connect_Click(object sender, RoutedEventArgs e)
{
var stream = ((Button)sender).DataContext as NtripStream;
if (stream == null)
return;
if (stop != null)
{
await stop();
}
counter = 0;
client.Connect(stream.Mountpoint);
stop = client.CloseAsync;
ntripstatus.Text = $"Connected";
}
System.Threading.Tasks.Task writingTask;
object writeLock = new object();
long counter = 0;
private async void Client_DataReceived(object sender, byte[] rtcm)
{
var device = MainWindow.currentDevice;
if (device != null && device.CanWrite)
{
try
{
//lock (writeLock)
{
await device.WriteAsync(rtcm, 0, rtcm.Length);
counter += rtcm.Length;
Dispatcher.Invoke(() =>
{
ntripstatus.Text = $"Transmitted {counter} bytes";
});
}
}
catch
{
}
}
//ParseData(rtcm);
}
Queue<byte> rtcmData = new Queue<byte>();
private void ParseData(byte[] rtcm)
{
foreach (var b in rtcm)
rtcmData.Enqueue(b);
}
}
}