// ********************************************************************************************************** // BITCOEDeviceInstrument.cs // 6/21/2022 // 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 System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using System.Xml.XPath; using NLog; using Raytheon.Common; using Raytheon.Common.Coe; using Raytheon.Instruments.Exceptions; using static Raytheon.Common.Coe.Message; namespace Raytheon.Instruments { /// /// This device supports different ways of communicating with other COE nodes /// TCP /// UDP /// Serial /// public enum CoeDriverType { Undefined, TCP, UDP, Serial }; /// /// Implementation of the IBit interface /// public class CoeCommDevice : CoeComm { private readonly ILogger _logger; private readonly IConfigurationManager _configurationManager; private readonly IConfiguration _configuration; /// /// reference to the main wrapper class for Common Operating Environment /// private readonly coe _coe; /// /// COE endpoint /// private coeEndpoint _endpoint; /// /// cancellation token for stopping reading thread /// private CancellationTokenSource _cancellationTokenSource = null; /// /// collection of the messages received /// private Dictionary>> _messages; // Record the time of when a message was logged, so we can slow down logging // For high-rate message, if we don't slow down logging, it will starve other threads from logging // and therefore lose logging data private Dictionary _messageLogTime = new Dictionary(); // Keep track of the log interval in seconds for each message private Dictionary _messageLogIntervalInSec = new Dictionary(); // Keep track whether or not to log for each message private Dictionary _messageLogging = new Dictionary(); // Record the time of when "Message Received" was logged private DateTime? _messageReceivedLogTime = null; // Keep track of the log interval in seconds for "Message Received" notification private int _messageReceivedLogIntervalInSec = 0; /// /// UDP, TCP, Serial or Undefined /// private CoeDriverType _driverType; /// /// dictionary of options when initializing COE endpoint and router /// private readonly Dictionary>> _options = new Dictionary>>(); /// /// used for initialization of the endpoint /// private uint _maxMessageSize; private uint _epQueueDepth; /// /// Number of milliseconds to wake up and check the message when receiving /// private int _checkForMessageIntervalMs; /// /// collection of all labels with message names per every XML file /// private readonly Dictionary> _icds = new Dictionary>(); /// /// collection of response labels or messages that COE endpoint should be registered for /// private readonly List _responseLabels = new List(); /// /// collection of message XML documents (processed XML files) used in COE communications /// private readonly Dictionary _xmlDocs = new Dictionary(); /// /// when set to true the instrument will check every value and if empty /// will populate it with the default value /// private bool _alwaysSendDefaults; private int _maxQueueSizeForEachMessage = 100; /// /// instrument constructor /// public CoeCommDevice(string deviceName, IConfigurationManager configurationManager) { Info = new InstrumentMetadata { ModelNumber = "COECommDevice" }; _logger = LogManager.GetLogger($"{this.GetType().Name} - {deviceName}"); Status = State.Uninitialized; DetailedStatus = "COE Uninitialized"; Name = deviceName; if (LogManager.Configuration == null) { var assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); LogManager.Configuration = new NLog.Config.XmlLoggingConfiguration(assemblyFolder + "\\nlog.config"); } if (configurationManager == null) { _logger.Error($"Cannot create {Name} without a configuration manager"); return; } _configurationManager = configurationManager; _configuration = _configurationManager.GetConfiguration(Name); _messages = new Dictionary>>(); _coe = new coe(); } ~CoeCommDevice() { _logger?.Debug($"Entering {this.GetType().Name}::{System.Reflection.MethodBase.GetCurrentMethod().Name}() ..."); } public string DetailedStatus { get; protected set; } public bool DisplayEnabled { get => false; set => throw new NotImplementedException(); } public bool FrontPanelEnabled { get => false; set => throw new NotImplementedException(); } public InstrumentMetadata Info { get; set; } public string Name { get; protected set; } public SelfTestResult SelfTestResult => PerformSelfTest(); public State Status { get; set; } public bool ClearErrors() { return true; } /// /// Initializes COE instrument /// public void Initialize() { _driverType = _configuration.GetConfigurationValue("Parameters", "DriverType"); _logger.Debug($"{Name}({_driverType}) Initializing..."); _alwaysSendDefaults = _configuration.GetConfigurationValue("Parameters", "AlwaysSendDefaults"); _options.Clear(); _options.Add("ROUTER_CONFIG", new List> { { new KeyValuePair("NODE_ID", _configuration.GetConfigurationValue("ROUTER_CONFIG", "NODE_ID", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_STATE", _configuration.GetConfigurationValue("ROUTER_CONFIG", "DISPLAY_DEBUG_STATE", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_LABEL_MESSAGE", _configuration.GetConfigurationValue("ROUTER_CONFIG", "DISPLAY_DEBUG_LABEL_MESSAGE", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_BRIDGE_REGISTRATION", _configuration.GetConfigurationValue("ROUTER_CONFIG", "DISPLAY_DEBUG_BRIDGE_REGISTRATION", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_ROUTER_DATABASE", _configuration.GetConfigurationValue("ROUTER_CONFIG", "DISPLAY_DEBUG_ROUTER_DATABASE", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_SEND", _configuration.GetConfigurationValue("ROUTER_CONFIG", "DISPLAY_DEBUG_SEND", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_RECV", _configuration.GetConfigurationValue("ROUTER_CONFIG", "DISPLAY_DEBUG_RECV", "0")) }, { new KeyValuePair("BUFFER_SIZE", _configuration.GetConfigurationValue("ROUTER_CONFIG", "BUFFER_SIZE", "256")) }, { new KeyValuePair("ENABLE_REGISTRATION_MESSAGES", _configuration.GetConfigurationValue("ROUTER_CONFIG", "ENABLE_REGISTRATION_MESSAGES", "1")) }, { new KeyValuePair("THREAD_STACK_SIZE", _configuration.GetConfigurationValue("ROUTER_CONFIG", "THREAD_STACK_SIZE", "16384")) }, { new KeyValuePair("TRANSMIT_OPTIONS", _configuration.GetConfigurationValue("ROUTER_CONFIG", "TRANSMIT_OPTIONS", "0")) }, }); _options.Add("ROUTER_PROTOCOL_CONFIG", new List> { { new KeyValuePair("DISPLAY_DEBUG_SEND", _configuration.GetConfigurationValue("ROUTER_PROTOCOL_CONFIG", "DISPLAY_DEBUG_SEND", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_RECV", _configuration.GetConfigurationValue("ROUTER_PROTOCOL_CONFIG", "DISPLAY_DEBUG_RECV", "0")) }, { new KeyValuePair("THREAD_STACK_SIZE", _configuration.GetConfigurationValue("ROUTER_PROTOCOL_CONFIG", "THREAD_STACK_SIZE", "16384")) }, }); var poolEntry = _configuration.GetConfigurationValue("ROUTER_BUFFER_POOLS", "POOL_ENTRY", "100,32|50,128|100,384|150,1536|10,65535"); if (!string.IsNullOrEmpty(poolEntry)) { var poolEntries = poolEntry.Split('|'); if (poolEntries.Any()) { var entries = new List>(); foreach (var entry in poolEntries) { entries.Add(new KeyValuePair("POOL_ENTRY", entry)); } _options.Add("ROUTER_BUFFER_POOLS", entries); } } _options.Add("BASIC_REGISTRATION_CONFIG", new List> { { new KeyValuePair("DISPLAY_DEBUG_SEND", _configuration.GetConfigurationValue("BASIC_REGISTRATION_CONFIG", "DISPLAY_DEBUG_SEND", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_SEND_BUFFER", _configuration.GetConfigurationValue("BASIC_REGISTRATION_CONFIG", "DISPLAY_DEBUG_SEND_BUFFER", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_RECV", _configuration.GetConfigurationValue("BASIC_REGISTRATION_CONFIG", "DISPLAY_DEBUG_RECV", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_RECV_BUFFER", _configuration.GetConfigurationValue("BASIC_REGISTRATION_CONFIG", "DISPLAY_DEBUG_RECV_BUFFER", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_STATE", _configuration.GetConfigurationValue("BASIC_REGISTRATION_CONFIG", "DISPLAY_DEBUG_STATE", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_PING_SEND", _configuration.GetConfigurationValue("BASIC_REGISTRATION_CONFIG", "DISPLAY_DEBUG_PING_SEND", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_PING_RECV", _configuration.GetConfigurationValue("BASIC_REGISTRATION_CONFIG", "DISPLAY_DEBUG_PING_RECV", "0")) }, { new KeyValuePair("THREAD_STACK_SIZE", _configuration.GetConfigurationValue("BASIC_REGISTRATION_CONFIG", "THREAD_STACK_SIZE", "16384")) }, }); switch (_driverType) { case CoeDriverType.UDP: _options.Add("UDP_MEDIA_BINDING_CONFIG", new List> { { new KeyValuePair("LOCAL_IP_ADDRESS", _configuration.GetConfigurationValue("UDP_MEDIA_BINDING_CONFIG", "LOCAL_IP_ADDRESS", "127.0.0.1")) }, { new KeyValuePair("REMOTE_IP_ADDRESS", _configuration.GetConfigurationValue("UDP_MEDIA_BINDING_CONFIG", "REMOTE_IP_ADDRESS", "127.0.0.1")) }, { new KeyValuePair("LOCAL_SEND_PORT", _configuration.GetConfigurationValue("UDP_MEDIA_BINDING_CONFIG", "LOCAL_SEND_PORT", "32010")) }, { new KeyValuePair("LOCAL_RECV_PORT", _configuration.GetConfigurationValue("UDP_MEDIA_BINDING_CONFIG", "LOCAL_RECV_PORT", "32020")) }, { new KeyValuePair("REMOTE_SEND_PORT", _configuration.GetConfigurationValue("UDP_MEDIA_BINDING_CONFIG", "REMOTE_SEND_PORT", "32011")) }, { new KeyValuePair("REMOTE_RECV_PORT", _configuration.GetConfigurationValue("UDP_MEDIA_BINDING_CONFIG", "REMOTE_RECV_PORT", "32021")) }, { new KeyValuePair("RECV_TIMEOUT", _configuration.GetConfigurationValue("UDP_MEDIA_BINDING_CONFIG", "RECV_TIMEOUT", "200")) }, { new KeyValuePair("DISPLAY_DEBUG_SEND", _configuration.GetConfigurationValue("UDP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_SEND", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_RECV", _configuration.GetConfigurationValue("UDP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_RECV", "0")) }, { new KeyValuePair("MTU_SIZE", _configuration.GetConfigurationValue("UDP_MEDIA_BINDING_CONFIG", "MTU_SIZE", "1472")) }, { new KeyValuePair("THREAD_STACK_SIZE", _configuration.GetConfigurationValue("UDP_MEDIA_BINDING_CONFIG", "THREAD_STACK_SIZE", "16384")) }, { new KeyValuePair("THREAD_NAME", _configuration.GetConfigurationValue("UDP_MEDIA_BINDING_CONFIG", "THREAD_NAME", "UDP_MB_RCV")) }, }); break; case CoeDriverType.TCP: _options.Add("TCP_MEDIA_BINDING_CONFIG", new List> { { new KeyValuePair("LOCAL_PORT", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "LOCAL_PORT", "9990")) }, { new KeyValuePair("NUM_PORTS", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "NUM_PORTS", "32")) }, { new KeyValuePair("NUM_DYNAMIC_NODES", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "NUM_DYNAMIC_NODES", "32")) }, { new KeyValuePair("SERVER_ADDRESS", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "SERVER_ADDRESS", "127.0.0.1:9990")) }, { new KeyValuePair("UDP_TX_BUFFER_SIZE", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "UDP_TX_BUFFER_SIZE", "5000")) }, { new KeyValuePair("UDP_RX_BUFFER_SIZE", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "UDP_RX_BUFFER_SIZE", "32768")) }, { new KeyValuePair("TCP_TX_BUFFER_SIZE", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "TCP_TX_BUFFER_SIZE", "5000")) }, { new KeyValuePair("TCP_RX_BUFFER_SIZE", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "TCP_RX_BUFFER_SIZE", "4096")) }, { new KeyValuePair("PACKET_SIZE", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "PACKET_SIZE", "5128")) }, { new KeyValuePair("TCP_SELECT_VALUE", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "TCP_SELECT_VALUE", "1")) }, { new KeyValuePair("DISABLE_NAG_DELAY", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "DISABLE_NAG_DELAY", "1")) }, { new KeyValuePair("TIMER_RATE", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "TIMER_RATE", "1000")) }, { new KeyValuePair("CONNECT_KA_RATE", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "CONNECT_KA_RATE", "1")) }, { new KeyValuePair("RECV_KA_RATE", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "RECV_KA_RATE", "1")) }, { new KeyValuePair("SERVER_CONNECT_RATE", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "SERVER_CONNECT_RATE", "1")) }, { new KeyValuePair("RECV_THREAD_STACK_SIZE", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "RECV_THREAD_STACK_SIZE", "4096")) }, { new KeyValuePair("RECV_THREAD_PRIORITY", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "RECV_THREAD_PRIORITY", "0")) }, { new KeyValuePair("RECV_THREAD_AFFINITY", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "RECV_THREAD_AFFINITY", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_SEND", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_SEND", "1")) }, { new KeyValuePair("DISPLAY_DEBUG_SEND_BUFFER", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_SEND_BUFFER", "1")) }, { new KeyValuePair("DISPLAY_DEBUG_UDP_RECV", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_UDP_RECV", "1")) }, { new KeyValuePair("DISPLAY_DEBUG_UDP_RECV_BUFFER", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_UDP_RECV_BUFFER", "1")) }, { new KeyValuePair("DISPLAY_DEBUG_TCP_RECV", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_TCP_RECV", "1")) }, { new KeyValuePair("DISPLAY_DEBUG_TCP_RECV_BUFFER", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_TCP_RECV_BUFFER", "1")) }, { new KeyValuePair("DISPLAY_DEBUG_RECV", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_RECV", "1")) }, { new KeyValuePair("DISPLAY_DEBUG_RECV_BUFFER", _configuration.GetConfigurationValue("TCP_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_RECV_BUFFER", "1")) }, }); break; case CoeDriverType.Serial: _options.Add("SERIAL_MEDIA_BINDING_CONFIG", new List> { { new KeyValuePair("DEVICE_NAME", _configuration.GetConfigurationValue("SERIAL_MEDIA_BINDING_CONFIG", "DEVICE_NAME", "\\\\.\\COM1")) }, { new KeyValuePair("BAUD_RATE", _configuration.GetConfigurationValue("SERIAL_MEDIA_BINDING_CONFIG", "BAUD_RATE", "9600")) }, { new KeyValuePair("DATA_BITS", _configuration.GetConfigurationValue("SERIAL_MEDIA_BINDING_CONFIG", "DATA_BITS", "8")) }, { new KeyValuePair("STOP_BITS", _configuration.GetConfigurationValue("SERIAL_MEDIA_BINDING_CONFIG", "STOP_BITS", "1")) }, { new KeyValuePair("PARITY", _configuration.GetConfigurationValue("SERIAL_MEDIA_BINDING_CONFIG", "PARITY", "0")) }, { new KeyValuePair("FLOW_CONTROL", _configuration.GetConfigurationValue("SERIAL_MEDIA_BINDING_CONFIG", "FLOW_CONTROL", "0")) }, { new KeyValuePair("MTU_SIZE", _configuration.GetConfigurationValue("SERIAL_MEDIA_BINDING_CONFIG", "MTU_SIZE", "256")) }, { new KeyValuePair("RECV_PROCESSING_DELAY", _configuration.GetConfigurationValue("SERIAL_MEDIA_BINDING_CONFIG", "RECV_PROCESSING_DELAY", "100")) }, { new KeyValuePair("DISPLAY_DEBUG_SEND", _configuration.GetConfigurationValue("SERIAL_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_SEND", "0")) }, { new KeyValuePair("DISPLAY_DEBUG_RECV", _configuration.GetConfigurationValue("SERIAL_MEDIA_BINDING_CONFIG", "DISPLAY_DEBUG_RECV", "0")) }, }); break; default: _logger.Error($"{Name}({_driverType}) Configured driver type not valid"); break; } _maxMessageSize = _configuration.GetConfigurationValue("Parameters", "MaxMessageSize"); _epQueueDepth = _configuration.GetConfigurationValue("Parameters", "EPQueueDepth"); _checkForMessageIntervalMs = _configuration.GetConfigurationValue("Parameters", "CheckForMessageIntervalMs"); RaytheonXmlConfigurationWrapper rayConfigWrapper; rayConfigWrapper = new RaytheonXmlConfigurationWrapper(_configuration.GetXmlConfiguration("MaxQueueSizePerResponseMsg")); int.TryParse(rayConfigWrapper.GetValue("/"), out _maxQueueSizeForEachMessage); rayConfigWrapper = new RaytheonXmlConfigurationWrapper(_configuration.GetXmlConfiguration("IncomingMessageNotificationIntervalInSec")); int.TryParse(rayConfigWrapper.GetValue("/"), out _messageReceivedLogIntervalInSec); var responseLabels = _configuration.GetConfigurationListValue("ResponseMessageIds", "ResponseLabel", new List { "1", "2" }); var coeXmlFiles = _configuration.GetConfigurationListValue("CoeXmlFiles", "FilePath", new List { "File1", "File2" }); _xmlDocs.Clear(); foreach (var file in coeXmlFiles) { string fileFullPath = Path.Combine(_configurationManager.ConfigurationStoragePath, file); fileFullPath = Path.GetFullPath(fileFullPath); _xmlDocs.Add(fileFullPath, new MessageXmlDocument(fileFullPath)); } _icds.Clear(); foreach (var file in coeXmlFiles) { string fileFullPath = Path.Combine(_configurationManager.ConfigurationStoragePath, file); fileFullPath = Path.GetFullPath(fileFullPath); _icds.Add(fileFullPath, ProcessFileForNamesAndLabels(fileFullPath)); } foreach (var strLabel in responseLabels) { uint label = GetLabelFromMessageId(strLabel); if (label > 0) _responseLabels.Add(label); } rayConfigWrapper = new RaytheonXmlConfigurationWrapper(_configuration.GetXmlConfiguration("MessageLogOptions")); XNode xNode = rayConfigWrapper.GetXElement("/Message/"); while (xNode != null) { if (xNode.NodeType != System.Xml.XmlNodeType.Element) { xNode = xNode.NextNode; continue; } string attrName = "LogIntervalInSec"; uint label = GetLabelFromMessageId(((XElement)xNode).Value); if (((XElement)xNode).Attribute(attrName) != null) { if (int.TryParse(((XElement)xNode).Attribute(attrName).Value, out int numSecs)) { if (!_messageLogTime.ContainsKey(label.ToString())) { _messageLogTime[label.ToString()] = null; _messageLogIntervalInSec[label.ToString()] = numSecs; } } } attrName = "Log"; if (((XElement)xNode).Attribute(attrName) != null) { if (bool.TryParse(((XElement)xNode).Attribute(attrName).Value, out bool logMessage)) { if (!_messageLogging.ContainsKey(label.ToString())) _messageLogging[label.ToString()] = logMessage; } } xNode = xNode.NextNode; } DetailedStatus = "COE Initialized"; Status = State.Ready; } /// /// performs self-test /// /// public SelfTestResult PerformSelfTest() { _logger.Trace($"{Name}({_driverType}) Performing Self Test..."); // TODO implement method return SelfTestResult.Pass; } /// /// Resets COE device comms /// /// public void Reset() { _logger.Trace($"{Name}({_driverType}) Resetting..."); Close(); Open(); } /// /// Shuts down COE device /// public void Shutdown() { _logger.Debug($"{Name}({_driverType}) Shutting Down..."); try { Close(); //coe.UnloadImportedDll("coeWindows-shared.dll"); } catch (Exception ex) { _logger.Error(ex, $"{Name}({_driverType}) Error while closing"); } } /// /// Opens COE connection /// /// public void Open() { _logger.Debug($"{Name}({_driverType}) Opening..."); try { switch (_driverType) { case CoeDriverType.TCP: if (_coe.tcp_media_binding_configure(_options, _logger) != coe.Status.SUCCESS) { Status = State.CommunicationFailure; throw new CoeNotConnectedException($"{Name}({_driverType}) COE TCP media binding initialization failure. Try checking your connection configuration. You could have a port collision."); } _logger.Info($"{Name}({_driverType}) COE TCP media binding initialization, {_coe.ProtocolCmitName}"); break; case CoeDriverType.UDP: if (_coe.udp_media_binding_configure(_options, _logger) != coe.Status.SUCCESS) { Status = State.CommunicationFailure; throw new CoeNotConnectedException($"{Name}({_driverType}) COE UDP media binding initialization failure. Try checking your connection configuration. You could have a port collision."); } _logger.Info($"{Name}({_driverType}) COE UDP media binding initialization, Local: {_coe.ProtocolCmitName} Remote: {_coe.ProtocolName}"); break; case CoeDriverType.Serial: if (_coe.serial_media_binding_configure(_options, _logger) != coe.Status.SUCCESS) { Status = State.CommunicationFailure; throw new CoeNotConnectedException($"{Name}({_driverType}) COE Serial media binding initialization failure. Try checking your connection configuration. You could have a port collision."); } _logger.Info($"{Name}({_driverType}) COE Serial media binding initialization, {_coe.ProtocolCmitName}"); break; default: throw new CoeNotConnectedException($"{Name}({_driverType}) Configured driver type not valid"); } foreach (var item in _options) { int keyMaxCharLength = 40; foreach (var pair in item.Value) { int keyCharPadding = keyMaxCharLength - pair.Key.Length; string keyStr = pair.Key; if (keyCharPadding > 0) { for (int i = 0; i < keyCharPadding; i++) { keyStr += "_"; } } _logger.Info($"{keyStr}{pair.Value}"); } } } catch (Exception) { Status = State.CommunicationFailure; DetailedStatus = "Unable to Open"; throw; } try { _coe.SetConnected(true); //_endpoint = new coeEndpoint(_maxMessageSize, _epQueueDepth, _coe.Router); _endpoint = new coeEndpoint(_maxMessageSize, _epQueueDepth); _logger.Info($"{Name}({_driverType}) Endpoint Created, Max Message Size: {_maxMessageSize}, Queue Depth: {_epQueueDepth}"); foreach (var item in _responseLabels) { var fileName = WhichFileContainsTheLabel(item); if (!string.IsNullOrEmpty(fileName)) { var msgName = _icds[fileName][item]; if (!string.IsNullOrEmpty(msgName)) { _endpoint.Register(item); _logger.Info($"{Name}({_driverType}) Registering new message with the endpoint, {item}: {msgName}"); } else { _logger.Warn($"{Name}({_driverType}) Message with label {item} is not located in file {fileName}"); } } else { _logger.Warn($"{Name}({_driverType}) Unable to locate label {item} in any of the XML files registered for COE device"); } } _cancellationTokenSource = new CancellationTokenSource(); Task.Run(() => ReadMessages(_cancellationTokenSource.Token)); Status = State.Ready; DetailedStatus = "Opened"; } catch (Exception) { Status = State.CommunicationFailure; DetailedStatus = "Unable to Open"; throw; } } /// /// Close COE endpoint /// /// public void Close() { _logger.Debug($"{Name}({_driverType}) Closing ..."); Status = State.Uninitialized; _cancellationTokenSource?.Cancel(); Thread.Sleep(1000); _messages.Clear(); _coe.SetConnected(false); var status = _driverType == CoeDriverType.TCP ? _coe.TCP_media_binding_shutdown(_logger) : _driverType == CoeDriverType.UDP ? _coe.UDP_media_binding_shutdown(_logger) : _coe.SERIAL_media_binding_shutdown(_logger); if (status != coe.Status.SUCCESS) { _logger.Error($"{_driverType} media binding shutdown failure, status {status}"); } else { _logger.Debug($"{_driverType} shutdown was successful"); } _endpoint?.Dispose(); _cancellationTokenSource?.Dispose(); _cancellationTokenSource = null; DetailedStatus = "Closed"; } /// /// Send a message /// /// /// /// /// /// public bool SendMessage(string messageId, IEnumerable> messageParams) { if (!_coe.IsConnected) { throw new CoeNotConnectedException(); } if (Status != State.Ready) { throw new Exception("COE is not initialized"); } var label = GetLabelFromMessageId(messageId); var coeXmlFile = WhichFileContainsTheLabel(label); if (string.IsNullOrEmpty(coeXmlFile)) { var msg = $"Message Id {messageId} not found in any of the COE XML files"; throw new CoeParseException(msg); } try { bool logData = true; if (_messageLogging.ContainsKey(label.ToString())) { if (!_messageLogging[label.ToString()]) { logData = false; } } if (logData && _messageLogTime.ContainsKey(label.ToString())) { if (_messageLogTime[label.ToString()] != null) { logData = false; double elapsedSecs = DateTime.Now.Subtract((DateTime)_messageLogTime[label.ToString()]).TotalSeconds; if (elapsedSecs >= (double)_messageLogIntervalInSec[label.ToString()]) { logData = true; _messageLogTime[label.ToString()] = DateTime.Now; } } else _messageLogTime[label.ToString()] = DateTime.Now; } var message = GetOeMessageWithParameters(label, messageParams, coeXmlFile, logData); if (logData) { _logger.Info("Sending ..."); message.XmlMessage.SendToLog(EnumerationType.EXPANDED_FIELDS, MessageDirection.Out); } var status = _endpoint.Send(message); if (status != coe.Status.SUCCESS) { _logger.Error($"Error sending COE message, error code: {status}"); } return true; } catch (Exception) { throw; } } /// /// Get next response in FIFO Queue, meaning we are getting the oldest response /// /// /// /// public CoeResponseMsgData GetNextResponseInQueue(string messageId) { if (!_coe.IsConnected) { throw new CoeNotConnectedException(); } uint label = 0; // empty string or zero means first available message from the top if (!string.IsNullOrEmpty(messageId) && messageId != "0") { label = GetLabelFromMessageId(messageId); if (label == 0) { throw new Exception($"{Name}({_driverType}) Unable to match message {messageId} with anything in the dictionary. Check your configuration"); } } string strLabel = label.ToString(); Tuple message = null; lock (this) { ConcurrentQueue> queue; queue = label == 0 ? _messages.Any() ? _messages.FirstOrDefault().Value : null : _messages.ContainsKey(strLabel) ? _messages[strLabel] : null; if (queue != null) { queue.TryDequeue(out message); } } if (message != null) { OeMessage oeMessage = message.Item2; // make a copy of the buffer var xmlMessage = oeMessage.XmlMessage; if (xmlMessage != null) { var results = new CoeResponseMsgData { Label = oeMessage.Label, Time = message.Item1, Name = xmlMessage.Name, // parse message result into list of results FieldDataList = FromXmlToBitTestResults(xmlMessage) }; return results; } else { var msg = $"Found a message with label {label}, but the Buffer is empty"; throw new CoeParseException(msg); } } else { // message not found return null; } } /// /// Clear the queue for a particular response message. /// This is useful if we have a large size queue and we don't want to dequeue each item to get /// to the newest item. So we clear the queue first, before trying to get the newest item in the queue /// public void ClearResponseMessageQueue(string messageId) { if (!_coe.IsConnected) { throw new CoeNotConnectedException(); } uint label = 0; // empty string or zero means first available message from the top if (!string.IsNullOrEmpty(messageId) && messageId != "0") { label = GetLabelFromMessageId(messageId); if (label == 0) { throw new Exception($"{Name}({_driverType}) Unable to match message {messageId} with anything in the dictionary. Check your configuration"); } } string strLabel = label.ToString(); lock (this) { if (_messages.ContainsKey(strLabel)) { _messages.Remove(strLabel); } } } /// /// Get XML Docs /// /// public object GetXmlDocs() { return _xmlDocs; } #region Private Functions /// /// keep reading messages and stash them in _messages dictionary with label and timestamp as a key /// /// /// private void ReadMessages(CancellationToken cancellationToken) { _logger.Info($"{Name}({_driverType}) Starting to read messages."); try { while (!cancellationToken.IsCancellationRequested) { //_logger.Debug($"{Name}({_driverType}) Checking for messages..."); var status = _endpoint.Wait(1000); if (status == coe.Status.SUCCESS) { bool logMessageReceived = true; if (_messageReceivedLogTime != null) { logMessageReceived = false; double elapsedSecs = DateTime.Now.Subtract((DateTime)_messageReceivedLogTime).TotalSeconds; if (elapsedSecs >= (double)_messageReceivedLogIntervalInSec) { logMessageReceived = true; _messageReceivedLogTime = DateTime.Now; } } else _messageReceivedLogTime = DateTime.Now; if (logMessageReceived) _logger.Debug("Message Received..."); bool logData = true; while (_endpoint.Peek(out uint label, out uint size, out int priority) == coe.Status.SUCCESS) { logData = true; var hexLabel = $"0x{label:X}"; if (_messageLogging.ContainsKey(label.ToString())) { if (!_messageLogging[label.ToString()]) { logData = false; } } if (logData && _messageLogTime.ContainsKey(label.ToString())) { if (_messageLogTime[label.ToString()] != null) { logData = false; double elapsedSecs = DateTime.Now.Subtract((DateTime)_messageLogTime[label.ToString()]).TotalSeconds; if (elapsedSecs >= (double)_messageLogIntervalInSec[label.ToString()]) { logData = true; _messageLogTime[label.ToString()] = DateTime.Now; } } else _messageLogTime[label.ToString()] = DateTime.Now; } if (logData) _logger.Debug($"{Name}({_driverType}) Identified message by peeking... {label} ({hexLabel})"); var xmlDoc = WhichFileContainsTheLabel(label); bool prevLogData = _xmlDocs[xmlDoc].LogData; _xmlDocs[xmlDoc].LogData = logData; var message = new OeMessage((int)size) { XmlMessage = new Message(_xmlDocs[xmlDoc], label.ToString(), logData) }; _xmlDocs[xmlDoc].LogData = prevLogData; status = _endpoint.Receive(message); if (status == coe.Status.SUCCESS) { if (logData) { _logger.Debug($"{Name}({_driverType}) Successfully read message... Label: {hexLabel} ({message.XmlMessage?.Name})"); message.XmlMessage.SendToLog(EnumerationType.EXPANDED_FIELDS, MessageDirection.In); } lock (this) { if (!_messages.ContainsKey(message.Label)) { _messages.Add(message.Label, new ConcurrentQueue>()); } if (_messages[message.Label].Count >= _maxQueueSizeForEachMessage) { _messages[message.Label].TryDequeue(out _); } _messages[message.Label].Enqueue(new Tuple(DateTime.Now, message)); } } else { _logger.Error($"{Name}({_driverType}) Endpoint Receive Failed. Status = {status}"); } } } // If not timeout and no cancellation requested else if (status != coe.Status.FAILED_TIMEOUT && !cancellationToken.IsCancellationRequested) { _logger.Error($"{Name}({_driverType}) Event Flag Wait Failed. Status = {status}"); } } _logger.Debug($"{Name}({_driverType}) Stopping to read messages. Cancellation was requested."); } catch (Exception ex) { _logger.Error(ex.Message + "\r\n" + ex.StackTrace); } } /// /// Determine if we should log this message /// /// /// public bool ShallLogMessage(string messageName) { bool logData = true; uint label = GetLabelFromMessageId(messageName); if (_messageLogging.ContainsKey(label.ToString())) { if (!_messageLogging[label.ToString()]) { logData = false; } } if (logData && _messageLogTime.ContainsKey(label.ToString())) { if (_messageLogTime[label.ToString()] != null) { logData = false; double elapsedSecs = DateTime.Now.Subtract((DateTime)_messageLogTime[label.ToString()]).TotalSeconds; if (elapsedSecs >= (double)_messageLogIntervalInSec[label.ToString()]) { logData = true; _messageLogTime[label.ToString()] = DateTime.Now; } } else _messageLogTime[label.ToString()] = DateTime.Now; } return logData; } /// /// if message id can be converted to uint returns the value /// otherwise returns the related label from the message id by dictionary lookup /// /// /// private uint GetLabelFromMessageId(string messageId) { uint labelId = FromStringToUint(messageId); if (labelId == 0) { foreach (var file in _icds) { var item = file.Value.FirstOrDefault(l => l.Value == messageId); if (!string.IsNullOrEmpty(item.Value)) return item.Key; } } return labelId; } /// /// return file path for the file that contains the label /// /// /// private string WhichFileContainsTheLabel(uint label) { foreach (var item in _icds) { if (item.Value.Keys.Contains(label)) { return item.Key; } } return string.Empty; } /// /// convert from Message to list of BItTestResult fields /// /// /// private IList FromXmlToBitTestResults(Message message) { if (message == null) return null; var result = FromMessageArrayToBitResult(message.MessageDataArray); return result; } /// /// recursive function for getting results out /// /// /// private IList FromMessageArrayToBitResult(MessageData[] messages) { if (messages == null || messages.Length == 0) return null; var result = new List(); foreach (var item in messages) { result.Add(FromMessageDataToBitTestResult(item)); if (item.MessageArray != null && item.MessageArray.Length > 0) { var moreResults = FromMessageArrayToBitResult(item.MessageArray); result.AddRange(moreResults); } } return result; } /// /// copy message data fields to BitTestResult /// /// /// private static CoeFieldData FromMessageDataToBitTestResult(MessageData from) { if (from == null) return null; return new CoeFieldData { FieldArrayValue = from.FieldArrayValue, FieldBitValue = from.FieldBitValue, FieldDefaultValue = from.FieldDefaultValue, FieldInstruType = from.FieldInstruType, FieldMaxValue = from.FieldMaxValue, FieldMinValue = from.FieldMinValue, FieldName = from.FieldName, FieldType = from.FieldType, FieldValue = from.FieldValue, Variable = from.Variable, MaxOffset = from.MaxOffset, MinOffset = from.MinOffset, VerifyType = from.VerifyType, IsSelected = from.isSelected, IsArray = from.isArray, IsStructure = from.isStructure, IsArrayOfStructures = from.isArrayOfStructures, IsEnum = from.isEnum, UsesRegister = from.usesRegister, IsValid = from.isValid, UseRange = from.useRange, ArrayLength = from.arrayLength, ImageWidth = from.imageWidth, ImageHeight = from.imageHeight, ImagePixelSize = from.imagePixelSize, BitMask = from.bitMask, Expanded = from.expanded, Depth = from.depth, ImageBuffer = from.imageBuffer?.ToArray(), ImageBufferSize = from.imageBufferSize, }; } /// /// Build OeMessage from messageId and provided parameters /// /// /// /// private OeMessage GetOeMessageWithParameters(uint messageId, IEnumerable> messageParams, string bitFilePath, bool logData = true) { var messageName = _icds[bitFilePath][messageId]; var message = new OeMessage(new Message(messageName, new MessageXmlDocument(bitFilePath, logData), logData)); if (messageParams != null) { message.XmlMessage.MessageDataArray = PopulateParameters(message.XmlMessage.MessageDataArray, 0, messageParams); } return message; } /// /// recursive function to populate parameters /// /// message data array /// indicates how deep in the tree we are populating parameters /// message parameters /// internal MessageData[] PopulateParameters(MessageData[] data, int level, IEnumerable> messageParams) { // only get parameters from the same level var levelParams = messageParams.Where(m => m.Key.Where(c => c == '.' || c == ']').Count() == level); foreach (var item in data) { if (item.FieldName.StartsWith("$")) continue; var messageParam = levelParams.FirstOrDefault(m => m.Key.EndsWith(item.FieldName)); if (!string.IsNullOrEmpty(messageParam.Key) && !string.IsNullOrEmpty(messageParam.Value)) { item.FieldValue = messageParam.Value; } // always send defaults means that even if parameter was not provided use the default value to populate field value else if (_alwaysSendDefaults) { item.FieldValue = item.FieldDefaultValue; } // if there are more levels, update recursively if (item.MessageArray != null && item.MessageArray.Length > 0) item.MessageArray = PopulateParameters(item.MessageArray, level + 1, messageParams); } return data; } /// /// reads xml file and extracts all message names with associated labels /// /// /// private Dictionary ProcessFileForNamesAndLabels(string filePath) { var doc = new XPathDocument(filePath); XPathNavigator node = doc.CreateNavigator(); XPathNodeIterator nodeset = node.Select("interface/message|interface/namespace/message"); var result = new Dictionary(); while (nodeset.MoveNext()) { var children = nodeset.Current.SelectChildren(XPathNodeType.Element); if (children.Count > 0) { string strName = string.Empty; string strLabel = string.Empty; while (children.MoveNext()) { if (children.Current.Name == "name") { strName = children.Current.Value; if (!string.IsNullOrEmpty(strName)) strName = strName.Trim(); } else if (children.Current.Name == "label") { strLabel = children.Current.Value; if (!string.IsNullOrEmpty(strLabel)) strLabel = strLabel.Trim(); } } uint iLabel = FromStringToUint(strLabel); if (!string.IsNullOrEmpty(strName) && iLabel > 0) { result.Add(iLabel, strName); } } } return result; } /// /// converts from string representation of a label to uint value /// /// /// private uint FromStringToUint(string data) { if (!string.IsNullOrEmpty(data)) { if (data.StartsWith("0x", StringComparison.CurrentCultureIgnoreCase) || data.StartsWith("&H", StringComparison.CurrentCultureIgnoreCase)) { if (uint.TryParse(data.Substring(2), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out uint uiValue)) return uiValue; } else { if (uint.TryParse(data, out uint uiValuel)) return uiValuel; } } return 0; } #endregion } }