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 { /// /// starts the auto discovery process if it's not running yet /// public void StartMulticastAutoDiscovery() { _dasFactory.StartMulticastAutoDiscovery(); } /// /// stops the auto discovery process if it is running /// public void StopMulticastAutoDiscovery() { _dasFactory.StopMulticastAutoDiscovery(); } /// /// returns any discovered devices /// /// 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; } } } } /// /// http://fogbugz/fogbugz/default.asp?2903 /// could also be called SliceDbHostNames /// 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 AutoDiscoverMulticast() { CancellationToken ct = new CancellationToken(); return _dasFactory.AutoDiscoverMulticast(ct); } public delegate void DiscoveredDASEventHandler(object sender, IEnumerable 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); } } /// /// starts qats listening /// public void StartQATSListening() { _dasFactory.StartQATSListening(); } /// /// stops any QATS listening /// public void StopQATSListening() { _dasFactory.StopQATSListening(); } /// /// sends the request to send UDP QATS messages /// public void SendQATSRequest() { _dasFactory.SendQATSRequest(); } /// /// returns any QueryArmTriggerStatus that are waiting and clears the list of waiting QATS /// /// public IUDPQATSEntry[] GetQATS() { return _dasFactory.GetQATS(); } /// /// configures the default timeout for multicast autodiscovery receive timeout /// in ms /// public int MulticastAutoDiscoveryReceiveTimeoutMS { get => _dasFactory.MultiCastAutoDiscoveryDefaultTimeoutMS; set => _dasFactory.MultiCastAutoDiscoveryDefaultTimeoutMS = value; } /// /// configures the trasmit address for multicast autodiscovery /// in ms /// public string MulticastAutoDiscoveryAddress { get => _dasFactory.MulticastAutoDiscoveryAddress; set => _dasFactory.MulticastAutoDiscoveryAddress = value; } /// /// configures the trasmit port for multicast autodiscovery /// in ms /// public int MulticastAutoDiscoveryPort { get => _dasFactory.MulticastAutoDiscoveryPort; set => _dasFactory.MulticastAutoDiscoveryPort = value; } /// /// configures the receive port for multicast autodiscovery /// in ms /// public int MulticastAutoDiscoveryResponsePort { get => _dasFactory.MulticastAutoDiscoveryResponsePort; set => _dasFactory.MulticastAutoDiscoveryResponsePort = value; } public double S6ConnectNewTimeout { get => _dasFactory.S6ConnectNewTimeout; set => _dasFactory.S6ConnectNewTimeout = value; } /// /// 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 /// /// 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 GetActiveDevices() { return _dasFactory.GetDASList(); } /// /// 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 /// public string[] GetReportedConnections() { return _dasFactory.GetConnectedDevices(); } /// /// 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 /// public void AutoDiscoverIfNecessary() { var foundDas = ApplicationProperties.DASFactory.GetActiveDevices(); var bNeedToRun = foundDas.OfType().Any(); if (!bNeedToRun) return; var ipAddressToIdas = new Dictionary(); 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(); 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); } } /// /// returns true if the DAS is streaming /// /// /// 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; } /// /// 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 /// /// /// public static bool IsInRealtime(IDASCommunication das) { if (null == das.DASArmStatus) { return false; } return das.DASArmStatus.IsInRealtime || IsStreaming(das); } /// /// returns true if any of the units in the input parameters are in realtime or are streaming /// /// /// public static bool AnyInRealtime(List das) { return das.Exists(unit => IsInRealtime(unit)); } } }