Files
DP44/Common/DTS.Common.DataModel/DASFactory.cs

453 lines
18 KiB
C#
Raw Normal View History

2026-04-17 14:55:32 -04:00
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using DTS.Common.Utilities;
using DTS.DASLib.DASFactory;
using DTS.DASLib.Service;
using DTS.Common.Utilities.Logging;
using DTS.Common.Interface.DASFactory;
using DTS.Common.Enums.DASFactory;
using DTS.Common.DataModel;
namespace DataPROWin7.DataModel
{
public class DASFactory
{
/// <summary>
/// starts the auto discovery process if it's not running yet
/// </summary>
public void StartMulticastAutoDiscovery()
{
_dasFactory.StartMulticastAutoDiscovery();
}
/// <summary>
/// stops the auto discovery process if it is running
/// </summary>
public void StopMulticastAutoDiscovery()
{
_dasFactory.StopMulticastAutoDiscovery();
}
/// <summary>
/// returns any discovered devices
/// </summary>
/// <returns></returns>
public IDiscoveredDevice [] GetDiscoveredDevices()
{
return _dasFactory.GetDiscoveredDevices();
}
private DTS.DASLib.DASFactory.DASFactory _dasFactory;
public IDASFactory GetDASFactory() { return _dasFactory; }
public DASFactory()
{
//10285 Unhandled exception during diagnostics.
//this initializes the interval value used in CheckUnitsAvailable to make the sleep time configurable.
DTS.DASLib.Service.ServiceBase.InitializeCheckUnitsInterval(DataModelSettings.CheckUnitsIntervalMillisecond);
//10843 TDAS communication needs to be throttled
//this initializes the throttling of TDAS devices
DTS.DASLib.Command.TDAS.CommandBase.InitializeSemaphore(
DataModelSettings.SemaphoreDelay,
DataModelSettings.SemaphoreSpots);
//initialize SLICESemaphore before any communication starts
//10852 Add semaphore to SLICE communication to improve SLICE 6 multiple IP performance
DTS.DASLib.Command.SliceCommandBase.Initialize(DataModelSettings.SLICEConcurrentSpots,
DataModelSettings.SLICEConcurrentDelayMs);
_dasFactory = new DTS.DASLib.DASFactory.DASFactory(false, false);
_dasFactory.DetachAllDevices();
var mre = new ManualResetEvent(false);
_dasFactory.Refresh(delegate { mre.Set(); });
mre.WaitOne();
_dasFactory.MultiCastAutoDiscoveryDefaultTimeoutMS = DataModelSettings.MulticastAutoDiscoveryReceiveTimeoutMS;
//16053 Implement KeepAliveSeconds and KeepAliveRetrySeconds in the .config file
DFConstantsAndEnums.LocalKeepAliveRetryIntervalMS = DataModelSettings.LocalKeepAliveRetryIntervalMS;
DFConstantsAndEnums.LocalKeepAliveTimeOutMS = DataModelSettings.LocalKeepAliveTimeOutMS;
DFConstantsAndEnums.RemoteKeepAliveRetryIntervalSeconds = DataModelSettings.RemoteKeepAliveRetryIntervalSeconds;
DFConstantsAndEnums.RemoteKeepAliveSeconds = DataModelSettings.RemoteKeepAliveSeconds;
DFConstantsAndEnums.ReceiveBufferSizeBytes = DataModelSettings.ReceiveBufferSizeBytes;
DFConstantsAndEnums.SendBufferSizeBytes = DataModelSettings.SendBufferSizeBytes;
DFConstantsAndEnums.HeartbeatAsyncConnectTimeoutMS = DataModelSettings.HeartbeatAsyncConnectTimeoutMS;
_dasFactory.DeviceArrived += _dasFactory_DeviceArrived;
_dasFactory.DeviceFailed += _dasFactory_DeviceFailed;
_dasFactory.DeviceRemoved += _dasFactory_DeviceRemoved;
}
public event DASFactoryEventHandler OnDeviceArrived;
public event DASFactoryEventHandler OnFactoryChanged;
public void TakeOwnership()
{
try
{
_dasFactory.TakeOwnership();
}
catch (Exception ex)
{
APILogger.Log(ex);
}
}
private void _dasFactory_DeviceRemoved(object sender, DASFactoryEventArgs e)
{
OnFactoryChanged?.Invoke(sender, e);
}
private void _dasFactory_DeviceFailed(object sender, DASFactoryEventArgs e)
{
OnFactoryChanged?.Invoke(sender, e);
}
private void _dasFactory_DeviceArrived(object sender, DASFactoryEventArgs e)
{
OnDeviceArrived?.Invoke(sender, e);
OnFactoryChanged?.Invoke(sender, e);
}
public void DetachAllDevices(bool detachUSB = false)
{
// FB14290: make USB detaching conditional
_dasFactory.DetachAllDevices(detachUSB);
}
public void DisposeFactory()
{
_dasFactory.Dispose();
_dasFactory = null;
}
//#define LOG_DEBUG_REFRESH
public string[] TDASHostNames
{
get { return _dasFactory.TDASHostNames; }
set
{
if (_bInRefresh)
{
#if LOG_DEBUG_REFRESH
var st = new StackTrace(true);
APILogger.Log("TDASHostNames SET WHILE WE ARE IN REFRESH\n", st.ToString());
#endif
}
if (null == value || null == _dasFactory.TDASHostNames)
{
_dasFactory.TDASHostNames = value;
}
else
{
var val = value.Distinct().OrderBy(a => a);
// this has side effects, so only change if needed
var isEqual = _dasFactory.TDASHostNames.OrderBy(a => a).SequenceEqual(val);
if (!isEqual)
{
_dasFactory.TDASHostNames = value;
}
}
}
}
public string[] SPFDHostNames
{
get => _dasFactory.SPFDHostNames;
set
{
if (null == value || null == _dasFactory.SPFDHostNames)
{
_dasFactory.SPFDHostNames = value;
}
else
{
var val = value.Distinct().OrderBy(a => a);
// this has side effects, so only change if needed
var isEqual = _dasFactory.SPFDHostNames.OrderBy(a => a).SequenceEqual(val);
if (!isEqual)
{
_dasFactory.SPFDHostNames = value;
}
}
}
}
/// <summary>
/// http://fogbugz/fogbugz/default.asp?2903
/// could also be called SliceDbHostNames
/// </summary>
public string[] SDBHostNames
{
get { return _dasFactory.SliceDBHostNames; }
set
{
if (_bInRefresh)
{
#if LOG_DEBUG_REFRESH
var st = new StackTrace(true);
APILogger.Log("SDBHostNames SET WHILE WE ARE IN REFRESH\n", st.ToString());
#endif
}
if (null == value || null == _dasFactory.SliceDBHostNames)
{
_dasFactory.SliceDBHostNames = value;
}
else
{
// this has side effects, so only change if needed
var equals = _dasFactory.SliceDBHostNames.OrderBy(a => a).SequenceEqual(value.OrderBy(a => a));
if (false == equals)
{
_dasFactory.SliceDBHostNames = value;
}
}
}
}
public SortableBindingList<IDiscoveredDevice> AutoDiscoverMulticast()
{
CancellationToken ct = new CancellationToken();
return _dasFactory.AutoDiscoverMulticast(ct);
}
public delegate void DiscoveredDASEventHandler(object sender, IEnumerable<IDiscoveredDevice> newDevices);
public event DiscoveredDASEventHandler DiscoveredDAS;
public void DiscoveryThread(DFConstantsAndEnums.MultiCastDeviceClasses[] deviceFilter, CancellationToken ct, bool discoverParents = true)
{
while (!ct.IsCancellationRequested)
{
var discoveries = _dasFactory.AutoDiscoverMulticast(ct, discoverParents).ToList();
var filteredDiscoveries = deviceFilter?.Count() > 0 ? discoveries.Where(idd => deviceFilter.Contains(idd.DevClass)).ToList() : discoveries;
DiscoveredDAS.Invoke(this, filteredDiscoveries);
ct.WaitHandle.WaitOne(1000);
}
}
/// <summary>
/// starts qats listening
/// </summary>
public void StartQATSListening()
{
_dasFactory.StartQATSListening();
}
/// <summary>
/// stops any QATS listening
/// </summary>
public void StopQATSListening()
{
_dasFactory.StopQATSListening();
}
/// <summary>
/// sends the request to send UDP QATS messages
/// </summary>
public void SendQATSRequest()
{
_dasFactory.SendQATSRequest();
}
/// <summary>
/// returns any QueryArmTriggerStatus that are waiting and clears the list of waiting QATS
/// </summary>
/// <returns></returns>
public IUDPQATSEntry[] GetQATS()
{
return _dasFactory.GetQATS();
}
/// <summary>
/// configures the default timeout for multicast autodiscovery receive timeout
/// in ms
/// </summary>
public int MulticastAutoDiscoveryReceiveTimeoutMS
{
get => _dasFactory.MultiCastAutoDiscoveryDefaultTimeoutMS;
set => _dasFactory.MultiCastAutoDiscoveryDefaultTimeoutMS = value;
}
/// <summary>
/// configures the trasmit address for multicast autodiscovery
/// in ms
/// </summary>
public string MulticastAutoDiscoveryAddress
{
get => _dasFactory.MulticastAutoDiscoveryAddress;
set => _dasFactory.MulticastAutoDiscoveryAddress = value;
}
/// <summary>
/// configures the trasmit port for multicast autodiscovery
/// in ms
/// </summary>
public int MulticastAutoDiscoveryPort
{
get => _dasFactory.MulticastAutoDiscoveryPort;
set => _dasFactory.MulticastAutoDiscoveryPort = value;
}
/// <summary>
/// configures the receive port for multicast autodiscovery
/// in ms
/// </summary>
public int MulticastAutoDiscoveryResponsePort
{
get => _dasFactory.MulticastAutoDiscoveryResponsePort;
set => _dasFactory.MulticastAutoDiscoveryResponsePort = value;
}
public double S6ConnectNewTimeout
{
get => _dasFactory.S6ConnectNewTimeout;
set => _dasFactory.S6ConnectNewTimeout = value;
}
/// <summary>
/// I noticed in some logs - only one refresh at a time
/// refresh is getting called multiple times for some reason.
/// This gets a bit sticky, there's no reason for Refresh to be called more than once while refresh is still running,
/// however one of the options for refresh is to not wait for it to finish, which means you could get into this situation.
/// I haven't been able to duplicate this problem myself, so I'm going to add some logging to help find out how we got there if we
/// get there again, and also some code to address when we do find ourselves there
///
/// </summary>
private volatile bool _bInRefresh;
public void Refresh(bool wait)
{
APILogger.Log(APILogger.GetCurrentMethod());
if (_bInRefresh)
{
#if LOG_DEBUG_REFRESH
var st = new StackTrace(true);
APILogger.Log(string.Format("Warning - OVERLAPPING REFRESH CALL!\nStackTrace:\n{0}", st));
#endif
}
else
{
var mre = new ManualResetEvent(false);
try
{
_bInRefresh = true;
_dasFactory.Refresh(delegate
{
mre.Set();
_bInRefresh = false;
});
}
catch (Exception ex)
{
APILogger.Log("Exception refreshing ", ex.Message);
return;
}
if (wait)
{
mre.WaitOne();
}
}
}
public List<IDASCommunication> GetActiveDevices()
{
return _dasFactory.GetDASList();
}
/// <summary>
/// 14157 Refresh/Stability Issue in Data Acquisition Tile
/// returns the list of all devices known connectable via ECM/SDB/S6DB
/// these distributors have a HELLO SLICEBASE x.x.x.x:yyyy format, so
/// we keep track of what the db told us
/// this returns all the reported connections
/// </summary>
public string[] GetReportedConnections()
{
return _dasFactory.GetConnectedDevices();
}
/// <summary>
/// runs auto discovery if needed, populating the downstream mac addresses for all attached SLICE6/SLICE6Db devices
/// completes immediately if there are no attached SLICE6/SLICE6Db devices
/// </summary>
public void AutoDiscoverIfNecessary()
{
var foundDas = ApplicationProperties.DASFactory.GetActiveDevices();
var bNeedToRun = foundDas.OfType<EthernetSlice6DB>().Any();
if (!bNeedToRun) return;
var ipAddressToIdas = new Dictionary<string, IDASCommunication>();
foreach (var das in foundDas)
{
var h = new DASHardware(das);
var connection = h.ConnectionUSBAware.ToLower();
if (connection.Contains("usb"))
{
connection = h.SerialNumber;
}
ipAddressToIdas[connection] = das;
}
try
{
var units = ApplicationProperties.DASFactory.AutoDiscoverMulticast();
var macAddresToDevice = new Dictionary<string, IDiscoveredDevice>();
foreach (var unit in units)
{
var mac = unit.Mac.Replace('-', ':').ToUpper();
macAddresToDevice[mac] = unit;
}
foreach (var unit in units)
{
if (!ipAddressToIdas.ContainsKey(unit.Ip))
{
continue;
}
ipAddressToIdas[unit.Ip].MACAddress = unit.Mac;
ipAddressToIdas[unit.Ip].DownstreamMACAddresses = (from c in unit.Connections
select c.MACAddress.Replace('-', ':').ToUpper()
into childMac
where macAddresToDevice.ContainsKey(childMac)
select macAddresToDevice[childMac]
into childDevice
select childDevice.Mac).ToArray();
}
}
catch (Exception ex)
{
APILogger.Log(ex);
}
}
/// <summary>
/// returns true if the DAS is streaming
/// </summary>
/// <param name="das"></param>
/// <returns></returns>
public static bool IsStreaming(IDASCommunication das)
{
if (!(das is EthernetSlice6Air)) { return false; }
if (null == das.DASArmStatus)
{
return false;
}
//15932 Error when performing test when S6A is streaming
//don't currently know of a better way to determine if the unit is streaming or not
//when attaching to a stream device some attributes can't be read and because of timing
//some status may not be populated, but if we aren't armed or in realtime and we are a S6A and
//we responded with Invalid mode during setup, we are _probably_ streaming
return !das.DASArmStatus.IsArmed && !das.DASArmStatus.IsInRealtime &&
das.DASArmStatus.ReceivedInvalidModeDuringSetup;
}
/// <summary>
/// returns true if the unit is in realtime
/// uses DASArmStatus, but this isn't always populated on time, so if the unit is streaming it will also
/// return true, which seems to be consistent with what was intended in the old code
/// 15932 Error when performing test when S6A is streaming
/// </summary>
/// <param name="das"></param>
/// <returns></returns>
public static bool IsInRealtime(IDASCommunication das)
{
if (null == das.DASArmStatus) { return false; }
return das.DASArmStatus.IsInRealtime || IsStreaming(das);
}
/// <summary>
/// returns true if any of the units in the input parameters are in realtime or are streaming
/// </summary>
/// <param name="das"></param>
/// <returns></returns>
public static bool AnyInRealtime(List<IDASCommunication> das)
{
return das.Exists(unit => IsInRealtime(unit));
}
}
}