Files
DP44/DataPRO/DASFactory/DASFactory.TDAS.cs
2026-04-17 14:55:32 -04:00

1061 lines
43 KiB
C#

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<string>();
var moduleFirmwareVersions = new List<string>();
var moduleIndices = new List<int>();
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<InfoResult.Module>();
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<EthernetConnection>.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");
}
}
/// <summary>
/// per rollin, iPort is pass through, so check VDS, then iDummy, then assume iPort
/// iDummy should apparently have two ids
/// </summary>
/// <param name="idas"></param>
/// <returns></returns>
private TDAS<EthernetConnection>.G5Modes GetG5Mode(DTS.Common.Interface.DASFactory.ICommunication idas)
{
try
{
var dockstat = new G5DockStat(idas, false);
dockstat.SyncExecute();
if (!dockstat.DockStationPresent)
{
return TDAS<EthernetConnection>.G5Modes.INDUMMY;
}
}
catch (Exception ex)
{
if (ex.Message.Contains("DOCKING STATION not present"))
{
//per CPB, TK
return TDAS<EthernetConnection>.G5Modes.INDUMMY;
}
APILogger.Log(ex);
}
return TDAS<EthernetConnection>.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<EthernetConnection> o)) return;
foreach (var s in _sockets)
{
if (s.ConnectString != o.ConnectString) continue;
s.SetDisconnected();
OnSocketDisconnect(s);
break;
}
}
/// <summary>
/// Timeout in milliseconds to connect an Ethernet TDAS device
/// </summary>
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<Tuple<QueueActions, DeviceHandling>> 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<string> { 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)); }
}
/// <summary>
/// this lock is to protect access to _connectStarted between threads
/// </summary>
private readonly object MyLock = new object();
/// <summary>
/// Starts the process of connecting (if needed, otherwise returns immediately)
/// will start the connection as a separate task
/// </summary>
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);
}
}
/// <summary>
/// this is the background connect logic
/// it will spin until we connect or the stop wait handle is signaled
/// </summary>
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));
}
/// <summary>
/// this is a wait handle, when signalled it indicates we should not try to connect
/// if we are still trying
/// </summary>
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
}
}
}
/// <summary>
/// Here we keep track of connected TDAS devices
/// </summary>
private readonly List<string> _connectedTDAS = new List<string>();
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<string> _ActiveDAS = new List<string>();
private List<string> GetConnectedEthernetDeviceString(bool slice)
{
var list = new List<string>();
lock (_ConnectedDASLock)
{
if (null == _connectedTDAS || _connectedTDAS.Count < 1) { return list; }
if (slice) { list.AddRange(_connectedTDAS); }
}
return list;
}
private List<string> GetActiveConnectedEthernetDeviceStrings(bool slice)
{
var list = new List<string>();
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<string> GetConnectedDeviceStrings()
{
return GetConnectedEthernetDeviceString(true);
}
private List<string> 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);
}
}
}