// ********************************************************************************************************** // CommDeviceSerialAsync.cs // 4/3/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 System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using System.Threading; using System.Reflection; using System.IO.Ports; namespace Raytheon.Instruments { /// /// A sim communication device /// public class CommDeviceSerialAsync : ICommAsync { #region PrivateClassMembers private uint _defaultReadTimeout; private uint _defaultSendTimeout; private uint _defaultReadBufferSize; private static readonly object _syncObj = new object(); private SerialPort _serialPort; private readonly string _comPortName; private readonly int _baudRate; private readonly Parity _parity; private readonly int _dataBits; private readonly StopBits _stopBits; private readonly string _name; private State _state; /// /// NLog logger /// private readonly ILogger _logger; /// /// Raytheon configuration /// private readonly IConfigurationManager _configurationManager; private readonly IConfiguration _configuration; #endregion public bool ClearErrors() => false; public bool FrontPanelEnabled { get => false; set => throw new NotImplementedException(); } public bool DisplayEnabled { get => false; set => throw new NotImplementedException(); } public string DetailedStatus => $"This is a TCP/IP Device called {_name}"; public InstrumentMetadata Info => throw new NotImplementedException(); public State Status => _state; public string Name => _name; public SelfTestResult PerformSelfTest() => SelfTestResult; public SelfTestResult SelfTestResult => SelfTestResult.Unknown; public void Close() => Shutdown(); public void Reset() { Close(); Open(); } #region Private Functions /// /// Dispose of the resources contained by this object /// public void Dispose() { try { lock (_syncObj) { Dispose(true); GC.SuppressFinalize(this); } } catch (Exception err) { _logger.Error(err.Message + "\r\n" + err.StackTrace); } } /// /// Dispose of the resources contained by this object /// /// protected virtual void Dispose(bool disposing) { if (disposing) { // close the socket try { Shutdown(); } catch (Exception err) { _logger.Error(err.Message + "\r\n" + err.StackTrace); } } } #endregion #region Public Functions /// /// CommDevice factory constructor /// /// /// public CommDeviceSerialAsync(string name, IConfigurationManager configurationManager, ILogger logger) { _name = name; _configurationManager = configurationManager; _configuration = _configurationManager.GetConfiguration(name); _logger = logger; _comPortName = _configuration.GetConfigurationValue("CommDeviceSerialAsync", "COMPortName", "COM15"); _baudRate = _configuration.GetConfigurationValue("CommDeviceSerialAsync", "BaudRate", 115200); _parity = _configuration.GetConfigurationValue("CommDeviceSerialAsync", "Parity", Parity.None); _dataBits = _configuration.GetConfigurationValue("CommDeviceSerialAsync", "DataBits", 8); _stopBits = _configuration.GetConfigurationValue("CommDeviceSerialAsync", "StopBits", StopBits.One); _defaultReadTimeout = _configuration.GetConfigurationValue("CommDeviceSerialAsync", "ReadTimeout", 25); _defaultSendTimeout = _configuration.GetConfigurationValue("CommDeviceSerialAsync", "SendTimeout", 5000); _defaultReadBufferSize = _configuration.GetConfigurationValue("CommDeviceSerialAsync", "BufferSize", 1024); _state = State.Uninitialized; } /// /// initialize instrument /// public void Initialize() { if (_state != State.Uninitialized) { _logger.Warn("Reinitialization of existing Serial Async Connection. Attempting to call Shutdown."); Shutdown(); } _serialPort = new SerialPort(_comPortName, _baudRate, _parity, _dataBits, _stopBits); Open(); } /// /// Opens COM serial port for communications /// public void Open() { try { _serialPort.Open(); _state = State.Ready; } catch (Exception ex) { _logger.Error(ex, ex.Message); throw; } } /// /// shuts down the device /// public void Shutdown() { _logger.Debug("Shutting down"); _serialPort.Close(); _state = State.Uninitialized; } /// /// Read data from the device asynchronously. /// /// The buffer to put the data in /// The number of bytes read public async Task ReadAsync(byte[] dataRead, CancellationToken token = default) { var bytesRead = await _serialPort.BaseStream.ReadAsync(dataRead, 0, dataRead.Length, token); return (uint)bytesRead; } /// /// Read string from the device asynchronously. /// /// The buffer to put the data in /// The number of bytes read public async Task ReadAsync(CancellationToken token = default) { var data = await ReadLineAsync(token); return data; } /// /// Sets the read timeout /// /// public void SetReadTimeout(uint timeoutMs) { if (_serialPort == null) return; _logger.Trace($"Setting Reader Timeout: {timeoutMs} Ms"); _serialPort.ReadTimeout = (int)timeoutMs; } /// /// Write data to the device asynchronously /// /// /// /// public async Task WriteAsync(byte[] dataToSend, uint numBytesToWrite, CancellationToken token = default) { if (_serialPort == null || !_serialPort.IsOpen) return 0; _logger.Trace($"Writing message to ({_comPortName}), bytes: {dataToSend?.Length}"); await _serialPort.BaseStream.WriteAsync(dataToSend, 0, (int)numBytesToWrite, token); return numBytesToWrite; } /// /// Write string data to the device asynchronously /// /// /// public async Task WriteAsync(string message, CancellationToken token = default) { if (_serialPort == null || !_serialPort.IsOpen) return; _logger.Trace($"Writing message to ({_comPortName}), message: {message}"); await WriteLineAsync(message, token); } /// /// Send Command and Get Response asynchronously /// /// /// /// public async Task SendCommandGetResponseAsync(string message, CancellationToken cancellationToken = default, int timeoutInMs = 5000) { if (_serialPort == null || !_serialPort.IsOpen) return null; _logger.Trace($"Sending command waiting for response from ({_comPortName}), message: {message}"); using (var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(timeoutInMs))) { if (cancellationToken == default) { cancellationToken = cts.Token; } await WriteAsync(message, cancellationToken); string readResponse = await ReadAsync(cancellationToken); _logger.Trace($"Received response: {readResponse}"); return readResponse; } } /// /// Send Command and Get Response asynchronously /// /// /// /// public async Task SendCommandGetResponseAsync(byte[] data, CancellationToken token = default, int timeoutInMs = 5000) { if (_serialPort == null || !_serialPort.IsOpen) return null; _logger.Trace($"Sending command waiting for response from ({_comPortName}), message: {data}"); await WriteAsync(data, (uint)data.Length, token); _serialPort.ReadTimeout = timeoutInMs; var response = new byte[data.Length]; await ReadAsync(response, token); return response; } /// /// keeps reading until canceled via token, /// received messages sent to dataReceived function /// /// /// /// public async Task KeepReadingAsync(CancellationToken cancellationToken, Action dataReceived) { if (_serialPort == null || !_serialPort.IsOpen) return; _logger.Debug($"Starting continuous reading from {_comPortName} ..."); while (!cancellationToken.IsCancellationRequested) { var data = await ReadAsync(cancellationToken); dataReceived?.Invoke(data); } _logger.Debug($"Finished continuous reading from {_comPortName} ..."); } /// /// keeps reading until canceled via token, /// received messages sent to dataReceived function /// /// /// /// public async Task KeepReadingAsync(CancellationToken cancellationToken, Action dataReceived) { if (_serialPort == null || !_serialPort.IsOpen) return; _logger.Debug($"Starting continuous reading from {_comPortName} ..."); while (!cancellationToken.IsCancellationRequested) { var data = new byte[_defaultReadBufferSize]; // Adjust buffer size as needed var bytesRead = await ReadAsync(data, cancellationToken); Array.Resize(ref data, (int)bytesRead); dataReceived?.Invoke(data); } _logger.Debug($"Finished continuous reading from {_comPortName} ..."); } #endregion #region private functions /// /// reads line async /// /// /// private async Task ReadLineAsync(CancellationToken cancellationToken = default) { try { cancellationToken.ThrowIfCancellationRequested(); var line = await Task.Run(() => _serialPort.ReadLine(), cancellationToken); return line; } catch (OperationCanceledException ex) { _logger.Error(ex, ex.Message); return null; } } /// /// writes line async /// /// /// /// private async Task WriteLineAsync(string message, CancellationToken cancellationToken = default) { try { cancellationToken.ThrowIfCancellationRequested(); await Task.Run(() => _serialPort.WriteLine(message), cancellationToken); } catch (OperationCanceledException ex) { _logger.Error(ex, ex.Message); } } #endregion } }