Big changes
This commit is contained in:
@@ -0,0 +1,335 @@
|
||||
// **********************************************************************************************************
|
||||
// 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,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="$(SolutionDir)Solution.props" />
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<AssemblyName>TelemetryMeasurementManager</AssemblyName>
|
||||
<Product>Composable Test Software Library</Product>
|
||||
<Description>Telemetry Measurement Manager</Description>
|
||||
<OutputType>Library</OutputType>
|
||||
|
||||
<!-- Static versioning (Suitable for Development) -->
|
||||
<!-- Disable the line below for dynamic versioning -->
|
||||
<Version>1.1.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<NoWarn>NU1603</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NLog" Version="5.0.0" />
|
||||
<PackageReference Include="Raytheon.Common" Version="1.0.0" />
|
||||
<PackageReference Include="Raytheon.Instruments.InstrumentManager.Contracts" Version="1.8.0" />
|
||||
<PackageReference Include="Raytheon.Instruments.CommAsync.Contracts" Version="1.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user