1061 lines
43 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
|