322 lines
15 KiB
C#
Raw Normal View History

2025-10-11 18:25:59 +08:00
using GraduatedCylinder;
using GraduatedCylinder.Geo;
using GraduatedCylinder.Geo.Gps;
using GraduatedCylinder.Geo.Gps.Nmea;
using Nmea.Core0183;
using Shouldly;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
namespace XiaoZhiSharp.Kernels.Gis
{
/* --NMEA0183协议--RMC,RMC,GGA,GSA,GSV,PVTG,GLL代表意义--
|----------------------------------------------------------------------------------------------------|
|NMEA0183 |
|----------------------------------------------------------------------------------------------------|
| | | |
|RMC | Recommended Minimum Specific GNSS Data | |
| | | - |
| | | - |
|---------|-------------------------------------------|----------------------------------------------|
|GGA | Global Positioning System Fix Data | GPS定位信息 |
| | | - |
| | | - GPS质量指示|
|---------|-------------------------------------------|----------------------------------------------|
|GSA | GNSS DOP and Active Satellites | |
| | | - 使(DOP) |
| | | - 2D/3DPDOPHDOPVDOP值 |
|---------|-------------------------------------------|----------------------------------------------|
|GSV | GNSS Satellites in View | |
| | | - |
| | | - (SNR) |
|---------|-------------------------------------------|----------------------------------------------|
|VTG | Course Over Ground and Ground Speed | |
| | | - |
| | | - / |
|---------|-------------------------------------------|----------------------------------------------|
|GLL | Geographic Position - Latitude/Longitude | |
| | | - |
| | | - / |
|----------------------------------------------------------------------------------------------------|
*/
public class SharpGISNmeaParser
{
/// <summary>
/// 实时GPS数据采集方法 - 从COM4串口读取GPS数据并记录到日志文件
/// </summary>
public static void LiveGpsOnCOM4()
{
// GPS日志文件路径
string fileName = @"C:\Gps\Test.glog";
// 如果日志文件已存在,则删除旧文件以确保重新开始记录
if (File.Exists(fileName))
{
File.Delete(fileName);
}
// 创建NMEA串口数据提供者指定COM4端口
// NMEA (National Marine Electronics Association) 0183是GPS设备通信标准协议
IProvideSentences sentences = new NmeaSerialPort(
portName: "COM4",
baudRate: 4800,
parity: System.IO.Ports.Parity.None,
dataBits: 8,
stopBits: System.IO.Ports.StopBits.One);
// 包装数据提供者添加日志记录功能所有接收到的GPS语句将被记录到指定文件
sentences = new SentenceLogger(sentences, fileName);
// 创建GPS单元实例用于解析NMEA语句并生成GPS数据事件
GpsUnit gps = new(sentences);
// 注册位置变化事件处理程序
// 当GPS设备报告新的位置数据时触发此事件
gps.LocationChanged += Gps_LocationChanged;
// 启用GPS数据采集开始监听串口数据并触发事件
gps.IsEnabled = true;
// 数据采集循环 - 保持GPS运行10秒100次 × 100毫秒
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100); // 每次休眠100毫秒
}
// 禁用GPS数据采集停止监听数据
gps.IsEnabled = false;
// 验证日志文件是否成功创建
// 如果文件不存在说明GPS数据记录过程中出现问题
if (!File.Exists(fileName))
{
throw new FileNotFoundException("Expected test to create a log file.", fileName);
}
}
private static void Gps_LocationChanged(LocationChangedEventArgs args)
{
// 记录当前系统时间和GPS消息时间戳
Trace.TraceInformation("Received: {0:u}: Message Timestamp: {1:u}", DateTime.Now, args.Time);
// 输出位置信息纬度、经度、海拔高度转换为英尺显示保留1位小数
Trace.TraceInformation(" Lat: {0} Long: {1} Alt: {2}",
args.Position.Latitude, // 纬度坐标
args.Position.Longitude, // 经度坐标
args.Position.Altitude.ToString(LengthUnit.Foot, 1)); // 海拔高度,单位:英尺
// 输出航向和速度信息
Trace.TraceInformation(" Heading: {0} at Speed: {1}",
args.Heading, // 航向角度
args.Speed.ToString(SpeedUnit.MilesPerHour)); // 速度,单位:英里/小时
Trace.TraceInformation(""); // 输出空行分隔不同的位置更新
}
/// <summary>
/// 以最快速度回放GPS日志文件用于测试GPS数据处理性能和事件触发机制
/// 该方法会模拟GPS数据播放验证在高速数据输入情况下的处理能力
/// </summary>
public static void PlaybackLogAsFastAsPossible()
{
// 记录接收到的事件数量
int eventCount = 0;
// 指定要回放的GPS日志文件路径
// @前缀表示逐字字符串,避免转义字符处理
string fileName = @".\Configs\Sample1.gpslog";
// 创建SentenceLog实例用于解析和回放GPS日志文件
// PlaybackRate.AsFastAsPossible表示以最大速度回放不按实际时间间隔
SentenceLog sentences = new(fileName, SentenceLog.PlaybackRate.AsFastAsPossible);
sentences.SentenceReceived += Sentences_SentenceReceived;
// 创建GPS单元实例传入日志回放器作为数据源
GpsUnit gps = new(sentences);
// 注册位置改变事件处理程序
// 使用lambda表达式每次事件触发时增加事件计数器
// _表示忽略事件参数因为我们只需要计数
gps.LocationChanged += _ => eventCount++;
gps.LocationChanged += Gps_LocationChanged;
// 记录测试开始时间
DateTime startTime = DateTime.Now;
// 启用GPS单元开始接收和处理数据
// 这将启动数据流并开始触发LocationChanged事件
gps.IsEnabled = true;
// 循环等待直到日志回放完成
// PlaybackComplete属性指示是否所有日志数据都已处理完毕
while (!sentences.PlaybackComplete)
{
// 每次循环暂停50毫秒避免过度占用CPU资源
// 这给了系统处理事件和更新状态的时间
Thread.Sleep(50);
}
// 回放完成后禁用GPS单元停止数据处理
gps.IsEnabled = false;
// 计算整个回放过程的持续时间
TimeSpan duration = DateTime.Now - startTime;
// 断言验证期望接收到18个位置改变事件
// 这是基于测试数据文件的预期行为验证
eventCount.ShouldBe(18);
// 断言验证整个回放过程应该在1秒内完成
// 这确保了"最快速度"回放的性能要求
duration.ShouldBeLessThan(new TimeSpan(0, 0, 1));
}
private static readonly GpsParser _parser = new();
private static readonly List<int> _activeSatellitePrns = new();
private static readonly IProvideSentences _nmeaProvider;
private static readonly Dictionary<int, SatelliteInfo> _satellites = new();
public static GpsFixType CurrentFixType { get; private set; }
public static GeoPosition CurrentLocation { get; private set; }
public static double PositionDop { get; private set; }
public static IEnumerable<SatelliteInfo> Satellites => _satellites.Where(skv => _activeSatellitePrns.Contains(skv.Key)).Select(skv => skv.Value);
public static double VerticalDop { get; private set; }
public static DateTimeOffset CurrentTime { get; private set; }
public static bool HasLocation { get; private set; }
public static Heading CurrentHeading { get; private set; }
public static double HorizontalDop { get; private set; }
public static Speed MinimumSpeedForHeadingUpdate { get; set; }
public static Speed CurrentSpeed { get; private set; }
private static void Sentences_SentenceReceived(Sentence st)
{
Message? message = _parser.Parse(st);
if (message == null)
{
return;
}
//NB set all values then raise all notifications
if (message.Value is IProvideSatelliteInfo info)
{
foreach (SatelliteInfo satellite in info.Satellites)
{
_satellites[satellite.Prn] = satellite;
}
}
if (message.Value is IProvideActiveSatellites active)
{
_activeSatellitePrns.Clear();
foreach (int prn in active.ActiveSatellitePrns.Where(prn => prn != 0))
{
_activeSatellitePrns.Add(prn);
}
}
if (message.Value is IProvideFixType fixType)
{
CurrentFixType = fixType.CurrentFix;
}
if (message.Value is IProvideDilutionOfPrecision dop)
{
PositionDop = dop.PositionDop;
HorizontalDop = dop.HorizontalDop;
VerticalDop = dop.VerticalDop;
}
if (message.Value is IProvideTime time)
{
CurrentTime = time.CurrentTime;
}
if (message.Value is IProvideTrajectory trajectory)
{
//NB heading and speed are correlated
CurrentSpeed = trajectory.CurrentSpeed;
// NB don't update heading when speed is near zero
if (CurrentSpeed > MinimumSpeedForHeadingUpdate)
{
CurrentHeading = trajectory.CurrentHeading;
}
}
if (message.Value is IProvideGeoPosition position)
{
GeoPosition newLocation = position.CurrentLocation;
CurrentLocation =
newLocation.Altitude == Length.Unknown ?
new GeoPosition(newLocation.Latitude,
newLocation.Longitude,
CurrentLocation.Altitude) :
newLocation;
HasLocation = true;
}
var sats = Satellites;
var satelliteType = AnalysisSatelliteType(message.Sentence.Id);
Console.WriteLine($"NMEA句子标识符{message.Sentence.Id}");
Console.WriteLine($"卫星类型:{satelliteType},卫星数量:{_satellites.Count},使用卫星数量;{sats.Count()}");
}
/// <summary>
/// 根据NMEA句子标识符分析卫星类型
/// </summary>
/// <param name="sentenceId">NMEA句子标识符</param>
/// <returns></returns>
private static SatelliteType AnalysisSatelliteType(string sentenceId)
{
SatelliteType satelliteType = SatelliteType.GPS;
switch (sentenceId)
{
case "$GP":
satelliteType = SatelliteType.GPS;
break;
case "$GL":
satelliteType = SatelliteType.GLONASS;
break;
case "$GA":
satelliteType = SatelliteType.Galileo;
break;
case "$GN":
satelliteType = SatelliteType.GNSS;
break;
case "$BD":
satelliteType = SatelliteType.BDS;
break;
}
return satelliteType;
}
}
public enum SatelliteType
{
/// <summary>
/// Global Positioning System全球定位系统
/// </summary>
GPS,
/// <summary>
/// Global Navigation Satellite System全球导航卫星系统
/// </summary>
GLONASS,
/// <summary>
/// European Global Satellite Navigation System伽利略卫星导航系统
/// </summary>
Galileo,
/// <summary>
/// Combined GPS, GLONASS, Galileo, etc.(组合导航系统)
/// </summary>
GNSS,
/// <summary>
/// BeiDou Navigation Satellite System北斗卫星导航系统
/// </summary>
BDS
}
}