// ********************************************************************************************************** // 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, }); } } }