using System; using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Threading; using DTS.Common; using DTS.Common.DAS.Concepts; using DTS.Common.ICommunication; using DTS.DASLib.Command; using DTS.DASLib.Command.TDAS; using DTS.DASLib.Service; using DTS.Common.Utilities.Logging; using ArmStatus = DTS.DASLib.Service.ArmStatus; using DTS.Common.Enums.DASFactory; using DTS.Common.Interface.DASFactory; using System.Collections.Concurrent; using DTS.Common.Utils; using System.Threading.Tasks; using System.Runtime.Remoting.Messaging; using DTS.DASLib.Service.Classes; namespace DTS.DASLib.DASFactory { internal abstract class TDASSetup : IDeviceSetup { private readonly bool UtilityMode; protected TDASSetup(bool _utilMode) { UtilityMode = _utilMode; } #region Slice information functions public const int DEFAULTMEMORYSIZE_PRO = 16000000; public const int DEFAULTMEMORYSIZE_G5 = 47600068; public const int DEFAULTMEMORYSIZE_DIM = 2000000; private bool NeedsModuleIndex(string serialNumber) { return serialNumber.StartsWith("5M") || serialNumber.StartsWith("PI"); } private void SetupDASInfo(DTS.Common.Interface.DASFactory.ICommunication dev, string name) { try { APILogger.Log("DasFactory.TDAS::SetupDASInfo enter"); // shortcut var DASComm = dev as IDASCommunication; uint MaxNumberOfModules = 8; try { var qm = new QueryModules(dev); try { qm.SyncExecute(); } catch (Exception ex) { APILogger.Log(ex); qm = new QueryModules(dev); qm.SyncExecute(); } try { var cmd = new QueryMaxModules(dev); cmd.SyncExecute(); DASComm.MaxModules = cmd.MaxModules; } catch (Exception) { } var qsn = new QuerySerialNumber(dev); qsn.SyncExecute(); dev.SerialNumber = qsn.SerialNumber; var moduleSerialNumbers = new List(); var moduleFirmwareVersions = new List(); var moduleIndices = new List(); TDASConfig tEntireConfig = null; bool RackIsUnreadable; if (qm.SerialNumbers.Length == 0) { //Must be an armed TDAS rack, so no module information was returned RackIsUnreadable = true; tEntireConfig = new TDASConfig(dev.SerialNumber + ".xml", false); foreach (var moduleConfig in tEntireConfig.Modules.Values) { //Serial numbers moduleSerialNumbers.Add(moduleConfig.SerialNumber); //Module indices moduleIndices.Add(moduleConfig.ModuleArrayIndex + 1); //Module firmware var module = new TDASModuleConfig { SerialNumber = moduleConfig.SerialNumber }; var mod = tEntireConfig.GetModule(module); //Strange that GetModule does basically a SetModule if not found...requiring a module to be passed instead of just a string moduleFirmwareVersions.Add(mod.FirmwareVersion); } } else { RackIsUnreadable = false; foreach (var moduleSerialNumber in qm.SerialNumbers) { moduleSerialNumbers.Add(moduleSerialNumber); } foreach (var moduleFirmwareVersion in qm.FirmwareVersions) { moduleFirmwareVersions.Add(moduleFirmwareVersion); } foreach (var moduleIndex in qm.ModuleIndices) { moduleIndices.Add(moduleIndex); } } var qfv = new QueryFirmwareVersion(dev); qfv.SyncExecute(); dev.FirmwareVersion = qfv.FirmwareVersion; var qas = new QueryArmStatus(dev); qas.SyncExecute(); var qsa = new QueryArmStatus(dev); qsa.SyncExecute(); if (null == DASComm.DASArmStatus) { DASComm.SetDASArmStatus(new ArmStatus { IsArmed = false }, false); } switch (qsa.Status) { case QueryArmStatus.ARMStatus.ARMED: case QueryArmStatus.ARMStatus.ARMING: case QueryArmStatus.ARMStatus.CAL: case QueryArmStatus.ARMStatus.FLASHWRITE: case QueryArmStatus.ARMStatus.REC: case QueryArmStatus.ARMStatus.TRIG: DASComm.DASArmStatus.IsArmed = true; DASComm.SetDASArmStatus(); break; } // get the stack contents dev.DASInfo = new Communication_DASInfo(moduleSerialNumbers.ToArray(), moduleFirmwareVersions.ToArray()); var ir = new InfoResult { NumberOfBridgeChannels = 8, MaxNumberOfModules = MaxNumberOfModules, OwningDAS = DASComm }; var modules = new List(); for (var i = 0; i < moduleSerialNumbers.Count; i++) { var moduleArrayIndex = moduleIndices[i] - 1; while (moduleArrayIndex > modules.Count) { modules.Add(new InfoResult.Module()); var index = modules.Count - 1; modules[index].SerialNumber = "EMPTY"; modules[index].TypeOfModule = DFConstantsAndEnums.ModuleType.EMPTYBANK; modules[index].NumberOfChannels = 0; modules[index].NumberOfBytesPerSampleClock = 1; modules[index].MaxEventStorageSpaceInBytes = DEFAULTMEMORYSIZE_DIM; modules[index].SupportedModes = new[]{DFConstantsAndEnums.RecordingMode.CircularBuffer, DFConstantsAndEnums.RecordingMode.RecorderMode}; modules[index].OwningInfoResult = ir; modules[index].ModuleArrayIndex = index; } modules.Add(new InfoResult.Module()); modules[moduleArrayIndex].ModuleArrayIndex = moduleArrayIndex; modules[moduleArrayIndex].OwningInfoResult = ir; modules[moduleArrayIndex].NumberOfChannels = 8; if (!qsn.SerialNumber.StartsWith("5M") && !DASComm.DASArmStatus.IsArmed) { try { var qcd = new QueryCalDate(dev) { ModuleIndex = moduleArrayIndex }; qcd.SyncExecute(); modules[moduleArrayIndex].CalibrationDate = qcd.CalDate; } catch (Exception ex) { APILogger.Log(ex); } } modules[moduleArrayIndex].SerialNumber = moduleSerialNumbers[i]; if (NeedsModuleIndex(modules[moduleArrayIndex].SerialNumber)) { modules[moduleArrayIndex].SerialNumber += i.ToString(); } modules[moduleArrayIndex].FirmwareVersion = moduleFirmwareVersions[i]; modules[moduleArrayIndex].SupportedModes = new[]{DFConstantsAndEnums.RecordingMode.CircularBuffer, DFConstantsAndEnums.RecordingMode.RecorderMode}; if (moduleSerialNumbers[i].Contains("TOM")) { modules[moduleArrayIndex].TypeOfModule = DFConstantsAndEnums.ModuleType.ProTOM; modules[moduleArrayIndex].MaxEventStorageSpaceInBytes = DEFAULTMEMORYSIZE_PRO; modules[moduleArrayIndex].NumberOfBytesPerSampleClock = 2; modules[moduleArrayIndex].NumberOfChannels = 16; } else if (moduleSerialNumbers[i].Contains("DigitalInput")) { // TDAS G5 modules[moduleArrayIndex].TypeOfModule = DFConstantsAndEnums.ModuleType.ProDIM; modules[moduleArrayIndex].MaxEventStorageSpaceInBytes = DEFAULTMEMORYSIZE_DIM; modules[moduleArrayIndex].NumberOfBytesPerSampleClock = 2; } else if (moduleSerialNumbers[i].Contains("DIM")) { // TDAS DIM modules[moduleArrayIndex].TypeOfModule = DFConstantsAndEnums.ModuleType.ProDIM; modules[moduleArrayIndex].MaxEventStorageSpaceInBytes = DEFAULTMEMORYSIZE_DIM; modules[moduleArrayIndex].NumberOfBytesPerSampleClock = 2; modules[moduleArrayIndex].NumberOfChannels = 16; } else if (moduleSerialNumbers[i].StartsWith("5M")) { //the G5 has memory at the rack, not at the modules modules[moduleArrayIndex].TypeOfModule = DFConstantsAndEnums.ModuleType.G5Analog; ir.MaxEventStorageSpaceInBytes = DEFAULTMEMORYSIZE_G5; ir.NumberOfBytesPerSampleClock = 68; //always samples all channels plus the "2" digitals } else { modules[moduleArrayIndex].TypeOfModule = DFConstantsAndEnums.ModuleType.ProSIM; modules[moduleArrayIndex].MaxEventStorageSpaceInBytes = DEFAULTMEMORYSIZE_PRO; modules[moduleArrayIndex].NumberOfBytesPerSampleClock = 16; // In reality this is dynamic for a SIM based on the number of channels configured. } try { var qmc = new QueryMemoryCommand(dev, AbstractCommandBase.Default_IO_Timeout) { ModuleIndex = modules[moduleArrayIndex].ModuleArrayIndex }; ulong? availableRamBytes; if (RackIsUnreadable) { var module = new TDASModuleConfig { SerialNumber = modules[moduleArrayIndex].SerialNumber }; var mod = tEntireConfig.GetModule(module); //Strange that GetModule does basically a SetModule if not found...requiring a module to be passed instead of just a string modules[moduleArrayIndex].RackIsUnreadable = true; availableRamBytes = mod.MaxEventStorageSpaceInBytes; } else { modules[moduleArrayIndex].RackIsUnreadable = false; qmc.SyncExecute(); availableRamBytes = qmc.AvailableRamBytes; } if (DFConstantsAndEnums.ModuleType.G5Analog == modules[moduleArrayIndex].TypeOfModule) { // There is a bit of a mis-match in domain here. qmc is named RAM bytes, but in older G5 firmware is returning flash bytes (which is less // than RAM). Unravel the mess here. ir.MaxEventStorageSpaceInBytes = 6000000 > qmc.AvailableRamBytes ? DEFAULTMEMORYSIZE_G5 : qmc.AvailableRamBytes; ir.NumberOfBytesPerSampleClock = 2 * 34;//always samples all channels plus the "2" digitals modules[moduleArrayIndex].NumberOfBytesPerSampleClock = null; modules[moduleArrayIndex].NumberOfBytesPerSampleClock = null; } else { modules[moduleArrayIndex].MaxEventStorageSpaceInBytes = availableRamBytes; } } catch (Exception ex) { APILogger.Log("Exception getting available memory, will use defaults", ex); } } if (qsn.SerialNumber.StartsWith("5M")) { var g5Mode = GetG5Mode(dev); if (dev is EthernetTDAS) { (dev as EthernetTDAS).G5Mode = g5Mode; } if (g5Mode == TDAS.G5Modes.VDS) { var i = 4; var moduleArrayIndex = i; modules.Add(new InfoResult.Module()); modules[moduleArrayIndex].ModuleArrayIndex = moduleArrayIndex; modules[moduleArrayIndex].OwningInfoResult = ir; modules[moduleArrayIndex].NumberOfChannels = 16; modules[moduleArrayIndex].SerialNumber = qsn.SerialNumber + i; modules[moduleArrayIndex].FirmwareVersion = " NA "; modules[moduleArrayIndex].SupportedModes = new[]{DFConstantsAndEnums.RecordingMode.CircularBuffer, DFConstantsAndEnums.RecordingMode.RecorderMode}; modules[moduleArrayIndex].TypeOfModule = DFConstantsAndEnums.ModuleType.G5Digital; modules[moduleArrayIndex].MaxEventStorageSpaceInBytes = DEFAULTMEMORYSIZE_DIM; modules[moduleArrayIndex].NumberOfBytesPerSampleClock = 2; } try { var qc = new QueryCalDate(dev); qc.SyncExecute(); ir.CalibrationDate = qc.CalDate; } catch (Exception ex) { APILogger.Log(ex); } } ir.Modules = modules.ToArray(); DASComm.SetDASInfo(ir); } catch (Exception ex) { APILogger.LogString("TDASSetup.SetupDASInfo exception: " + name); APILogger.LogException(ex); // if we're not in util mode we bail out if (!UtilityMode) { throw; } // if we are, we must fill in something // dev.FirmwareVersion // DASComm.SerialNumber // dev.DASInfo // DASComm.DASInfo dev.FirmwareVersion = "UNKNOWN"; DASComm.SerialNumber = "UNKNOWN"; dev.DASInfo = new Communication_DASInfo { SerialNumbers = new[] { DASComm.SerialNumber }, FirmwareVersions = new[] { dev.FirmwareVersion } }; var dasInfo = new InfoResult { MaxNumberOfModules = MaxNumberOfModules, NumberOfBytesPerSampleClock = 0, MaxEventStorageSpaceInBytes = 0, OwningDAS = DASComm, Modules = new InfoResult.Module[0] }; DASComm.SetDASInfo(dasInfo, false); } } catch (Exception ex) { APILogger.Log("DASFactory::TDASSetup failed because: ", ex); throw ex; } finally { APILogger.Log("DasFactory.TDAS::SetupDASInfo exit"); } } /// /// per rollin, iPort is pass through, so check VDS, then iDummy, then assume iPort /// iDummy should apparently have two ids /// /// /// private TDAS.G5Modes GetG5Mode(DTS.Common.Interface.DASFactory.ICommunication idas) { try { var dockstat = new G5DockStat(idas, false); dockstat.SyncExecute(); if (!dockstat.DockStationPresent) { return TDAS.G5Modes.INDUMMY; } } catch (Exception ex) { if (ex.Message.Contains("DOCKING STATION not present")) { //per CPB, TK return TDAS.G5Modes.INDUMMY; } APILogger.Log(ex); } return TDAS.G5Modes.VDS; } private void ReadProtocolVersion(DTS.Common.Interface.DASFactory.ICommunication dev, string name) { dev.ProtocolVersion = 1; } private bool IsInUpdateMode(DTS.Common.Interface.DASFactory.ICommunication dev, string name) { return false; } public bool QueryInformation(ConnectedDevice dev) { // don't assume anything... if (dev == null || dev.Dev == null || string.IsNullOrEmpty(dev.Dev.ConnectString)) { APILogger.LogString("TDASSetup.QueryInformation: Invalid parameter passed1"); return false; } if (!(dev.Dev is IConnectedDAS) || !((dev.Dev as IConnectedDAS).DASComm is DTS.Common.Interface.DASFactory.ICommunication) || !((dev.Dev as IConnectedDAS).DASComm is IDASCommunication)) { APILogger.LogString("TDASSetup.QueryInformation: Invalid parameter passed2"); return false; } // make shortcuts var CommDev = (dev.Dev as IConnectedDAS).DASComm as DTS.Common.Interface.DASFactory.ICommunication; var DevName = dev.Dev.ConnectString; try { APILogger.LogString("TDASHandling.QueryInformation: " + DevName); // First get protocol version ... ReadProtocolVersion(CommDev, DevName); // check how stack is configured SetupDASInfo(CommDev, DevName); try { if (dev.Dev is IConnectedDAS idas) { idas.DASComm?.ReadFirstUseDate(); } } catch (Exception ex) { APILogger.Log(ex); } return true; } catch (Exception ex) { try { if (ex is SocketException) { APILogger.LogString(string.Format("{0} Connection:{1} Message:{2}", APILogger.GetCurrentMethod(), DevName, ex.Message)); } else { APILogger.LogException(ex); } if (IsInUpdateMode(CommDev, DevName)) { var dasInfo = new InfoResult { MaxNumberOfModules = 10, Modules = new InfoResult.Module[0] }; (dev.Dev as IConnectedDAS).DASComm.SetDASInfo(dasInfo, false); dev.InUpdateMode = true; return true; } return false; } catch (Exception eex) { APILogger.LogString("DASFactory.QuerySliceInformation.EX exception: " + dev.Dev.ConnectString); APILogger.LogException(eex); return false; } } } #endregion public abstract DTS.Common.Interface.DASFactory.ICommunication GetICommunication(); public abstract DTS.Common.Interface.DASFactory.ICommunication GetICommunication(ConnectedDevice dev); public abstract IConnectedDevice GetIConnectedDevice(DTS.Common.Interface.DASFactory.ICommunication comm); public abstract bool IsCorrectType(ConnectedDevice dev); public abstract DFConstantsAndEnums.DASType GetDASType(); public abstract Guid GetGuid(); public virtual int GetProductId() { return 0; } public virtual string GetProductIdString() { return string.Empty; } protected DeviceHandling _handler; public virtual void SetHandler(DeviceHandling handler) { _handler = handler; } } internal class TDASEthernetSetup : TDASSetup { public override DTS.Common.Interface.DASFactory.ICommunication GetICommunication() { var et = new EthernetTDAS(); et.OnDisconnected += et_OnDisconnected; return et; } void et_OnDisconnected(object sender, EventArgs e) { _handler.ReportDisconnect(sender); } public override DTS.Common.Interface.DASFactory.ICommunication GetICommunication(ConnectedDevice dev) { return (dev.Dev as ConnectedEthernetTDAS).Comm; } public override IConnectedDevice GetIConnectedDevice(DTS.Common.Interface.DASFactory.ICommunication comm) { return new ConnectedEthernetTDAS(comm as EthernetTDAS); } public override bool IsCorrectType(ConnectedDevice dev) { return dev.Dev is ConnectedEthernetTDAS; } public override DFConstantsAndEnums.DASType GetDASType() { return DFConstantsAndEnums.DASType.ETHERNET_TDAS; } public override Guid GetGuid() { return Guid.Empty; } public TDASEthernetSetup(bool bInUtilityMode) : base(bInUtilityMode) { } } internal class EthernetTDASHandling : DeviceHandling { public override void ReportDisconnect(object obj) { if (!(obj is TDAS o)) return; foreach (var s in _sockets) { if (s.ConnectString != o.ConnectString) continue; s.SetDisconnected(); OnSocketDisconnect(s); break; } } /// /// Timeout in milliseconds to connect an Ethernet TDAS device /// public int ConnectEthernetTDASTimeout { get; set; } private Thread TDASListenerThread; private readonly ManualResetEvent TDASSignalEvent = new ManualResetEvent(false); // device type 1 private IDeviceSetup tdasSetup { get; set; } private static readonly object TDASHOSTSLOCK = new object(); private readonly ManualResetEvent _interrupt = new ManualResetEvent(false); public void Interrupt() { _interrupt.Set(); } private string[] _TDASHostNames; public string[] TDASHostNames { get { lock (TDASHOSTSLOCK) { return _TDASHostNames; } } set { if (TDASListenerThread == null) { // SLICEDBListenerThread is not running if (null == value || value.Length < 1 || string.Empty == value[0]) { // no-op return; } } else { // it's already running, we need to stop it TDASSignalEvent.Reset(); _bKeepGoing = false; //setting interrupt here signals anny connection attempts to stop Interrupt(); // this will signal when the listen thread is done executing TDASSignalEvent.WaitOne(3000, false); } lock (TDASHOSTSLOCK) { _TDASHostNames = value; } // start it up TDASSignalEvent.Reset(); _interrupt.Reset(); TDASListenerThread = new Thread(TDASEventListenerTask) { IsBackground = true }; TDASListenerThread.Start(); // wait for it to start TDASSignalEvent.WaitOne(); } } public EthernetTDASHandling(DASFactory _factory, UpdateFinishedEventHandler _SerialUpdateFinished, IDeviceSetup _tdas, BlockingCollection> queueActionPerDevice ) : base(_factory, _SerialUpdateFinished, queueActionPerDevice) { factory = _factory; tdasSetup = _tdas; ConnectEthernetTDASTimeout = 120000; } private static DASFactory factory { get; set; } public override void Dispose() { try { if (null != TDASListenerThread) { TDASListenerThread.Abort(); } } catch (Exception) { } base.Dispose(); } internal override bool DetachAllDevices() { var bDevicesRemoved = false; lock (ConnectedDevicesLock) { foreach (var dev in ConnectedDevices) { dev.Dispose(); bDevicesRemoved = true; } ConnectedDevices.Clear(); } return bDevicesRemoved; } internal delegate void SocketConnected(TDASSocket socket); internal delegate void SocketDisconnected(TDASSocket socket); internal void OnSocketConnect(TDASSocket socket) { lock (_ConnectedDASLock) { if (!_connectedTDAS.Contains(socket.ConnectString)) { APILogger.Log($"Added to connected devices {socket.ConnectString} [tdas]"); _connectedTDAS.Add(socket.ConnectString); } } UpdateConnectedDevices(); } internal void OnSocketDisconnect(TDASSocket socket) { var updated = false; lock (_ConnectedDASLock) { if (_connectedTDAS.Contains(socket.ConnectString)) { updated = true; _connectedTDAS.Remove(socket.ConnectString); APILogger.Log($"Removed from connected devices {socket.ConnectString} [tdas]"); } } if (updated) { UpdateDisconnectedDevices(); } } [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S101:Types should be named in PascalCase", Justification = "Acronym")] internal class TDASSocket : IDisposable { private volatile bool _haveConnected; private volatile bool _connectStarted; private readonly SocketConnected _sockConnect; private readonly SocketDisconnected _sockDisconnect; public string HostName { get; } private const int TDAS_PORT = 8000; private readonly int _port = TDAS_PORT; public int Port => _port; public string ConnectString => string.Format("{0}:{1}", HostName, _port); private Socket _socket; public void SetDisconnected() { _haveConnected = false; } private void OnConnect(IAsyncResult ar) { try { lock (SocketLock) { if (null != _socket) { try { if (!_socket.Connected) { //end connect is handled by original caller _haveConnected = false; return; } //we actually connected, disconnect now EndConnectAndClose(_socket); } catch (SocketException se) { APILogger.Log(se); _haveConnected = false; return; } catch (Exception ex) { APILogger.Log(ex); _sockDisconnect(this); } } _socket = null; } lock (MyLock) { //we've successfully connected, mark and then continue on //with the communication code _haveConnected = true; _connectStarted = false; } _sockConnect(this); } catch (SocketException) { Thread.Sleep(100); _sockDisconnect(this); } } private static void PingHost(string ip) { try { var pingDevice = new PingUtils.PingDevice(); pingDevice.PingDevices(new List { ip }); } catch ( Exception ex) { APILogger.Log($"Failed to ping {ip}", ex); } } private void CreateSocket() { _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP); _socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); // https://benohead.com/windows-network-connections-timing-quickly-temporary-connectivity-loss/ //KeepAliveTime: default value is 2hr //KeepAliveInterval: default value is 1s and Detect 5 times uint dummy = 0; //lenth = 4 var inOptionValues = new byte[System.Runtime.InteropServices.Marshal.SizeOf(dummy) * 3]; var OnOff = true; var KeepAliveTimeOutMS = 5 * 1000; var KeepAliveRetryIntervalMS = 1000; BitConverter.GetBytes((uint)(OnOff ? 1 : 0)).CopyTo(inOptionValues, 0); BitConverter.GetBytes(DFConstantsAndEnums.LocalKeepAliveTimeOutMS) .CopyTo(inOptionValues, System.Runtime.InteropServices.Marshal.SizeOf(dummy)); BitConverter.GetBytes(DFConstantsAndEnums.LocalKeepAliveRetryIntervalMS) .CopyTo(inOptionValues, System.Runtime.InteropServices.Marshal.SizeOf(dummy) * 2); _socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); //FB 18152 get the host for TDAS if (!Common.Utils.PingUtils.DasToHost.ContainsKey(HostName)) { PingHost(HostName); } var hostIpAddress = Common.Utils.PingUtils.DasToHost[HostName].HostIpAddress; if (!string.IsNullOrEmpty(hostIpAddress)) { _socket.Bind(new IPEndPoint(IPAddress.Parse(hostIpAddress), 0)); } } /// /// this lock is to protect access to _connectStarted between threads /// private readonly object MyLock = new object(); /// /// Starts the process of connecting (if needed, otherwise returns immediately) /// will start the connection as a separate task /// public void Connect() { lock (MyLock) { if (_connectStarted) { return; } if (_haveConnected) { return; } _connectStarted = true; } _ = Task.Run(ConnectAsync); } private readonly object SocketLock = new object(); private static void EndConnectAndClose(Socket socket) { try { socket.Close(); socket.Dispose(); } catch( Exception ex) { APILogger.Log(ex); } } /// /// this is the background connect logic /// it will spin until we connect or the stop wait handle is signaled /// private void ConnectAsync() { do { try { lock (SocketLock) { if (null == _socket) { CreateSocket(); } var ar = _socket.BeginConnect(HostName, _port, OnConnect, null); ar.AsyncWaitHandle.WaitOne(1500, true); if (!_socket.Connected) { EndConnectAndClose(_socket); _socket = null; continue; } } return; } catch( Exception ex) { APILogger.Log(ex); } } while (!_stop.WaitOne(0, false)); } /// /// this is a wait handle, when signalled it indicates we should not try to connect /// if we are still trying /// private readonly ManualResetEvent _stop; public TDASSocket(string hostName, SocketConnected sockConnect, SocketDisconnected sockDisconnect, ManualResetEvent stop) { _stop = stop; var tokens = hostName.Split(':'); if (tokens.Length > 1) { int.TryParse(tokens[1], out _port); HostName = tokens[0]; } else { HostName = hostName; } _sockConnect = sockConnect; _sockDisconnect = sockDisconnect; Connect(); } public void Dispose() { try { _socket.Disconnect(false); _socket.Close(); } catch (Exception) { // don't care } } } /// /// Here we keep track of connected TDAS devices /// private readonly List _connectedTDAS = new List(); private TDASSocket[] _sockets; private volatile bool _bKeepGoing; private const int RECONNECT_SPIN_MS = 1000; private void TDASEventListenerTask() { lock (_ConnectedDASLock) { _connectedTDAS.Clear(); } _ActiveDAS.Clear(); // signal that we're alive _bKeepGoing = true; TDASSignalEvent.Set(); try { if (null == _TDASHostNames || _TDASHostNames.Length < 1) { return; } _sockets = new TDASSocket[TDASHostNames.Length]; for (var i = 0; i < TDASHostNames.Length; i++) { if (string.IsNullOrWhiteSpace(TDASHostNames[i])) { continue; } _sockets[i] = new TDASSocket(TDASHostNames[i], OnSocketConnect, OnSocketDisconnect, _interrupt); } while (_bKeepGoing) { foreach (var socket in _sockets) { //call connect, if we are already connected or connecting it will //return immediately socket.Connect(); } Thread.Sleep(RECONNECT_SPIN_MS); } } catch (ThreadInterruptedException) { //don't log } catch (ThreadAbortException) { // we must exit } catch (Exception) { // don't care } finally { _bKeepGoing = false; //signal that the thread is done and any connection attempts should stop _interrupt.Set(); RemoveAll(); TDASListenerThread = null; _TDASHostNames = null; // signal that thread is done executing TDASSignalEvent.Set(); } } private readonly object _ConnectedDASLock = new object(); private readonly List _ActiveDAS = new List(); private List GetConnectedEthernetDeviceString(bool slice) { var list = new List(); lock (_ConnectedDASLock) { if (null == _connectedTDAS || _connectedTDAS.Count < 1) { return list; } if (slice) { list.AddRange(_connectedTDAS); } } return list; } private List GetActiveConnectedEthernetDeviceStrings(bool slice) { var list = new List(); lock (_ConnectedDASLock) { if (null == _connectedTDAS || _connectedTDAS.Count < 1) { return list; } } var devs = ConnectedDevices.ToArray(); foreach (var dev in devs) { if (slice) { list.Add(dev.Dev.ConnectString); } } return list; } private List GetConnectedDeviceStrings() { return GetConnectedEthernetDeviceString(true); } private List GetAllActiveDeviceStrings() { return GetActiveConnectedEthernetDeviceStrings(true); } private DTS.Common.Interface.DASFactory.ICommunication GetICommunication() { return tdasSetup.GetICommunication(); } private IConnectedDevice GetIConnectedDevice(DTS.Common.Interface.DASFactory.ICommunication com) { return tdasSetup.GetIConnectedDevice(com); } private void UpdateConnectedTDAS() { ConnectNewDevices(GetConnectedDeviceStrings, GetAllActiveDeviceStrings, GetICommunication, GetIConnectedDevice, tdasSetup.GetDASType(), tdasSetup, ConnectEthernetTDASTimeout); } public override void UpdateConnectedDevices() { UpdateConnectedTDAS(); } private bool IsCorrectType(ConnectedDevice dev) { return tdasSetup.IsCorrectType(dev); } private DTS.Common.Interface.DASFactory.ICommunication ConnectedDevice2Communication(ConnectedDevice dev) { return tdasSetup.GetICommunication(dev); } private void UpdateDisconnectedTDAS() { DisconnectRemovedDevices(GetConnectedDeviceStrings, IsCorrectType, ConnectedDevice2Communication, tdasSetup.GetDASType(), ConnectEthernetTDASTimeout); } public override void UpdateDisconnectedDevices() { UpdateDisconnectedTDAS(); } private void RemoveAll() { EnqueueDisconnect(); lock (_ConnectedDASLock) { _connectedTDAS.Clear(); } _ActiveDAS.Clear(); } public override void UpdateDeviceSetups() { tdasSetup.SetHandler(this); } } }