FSFlightLogger/FSFlightLoggerCmd/Program.cs

219 lines
7.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommandLine;
using FlightLoggerLib;
using FlightLoggerLib.Concrete;
using SimConnect;
using SimConnect.Attribute;
using SimConnect.Concrete;
using SimConnect.Lib;
namespace FSFlightLoggerCmd
{
// TODO verb for converting CSV to KML
// TODO with the introduction of the GUI and SimConnectLogger, either port to SimConnectLogger for a single code base or remove this project
public class PositionData
{
[SimConnectVariable("PLANE LATITUDE", "degrees")]
public float Latitude;
[SimConnectVariable("PLANE LONGITUDE", "degrees")]
public float Longitude;
[SimConnectVariable("PLANE ALTITUDE", "feet")]
public float Altitude;
[SimConnectVariable("AIRSPEED INDICATED", "knots")]
public float Airspeed;
}
public class Program
{
private enum OutputFormat
{
None,
CSV,
KML
};
// ReSharper disable once ClassNeverInstantiated.Local - used by CommandLineParser
// ReSharper disable UnusedAutoPropertyAccessor.Local - used by CommandLineParser
private class Options
{
[Option('o', "outputPath", Required = false, HelpText = "Specifies the output path for the log files. Defaults to a 'Flight logs' folder on the desktop.")]
public string OutputPath { get; set; }
[Option('i', "interval", Required = false, Default = 1, HelpText = "The minimum time, in seconds, between log entries.")]
public int IntervalTime { get; set; }
[Option('d', "distance", Required = false, Default = 1, HelpText = "The minimum distance, in meters, between log entries.")]
public int IntervalDistance { get; set; }
[Option('v', "verbose", Required = false, HelpText = "Enable verbose logging.")]
public bool Verbose { get; set; }
[Option('f', "format", Required = false, Default = OutputFormat.CSV, HelpText = "The output format to use. Possible values: CSV, KML.")]
public OutputFormat OutputFormat { get; set; }
[Option('s', "format2", Required = false, Default = OutputFormat.None, HelpText = "The secondary output format to use. Possible values: None, CSV, KML.")]
public OutputFormat OutputFormat2 { get; set; }
}
// ReSharper restore UnusedAutoPropertyAccessor.Local
public static void Main(string[] args)
{
var parser = new Parser(settings =>
{
settings.CaseInsensitiveEnumValues = true;
});
parser.ParseArguments<Options>(args)
.WithParsed(Run);
}
private static void Run(Options o)
{
var intervalTime = TimeSpan.FromSeconds(o.IntervalTime);
void VerboseLog(string message)
{
if (o.Verbose)
Console.WriteLine(message);
}
var factory = new SimConnectClientFactory();
var client = TryConnect(factory).Result;
var outputPath = o.OutputPath;
if (string.IsNullOrEmpty(outputPath))
outputPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "Flight logs");
Directory.CreateDirectory(outputPath);
var loggers = new List<IFlightLogger>();
AddLogger(loggers, o.OutputFormat, outputPath);
if (o.OutputFormat2 != o.OutputFormat)
AddLogger(loggers, o.OutputFormat2, outputPath);
var lastTime = DateTime.MinValue;
var lastData = new PositionData
{
Latitude = 0,
Longitude = 0,
Altitude = 0,
Airspeed = 0
};
client.AddDefinition<PositionData>(async data =>
{
// TODO take vertical position into account when going straight up or down (not a common use case, but still)
var distanceMeters = LatLon.DistanceBetweenInMeters(lastData.Latitude, lastData.Longitude, data.Latitude, data.Longitude);
if (distanceMeters < o.IntervalDistance)
{
if (data.Airspeed < 0.1)
data.Airspeed = 0;
// Make an exception if we were last moving and have now stopped, so the 0 velocity record is logged as well
if (data.Airspeed > 0 || lastData.Airspeed == 0)
return;
}
var now = DateTime.Now;
var time = now - lastTime;
if (time < intervalTime)
return;
VerboseLog("Logging position, elapsed time: " + time.TotalSeconds + ", distance: " + distanceMeters);
lastTime = now;
lastData = data;
// ReSharper disable once AccessToDisposedClosure - covered by disposing the client first
await Task.WhenAll(loggers.Select(logger =>
logger.LogPosition(now, new FlightPosition
{
Latitude = data.Latitude,
Longitude = data.Longitude,
Altitude = data.Altitude,
Airspeed = data.Airspeed
})));
});
var stopEvent = new ManualResetEventSlim(false);
Console.CancelKeyPress += (sender, args) =>
{
stopEvent.Set();
args.Cancel = true;
};
Console.WriteLine("Flight log active, press Ctrl-C to stop");
Console.WriteLine("Output path: " + outputPath);
stopEvent.Wait(Timeout.Infinite);
Console.WriteLine("Closing...");
client.DisposeAsync();
loggers.ForEach(logger => logger.DisposeAsync());
if (!Debugger.IsAttached)
return;
Console.WriteLine("Press any Enter key to continue");
Console.ReadLine();
}
private static void AddLogger(ICollection<IFlightLogger> loggers, OutputFormat outputFormat, string outputPath)
{
switch (outputFormat)
{
case OutputFormat.CSV:
loggers.Add(new CSVFlightLogger(outputPath));
break;
case OutputFormat.KML:
loggers.Add(new KMLFlightLogger(outputPath, TimeSpan.FromSeconds(5)));
break;
case OutputFormat.None:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private static async Task<ISimConnectClient> TryConnect(ISimConnectClientFactory factory)
{
while (true)
{
Console.WriteLine("Attempting to connect to SimConnect...");
var client = await factory.TryConnect("FS Flight Logger");
if (client != null)
{
Console.WriteLine("Success!");
return client;
}
Console.WriteLine("Failed to connect, retrying in 5 seconds");
await Task.Delay(TimeSpan.FromSeconds(5));
}
}
}
}