336 lines
11 KiB
C#
336 lines
11 KiB
C#
// **********************************************************************************************************
|
|
// TelemetryMeasurementManager.cs
|
|
// 2/19/2024
|
|
// NGI - Next Generation Interceptor
|
|
//
|
|
// Contract No. HQ0856-21-C-0003/1022000209
|
|
//
|
|
// THIS DOCUMENT DOES NOT CONTAIN TECHNOLOGY OR TECHNICAL DATA CONTROLLED UNDER EITHER THE U.S.
|
|
// INTERNATIONAL TRAFFIC IN ARMS REGULATIONS OR THE U.S. EXPORT ADMINISTRATION REGULATIONS.
|
|
//
|
|
// RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION PROPRIETARY TO RAYTHEON
|
|
// COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT.
|
|
// DISCLOSURE TO UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO RAYTHEON
|
|
// COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS CONTENTS SHALL BE FURNISHED OR DISCLOSED
|
|
// TO OR COPIED OR USED BY PERSONS OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF
|
|
// RAYTHEON COMPANY.
|
|
//
|
|
// UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
|
|
//
|
|
// DESTRUCTION NOTICE: FOR CLASSIFIED DOCUMENTS FOLLOW THE PROCEDURES IN DOD 5220.22-M,
|
|
// NATIONAL INDUSTRIAL SECURITY PROGRAM OPERATING MANUAL, FEBRUARY 2006,
|
|
// INCORPORATING CHANGE 1, MARCH 28, 2013, CHAPTER 5, SECTION 7, OR DODM 5200.01-VOLUME 3,
|
|
// DOD INFORMATION SECURITY PROGRAM: PROTECTION OF CLASSIFIED INFORMATION, ENCLOSURE 3,
|
|
// SECTION 17. FOR CONTROLLED UNCLASSIFIED INFORMATION FOLLOW THE PROCEDURES IN DODM 5200.01-VOLUME 4,
|
|
// INFORMATION SECURITY PROGRAM: CONTROLLED UNCLASSIFIED INFORMATION.
|
|
//
|
|
// CONTROLLED BY: MISSILE DEFENSE AGENCY
|
|
// CONTROLLED BY: GROUND-BASED MIDCOURSE DEFENSE PROGRAM OFFICE
|
|
// CUI CATEGORY: CTI
|
|
// DISTRIBUTION/DISSEMINATION CONTROL: F
|
|
// POC: Alex Kravchenko (1118268)
|
|
// **********************************************************************************************************
|
|
using NLog;
|
|
using Raytheon.Common;
|
|
using Raytheon.Instruments;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace MeasurementManagerLib
|
|
{
|
|
internal class TelemetryClient
|
|
{
|
|
public ICommAsync Client { get; set; }
|
|
public CancellationTokenSource TokenSource { get; set; }
|
|
public string BaseDirectory { get; set; }
|
|
public string BaseFileName { get; set; }
|
|
public int BufferSize { get; set; }
|
|
public FileRecordingMode FileRecordingMode { get; set; }
|
|
}
|
|
|
|
// create a resulting file based on mode
|
|
public enum FileRecordingMode
|
|
{
|
|
/// <summary>
|
|
/// use default configuration value
|
|
/// </summary>
|
|
Undefined,
|
|
/// <summary>
|
|
/// user client provided name as is
|
|
/// overwrite the existing file
|
|
/// </summary>
|
|
None,
|
|
/// <summary>
|
|
/// append date time at the end of the base file name
|
|
/// </summary>
|
|
UnigueDateTime,
|
|
/// <summary>
|
|
/// append rolling index number at the end of the file name
|
|
/// </summary>
|
|
UniqueIndex
|
|
}
|
|
|
|
public class TelemetryMeasurementManager : IDisposable
|
|
{
|
|
private readonly ILogger _logger;
|
|
private readonly Dictionary<string, TelemetryClient> _clients = new Dictionary<string, TelemetryClient>();
|
|
|
|
private const string DefaultConfigurationName = "TelemetryManager.xml";
|
|
|
|
/// <summary>
|
|
/// adds multiple instruments for Telemetry or any other data collection
|
|
/// </summary>
|
|
/// <param name="instrumentManager"></param>
|
|
/// <param name="instrumentNames"></param>
|
|
/// <param name="configName"></param>
|
|
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
|
public TelemetryMeasurementManager(IInstrumentManager instrumentManager, List<string> instrumentNames, string configName = "")
|
|
{
|
|
_logger = LogManager.GetCurrentClassLogger();
|
|
|
|
if (instrumentNames == null)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(instrumentNames));
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(configName))
|
|
{
|
|
configName = DefaultConfigurationName;
|
|
}
|
|
IConfigurationFile config = new ConfigurationFile(configName);
|
|
|
|
instrumentNames.ForEach(i => AddInstrument(instrumentManager, i, config));
|
|
}
|
|
|
|
/// <summary>
|
|
/// starts recording for the first instrument
|
|
/// </summary>
|
|
/// <param name="fileName"></param>
|
|
/// <param name="recordingMode"></param>
|
|
public void StartRecording(string fileName = "", FileRecordingMode recordingMode = FileRecordingMode.UnigueDateTime)
|
|
{
|
|
StartRecording(_clients.FirstOrDefault().Key, fileName, recordingMode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// starts recording for the specified instrument
|
|
/// </summary>
|
|
/// <param name="fileName"></param>
|
|
/// <param name="instrumentName"></param>
|
|
/// <param name="recordingMode"></param>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
public void StartRecording(string instrumentName, string fileName = "", FileRecordingMode recordingMode = FileRecordingMode.Undefined)
|
|
{
|
|
if (string.IsNullOrEmpty(instrumentName) || !_clients.ContainsKey(instrumentName))
|
|
{
|
|
throw new InvalidOperationException($"No Telemetry Instrument Found for {instrumentName}.");
|
|
}
|
|
|
|
var telemetryClient = _clients[instrumentName];
|
|
|
|
var client = telemetryClient.Client;
|
|
var token = telemetryClient.TokenSource.Token;
|
|
if(string.IsNullOrEmpty(fileName))
|
|
{
|
|
fileName = telemetryClient.BaseFileName;
|
|
}
|
|
if(recordingMode == FileRecordingMode.Undefined)
|
|
{
|
|
recordingMode = telemetryClient.FileRecordingMode;
|
|
}
|
|
|
|
if (!Directory.Exists(telemetryClient.BaseDirectory))
|
|
{
|
|
// The directory does not exist, so create it
|
|
Directory.CreateDirectory(telemetryClient.BaseDirectory);
|
|
Console.WriteLine($"Directory '{telemetryClient.BaseDirectory}' created successfully.");
|
|
}
|
|
|
|
string filePath = Path.Combine(telemetryClient.BaseDirectory, fileName);
|
|
string uniqueFileName;
|
|
|
|
if(recordingMode == FileRecordingMode.UnigueDateTime)
|
|
{
|
|
uniqueFileName = GenerateUniqueFileNameDyDateTime(filePath);
|
|
}
|
|
else if(recordingMode == FileRecordingMode.UniqueIndex)
|
|
{
|
|
uniqueFileName = GenerateUniqueFileNameByIndex(filePath);
|
|
}
|
|
else
|
|
{
|
|
uniqueFileName = fileName;
|
|
if(File.Exists(uniqueFileName))
|
|
{
|
|
File.Delete(uniqueFileName);
|
|
}
|
|
}
|
|
|
|
_logger.Debug($"Starting Recording in {uniqueFileName}");
|
|
|
|
try
|
|
{
|
|
client.Initialize();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Error(ex, ex.Message);
|
|
throw;
|
|
}
|
|
|
|
var fileStream = new FileStream(uniqueFileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, bufferSize: telemetryClient.BufferSize, useAsync: true);
|
|
|
|
// use a thread from the thread pool and provide a function to write incoming data into a file
|
|
Task.Run(async () => await client.KeepReadingAsync(token, async (byte[] incomingData) =>
|
|
{
|
|
await fileStream?.WriteAsync(incomingData, 0, incomingData.Length);
|
|
}).ContinueWith((r) =>
|
|
{
|
|
// done recording
|
|
if (r.IsFaulted)
|
|
{
|
|
_logger.Error(r.Exception, r.Exception.Message);
|
|
}
|
|
else
|
|
{
|
|
_logger.Info($"Telemetry Manager Recording Completed for {uniqueFileName}");
|
|
}
|
|
|
|
try
|
|
{
|
|
client?.Close();
|
|
client = null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.Error(ex, ex.Message);
|
|
}
|
|
finally
|
|
{
|
|
fileStream?.Dispose();
|
|
fileStream = null;
|
|
}
|
|
}));
|
|
}
|
|
|
|
/// <summary>
|
|
/// stops recording
|
|
/// </summary>
|
|
public void StopRecording()
|
|
{
|
|
_logger.Debug("Stopping Recording.");
|
|
|
|
foreach (var instrumentName in _clients.Keys)
|
|
{
|
|
StopRecording(instrumentName);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// stops recording for specific instrument name
|
|
/// </summary>
|
|
/// <param name="instrumentName"></param>
|
|
public void StopRecording(string instrumentName)
|
|
{
|
|
if (!_clients.ContainsKey(instrumentName))
|
|
return;
|
|
|
|
_logger.Debug($"Stopping Recording for {instrumentName}.");
|
|
|
|
var telemetryClient = _clients[instrumentName];
|
|
var tokenSource = telemetryClient.TokenSource;
|
|
|
|
if (!tokenSource.IsCancellationRequested)
|
|
{
|
|
tokenSource.Cancel();
|
|
}
|
|
tokenSource.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cleanup code
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
StopRecording();
|
|
|
|
foreach (var tokenClient in _clients.Values)
|
|
{
|
|
var client = tokenClient?.Client;
|
|
client?.Close();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// generates unique file name
|
|
/// </summary>
|
|
/// <param name="existingFilePath"></param>
|
|
/// <returns></returns>
|
|
private static string GenerateUniqueFileNameDyDateTime(string existingFilePath)
|
|
{
|
|
string directory = Path.GetDirectoryName(existingFilePath);
|
|
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(existingFilePath);
|
|
string extension = Path.GetExtension(existingFilePath);
|
|
|
|
// Append a timestamp to the original filename
|
|
string uniqueFileName = $"{fileNameWithoutExtension}_{DateTime.Now:MMddHHmmss}{extension}";
|
|
|
|
// Combine with the directory path
|
|
return Path.Combine(directory, uniqueFileName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// generates unique file name based on rolling index
|
|
/// </summary>
|
|
/// <param name="existingFilePath"></param>
|
|
/// <returns></returns>
|
|
private static string GenerateUniqueFileNameByIndex(string existingFilePath)
|
|
{
|
|
string directoryPath = Path.GetDirectoryName(existingFilePath);
|
|
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(existingFilePath);
|
|
string extension = Path.GetExtension(existingFilePath);
|
|
|
|
int index = 1; // Initialize the rolling index
|
|
while (File.Exists(Path.Combine(directoryPath, $"{fileNameWithoutExtension}_{index}{extension}")))
|
|
{
|
|
index++; // Increment the index until a unique file name is found
|
|
}
|
|
|
|
// return appended index to the original filename
|
|
return Path.Combine(directoryPath, $"{fileNameWithoutExtension}_{index}{extension}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// adds single instrument to the collection
|
|
/// </summary>
|
|
/// <param name="instrumentManager"></param>
|
|
/// <param name="instrumentName"></param>
|
|
/// <param name="config"></param>
|
|
private void AddInstrument(IInstrumentManager instrumentManager, string instrumentName, IConfigurationFile config)
|
|
{
|
|
_logger.Info($"TelemetryMeasurementManager - Adding Instrument Name {instrumentName}\nConfiguration {config.FileName}");
|
|
|
|
string baseDirectory = config.ReadValue($"Telemetry_{instrumentName}", "Directory", $"./{instrumentName}");
|
|
string baseFileName = config.ReadValue($"Telemetry_{instrumentName}", "BaseFileName", $"./{instrumentName}");
|
|
FileRecordingMode fileRecordingMode = config.ReadValue($"Telemetry_{instrumentName}", "FileRecordingMode", FileRecordingMode.UnigueDateTime);
|
|
|
|
int bufferSize = config.ReadValue($"Telemetry_{instrumentName}", "BufferSize", 4096);
|
|
|
|
_clients.Add(instrumentName, new TelemetryClient
|
|
{
|
|
Client = (ICommAsync)instrumentManager.GetGenericInstrument(instrumentName),
|
|
TokenSource = new CancellationTokenSource(),
|
|
BaseDirectory = baseDirectory,
|
|
BaseFileName = baseFileName,
|
|
BufferSize = bufferSize,
|
|
FileRecordingMode = fileRecordingMode,
|
|
});
|
|
}
|
|
|
|
}
|
|
}
|