// **********************************************************************************************************
// 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
{
///
/// use default configuration value
///
Undefined,
///
/// user client provided name as is
/// overwrite the existing file
///
None,
///
/// append date time at the end of the base file name
///
UnigueDateTime,
///
/// append rolling index number at the end of the file name
///
UniqueIndex
}
public class TelemetryMeasurementManager : IDisposable
{
private readonly ILogger _logger;
private readonly Dictionary _clients = new Dictionary();
private const string DefaultConfigurationName = "TelemetryManager.xml";
///
/// adds multiple instruments for Telemetry or any other data collection
///
///
///
///
///
public TelemetryMeasurementManager(IInstrumentManager instrumentManager, List 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));
}
///
/// starts recording for the first instrument
///
///
///
public void StartRecording(string fileName = "", FileRecordingMode recordingMode = FileRecordingMode.UnigueDateTime)
{
StartRecording(_clients.FirstOrDefault().Key, fileName, recordingMode);
}
///
/// starts recording for the specified instrument
///
///
///
///
///
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;
}
}));
}
///
/// stops recording
///
public void StopRecording()
{
_logger.Debug("Stopping Recording.");
foreach (var instrumentName in _clients.Keys)
{
StopRecording(instrumentName);
}
}
///
/// stops recording for specific instrument name
///
///
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();
}
///
/// Cleanup code
///
public void Dispose()
{
StopRecording();
foreach (var tokenClient in _clients.Values)
{
var client = tokenClient?.Client;
client?.Close();
}
}
///
/// generates unique file name
///
///
///
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);
}
///
/// generates unique file name based on rolling index
///
///
///
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}");
}
///
/// adds single instrument to the collection
///
///
///
///
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,
});
}
}
}