// ********************************************************************************************************** // 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 Raytheon.Instruments.MessagingUtilities; using System.Xml.XPath; using System.Collections.Generic; using System.Threading.Tasks; using System.Linq; using System.Threading; using Raytheon.Common; using Raytheon.Instruments.coeCSharp; using Raytheon.Instruments.Exceptions; using System.Collections.Concurrent; using static Raytheon.Instruments.MessagingUtilities.Message; using System.IO; using System.Reflection; using System.Globalization; using System.Runtime.CompilerServices; using NLog; [assembly: InternalsVisibleTo("BITCOEDeviceNode.Tests")] namespace Raytheon.Instruments { /// /// This device supports different ways of communicating with other COE nodes /// TCP /// UDP /// Serial /// public enum DriverType { Undefined, TCP, UDP, Serial }; /// /// Implementation of the IBit interface /// public class BITCOEDeviceInstrument : IBit { /// /// Nlog logger /// private readonly ILogger _logger; /// /// Raytheon configuration /// 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 readonly Dictionary>> _messages; /// /// UDP, TCP, Serial or Undefined /// private DriverType _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; /// /// instrument constructor /// public BITCOEDeviceInstrument(string name, IConfigurationManager configurationManager, DriverType driverType = DriverType.Undefined, ILogger logger = null) { Info = new InstrumentMetadata { ModelNumber = "COECommDevice" }; if (logger == null) logger = LogManager.GetCurrentClassLogger(); _logger = logger; Status = State.Uninitialized; DetailedStatus = "COE Uninitialized"; Name = name; 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>>(); _driverType = driverType; _coe = new coe(); } 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() { _logger.Trace($"{Name}({_driverType}) Initializing..."); if (_driverType == DriverType.Undefined) _driverType = _configuration.GetConfigurationValue("Parameters", "DriverType", "TCP"); _alwaysSendDefaults = _configuration.GetConfigurationValue("Parameters", "AlwaysSendDefaults", false); _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")) }, }); _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 DriverType.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 DriverType.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 DriverType.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", "5000"); _epQueueDepth = _configuration.GetConfigurationValue("Parameters", "EPQueueDepth", "5000"); _checkForMessageIntervalMs = _configuration.GetConfigurationValue("Parameters", "CheckForMessageIntervalMs", "100"); var responseLabels = _configuration.GetConfigurationListValue("ResponseMessageIds", "ResponseLabel", new List { "1", "2" }); var bitFilePaths = _configuration.GetConfigurationListValue("BitFilePaths", "FilePath", new List { "File1", "File2" }); _xmlDocs.Clear(); foreach (var path in bitFilePaths) { _xmlDocs.Add(path, new MessageXmlDocument(path, _logger)); } _icds.Clear(); foreach (var path in bitFilePaths) { _icds.Add(path, ProcessFileForNamesAndLabels(path)); } foreach (var strLabel in responseLabels) { uint label = GetLabelFromMessageId(strLabel); if (label > 0) _responseLabels.Add(label); } 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.Trace($"{Name}({_driverType}) Shutting Down..."); try { Close(); //coe.UnloadImportedDll("coeWindows-shared.dll"); } catch (Exception ex) { _logger.Error(ex, $"{Name}({_driverType}) Error while closing"); } } #region IBit functions /// /// Opens COE connection /// /// public void Open() { _logger.Trace($"{Name}({_driverType}) Opening..."); try { switch (_driverType) { case DriverType.TCP: if (_coe.tcp_media_binding_configure(_options, _logger) != coe.Status.SUCCESS) { _logger.Error($"{Name}({_driverType}) COE TCP media binding initialization failure.\nTry checking your connection configuration.\nYou could have a port collision."); Status = State.CommunicationFailure; throw new BitNotConnectedException($"{Name}({_driverType}) COE TCP media binding initialization failure"); } _logger.Trace($"{Name}({_driverType}) COE TCP media binding initialization, {_coe.ProtocolCmitName}"); break; case DriverType.UDP: if (_coe.udp_media_binding_configure(_options, _logger) != coe.Status.SUCCESS) { _logger.Error($"{Name}({_driverType}) COE UDP media binding initialization failure.\nTry checking your connection configuration.\nYou could have a port collision."); Status = State.CommunicationFailure; throw new BitNotConnectedException($"{Name}({_driverType}) COE UDP media binding initialization failure"); } _logger.Trace($"{Name}({_driverType}) COE UDP media binding initialization, Local: {_coe.ProtocolCmitName} Remote: {_coe.ProtocolName}"); break; case DriverType.Serial: if (_coe.serial_media_binding_configure(_options, _logger) != coe.Status.SUCCESS) { _logger.Error($"{Name}({_driverType}) COE Serial media binding initialization failure.\nTry checking your connection configuration.\nYou could have a port collision."); Status = State.CommunicationFailure; throw new BitNotConnectedException($"{Name}({_driverType}) COE Serial media binding initialization failure"); } _logger.Trace($"{Name}({_driverType}) COE Serial media binding initialization, {_coe.ProtocolCmitName}"); break; default: _logger.Error($"{Name}({_driverType}) Configured driver type not valid"); throw new BitNotConnectedException($"{Name}({_driverType}) Configured driver type not valid"); } foreach (var item in _options) { _logger.Trace($"{item.Key}:"); foreach (var pair in item.Value) { _logger.Trace(string.Format("{0,-50} {1, -40}", pair.Key, pair.Value)); } } } catch (Exception ex) { _logger.Error(ex); 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.Debug($"{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 ex) { Status = State.CommunicationFailure; DetailedStatus = "Unable to Open"; _logger.Error(ex); throw; } } /// /// Close COE endpoint /// /// public void Close() { _logger.Trace($"{Name}({_driverType}) Closing ..."); Status = State.Uninitialized; _cancellationTokenSource?.Cancel(); Thread.Sleep(1000); if (_messages.Any()) { foreach (var queue in _messages) { while (queue.Value.TryDequeue(out Tuple throwAway)) { _logger.Warn($"Message {throwAway.Item2.Label} ({throwAway.Item2.XmlMessage.Name}) received at {throwAway.Item1:hh.mm.ss.fff} was unclaimed"); } } } _messages.Clear(); _coe.SetConnected(false); var status = _driverType == DriverType.TCP ? _coe.TCP_media_binding_shutdown(_logger) : _driverType == DriverType.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"; } /// /// runs single BIT test request, no waiting for response /// expecting user to run /// /// /// /// /// /// public bool RunBIT(string messageId, uint timeoutInMs, IEnumerable> messageParams) { _logger.Trace($"{Name}({_driverType}) Running BIT for {messageId} with timeout {timeoutInMs} ..."); if (!_coe.IsConnected) { _logger.Error("Error sending COE message, COE not connected"); throw new BitNotConnectedException(); } if (Status != State.Ready) { _logger.Warn("Exiting RunBIT due to status"); throw new BitNotConnectedException(); } var label = GetLabelFromMessageId(messageId); var path = WhichFileContainsTheLabel(label); if (string.IsNullOrEmpty(path)) { var msg = $"Message Id {messageId} not found in any of the BIT files"; _logger.Error(msg); throw new BitParseException(msg); } try { var message = GetOeMessageWithParameters(label, messageParams, path); _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 ex) { _logger.Error(ex); return false; } } /// /// Runs a BIT and expects a result /// /// /// /// /// /// /// public BitTestResults RunBITWaitForResults(string messageIdOut, string messageIdIn, uint timeoutInMs, IEnumerable> messageParams) { _logger.Trace($"{Name}({_driverType}) Running BIT for {messageIdOut} and waiting for result {messageIdIn} with timeout {timeoutInMs}..."); if (!_coe.IsConnected) { _logger.Error("Error sending COE message, COE not connected"); throw new BitNotConnectedException(); } if (Status != State.Ready) { _logger.Warn("Exiting RunBITWaitForResults due to status"); throw new BitNotConnectedException(); } if (RunBIT(messageIdOut, timeoutInMs, messageParams)) { if (string.IsNullOrEmpty(messageIdIn)) { messageIdIn = "0"; } string[] multipleIds = messageIdIn.Split(','); var totalWaitTimeMs = 0; BitTestResults results = null; do { foreach (var id in multipleIds) { results = GetBITResults(id); if (results != null) { break; } } if (results != null || Status != State.Ready) { break; } else { Thread.Sleep(_checkForMessageIntervalMs); totalWaitTimeMs += _checkForMessageIntervalMs; } } while (results == null && totalWaitTimeMs < timeoutInMs); if (results != null) { _logger.Debug($"-- Successfully retrieved result message, totalWaitTimeMs = {totalWaitTimeMs}"); return results; } else throw new BitTimeoutException(); } else { return null; } } /// /// Reads BIT results /// /// /// /// public BitTestResults GetBITResults(string messageId) { if (!_coe.IsConnected) { _logger.Error("Error reading COE message, COE not connected"); throw new BitNotConnectedException(); } 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) { _logger.Error($"{Name}({_driverType}) Unable to match message {messageId} with anything in the dictionary. Check your configuration"); return null; } } string strLabel = label.ToString(); ConcurrentQueue> queue; lock (this) { queue = label == 0 ? _messages.Any() ? _messages.FirstOrDefault().Value : null : _messages.ContainsKey(strLabel) ? _messages[strLabel] : null; } if (queue != null && queue.TryDequeue(out Tuple message)) { var oeMessage = message.Item2; if (queue.IsEmpty) { lock (this) { _messages.Remove(oeMessage.Label); } } // make a copy of the buffer var xmlMessage = oeMessage.XmlMessage; if (xmlMessage != null) { var results = new BitTestResults { Label = oeMessage.Label, Time = message.Item1, // parse message result into list of results Results = FromXmlToBitTestResults(xmlMessage) }; return results; } else { var msg = $"Found a message with label {label}, but the Buffer is empty"; _logger.Error(msg); throw new BitParseException(msg); } } else { // message not found return null; } } #endregion #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.Debug($"{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) { _logger.Debug("Message Received..."); while (_endpoint.Peek(out uint label, out uint size, out int priority) == coe.Status.SUCCESS) { var hexLabel = $"0x{label:X}"; _logger.Debug($"{Name}({_driverType}) Identified message by peeking... {label} ({hexLabel})"); var xmlDoc = WhichFileContainsTheLabel(label); var message = new OeMessage((int)size + 1) { XmlMessage = new Message(_xmlDocs[xmlDoc], label.ToString()) }; status = _endpoint.Receive(message); if (status == coe.Status.SUCCESS) { _logger.Debug($"{Name}({_driverType}) Successfully read message... Label: {hexLabel} ({message.XmlMessage?.Name})"); message.XmlMessage.SendToLog(EnumerationType.EXPANDED_FIELDS, MessageDirection.In); ConcurrentQueue> queue; lock (this) { if (!_messages.ContainsKey(message.Label)) { _messages.Add(message.Label, new ConcurrentQueue>()); } queue = _messages[message.Label]; } queue.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); } } /// /// 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 BitTestResult FromMessageDataToBitTestResult(MessageData from) { if (from == null) return null; return new BitTestResult { 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) { var messageName = _icds[bitFilePath][messageId]; var message = new OeMessage(new Message(messageName, new MessageXmlDocument(bitFilePath, _logger))); 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"); 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 } }