using DTS.Common.Enums; using DTS.Common.Enums.Sensors; using DTS.Common.Interface.DASFactory; using DTS.Common.Interface.DASFactory.Config; using DTS.Common.Interface.DataRecorders; using DTS.Common.Interface.StatusAndProgressBar; using DTS.Common.Utilities.Logging; using DTS.Common.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace DTS.DASLib.Service.StateMachine { public class HardwareDiscoveryStatusInfo : IStatusInfo { /// /// if we were told of DAS to connect to, /// this will be true if all the das were connected /// public bool AllDASFound { get; set; } = false; /// /// if any of the units we connected to were in Armed state, this will be true /// public bool SomeUnitsInArmState { get; set; } = false; /// /// list of all devices we successfully connected to /// [note all IDASCommunication are references, not copies] /// public IDASCommunication[] UnitsConnected { get; private set; } = new IDASCommunication[0]; /// /// list of all devices we successfully got the configuration of /// [note all IDASCommunication are references, not copies] /// public IDASCommunication[] UnitsWithConfiguration { get; private set; } = new IDASCommunication[0]; /// /// all possible states the /// public enum StatusValues { Pinging, Connecting, Cancelling, Cancelled, Complete, UDPUnavailable, UnitIsArmed, AllUnitsFound, NotAllUnitsFound, MulticastDiscovery, DiscoveredDevice, PingStatus, Connected, FailedToGetConfiguration, UnitGotConfiguration, GettingConfiguration, AutoSense, AutoSenseFailed, AutoSenseSuccess, AutoSenseComplete, FailedHardwareChecks, PerformingHardwareChecks, G5DockLost, CheckingDASStates, UnitIsAutoArmed, UnitIsReadyToStream, UnitIsInRealtime, UnitIsStreaming, UnitNotInDb, UnitHasWrongFirmware, CalDateExpired, UnitNANDIssue, UnitWithWrongMaxMemory, UnitWithWrongChannelCount, UnitWithChannelTypeIssue, ExtraUnit, UnitIsInArm, VoltageCheckFinished, VoltageCheckSuccess, VoltageCheckFailure } /// /// signals cancel was requested /// private ManualResetEvent CancelEvent = new ManualResetEvent(false); /// /// signals when ping and connect is finished /// internal ManualResetEvent DoneEvent = new ManualResetEvent(false); /// /// action to take on completion of ping and connect /// public ActionCompleteDelegate CompleteAction { get; set; } /// /// action to take on progress /// public SetProgressValueDelegate ProgressAction { get; set; } /// /// action to take when status is notified /// public StatusIntDelegate StatusAction { get; set; } /// /// action to take when extended status is notified /// public StatusExIntDelegate StatusExAction { get; set; } /// /// this is an array of units that were detected without docks that should have had docks /// public IDASCommunication[] UnitsWithLostDocks { get; private set; } = new IDASCommunication[0]; /// /// this is an array of units that were expected but not available /// public IDASHardware[] UnitsMissing { get; private set; } = new IDASHardware[0]; private void AddMissingHardware(IDASHardware das) { if (UnitsMissing.Contains(das)) { return; } var list = new List(); list.AddRange(UnitsMissing); list.Add(das); UnitsMissing = list.ToArray(); StatusExAction?.Invoke((int)StatusValues.NotAllUnitsFound, das); } /// /// these are units which are connected but are not in the db /// public IDASCommunication[] UnitsNotInDb { get; private set; } = new IDASCommunication[0]; private void AddUnitNotInDb(IDASCommunication das) { if (UnitsNotInDb.Contains(das)) { return; } var list = new List(); list.AddRange(UnitsNotInDb); list.Add(das); UnitsNotInDb = list.ToArray(); StatusExAction?.Invoke((int)StatusValues.UnitNotInDb, das); } /// /// this is an array of units that were detected with auto arm set /// public IDASCommunication[] UnitsInAutoArm { get; private set; } = new IDASCommunication[0]; /// /// adds a unit into the list of units that are in autoarm state /// /// private void AddUnitInAutoArm(IDASCommunication das) { if (UnitsInAutoArm.Contains(das)) { return; }//already present var list = new List(); list.AddRange(UnitsInAutoArm); list.Add(das); UnitsInAutoArm = list.ToArray(); StatusExAction?.Invoke((int)StatusValues.UnitIsAutoArmed, das); } /// /// this is an array of units that were detected ready to stream (S6A) /// public IDASCommunication[] UnitsReadyToStream { get; private set; } = new IDASCommunication[0]; private void AddUnitReadyToStream(IDASCommunication das) { if (UnitsReadyToStream.Contains(das)) { return; //already contains it } var list = new List(); list.AddRange(UnitsReadyToStream); list.Add(das); UnitsReadyToStream = list.ToArray(); StatusExAction?.Invoke((int)StatusValues.UnitIsReadyToStream, das); } public IDASCommunication[] UnitsStreaming { get; private set; } = new IDASCommunication[0]; private void AddUnitStreaming(IDASCommunication das) { if (UnitsStreaming.Contains(das)) { return; //already contains it ... } var list = new List(); list.Add(das); UnitsStreaming = list.ToArray(); StatusExAction?.Invoke((int)StatusValues.UnitIsStreaming, das); } //public IDASCommunication[] UnitsInArm { get; private set; } = new IDASCommunication [0]; //private void AddUnitInArm(IDASCommunication das) //{ // if( UnitsInArm.Contains(das) ){ return; } // var list = new List(); // list.AddRange(UnitsInArm); // list.Add(das); // UnitsInArm = list.ToArray(); // StatusExAction?.Invoke((int) StatusValues.UnitIsArmed, das); //} //public IDASCommunication[] UnitsInRealtime { get; private set; } = new IDASCommunication[0]; //private void AddUnitInRealtime(IDASCommunication das) //{ // if( UnitsInRealtime.Contains(das)){ return; } //already contains it // var list = new List(); // list.AddRange(UnitsInRealtime); // list.Add(das); // UnitsInRealtime = list.ToArray(); // StatusExAction?.Invoke((int) StatusValues.UnitIsInRealtime, das); //} /// /// this array holds any units that have firmware of a different version than expected /// public IDASCommunication[] UnitsWithWrongFirmware { get; private set; } = new IDASCommunication[0]; private void AddUnitWithWrongFirmware(IDASCommunication das) { if (UnitsWithWrongFirmware.Contains(das)) { return; } var list = new List(); list.AddRange(UnitsWithWrongFirmware); list.Add(das); UnitsWithWrongFirmware = list.ToArray(); StatusExAction?.Invoke((int)StatusValues.UnitHasWrongFirmware, das); } /// /// this array holds any units that have expired cal dates /// public IDASCommunication[] UnitsWithExpiredCalDates { get; private set; } = new IDASCommunication[0]; private void AddUnitWithExpiredCalDate(IDASCommunication das) { if (UnitsWithExpiredCalDates.Contains(das)) { return; } var list = new List(); list.AddRange(UnitsWithExpiredCalDates); UnitsWithExpiredCalDates = list.ToArray(); StatusExAction?.Invoke((int)StatusValues.CalDateExpired, das); } /// /// this array holds any units with NAND/Flash issues /// public IDASCommunication[] UnitsWithNANDIssues { get; private set; } = new IDASCommunication[0]; private void AddUnitWithNANDIssue(IDASCommunication das) { if (UnitsWithNANDIssues.Contains(das)) { return; } var list = new List(); list.AddRange(UnitsWithNANDIssues); list.Add(das); UnitsWithNANDIssues = list.ToArray(); StatusExAction?.Invoke((int)StatusValues.UnitNANDIssue, das); } public IDASCommunication[] UnitsWithWrongMaxMemory { get; private set; } = new IDASCommunication[0]; private void AddUnitWithWrongMaxMemory(IDASCommunication das) { if (UnitsWithWrongMaxMemory.Contains(das)) { return; } var list = new List(); list.AddRange(UnitsWithWrongMaxMemory); list.Add(das); UnitsWithWrongMaxMemory = list.ToArray(); StatusExAction?.Invoke((int)StatusValues.UnitWithWrongMaxMemory, das); } public IDASCommunication[] UnitsWithWrongChannelCount { get; private set; } = new IDASCommunication[0]; private void AddUnitChannelCountIssue(IDASCommunication das) { if (UnitsWithWrongChannelCount.Contains(das)) { return; } var list = new List(); list.AddRange(UnitsWithWrongChannelCount); list.Add(das); UnitsWithWrongChannelCount = list.ToArray(); StatusExAction?.Invoke((int)StatusValues.UnitWithWrongChannelCount, das); } /// /// this array holds units where the channel types don't match expectations, /// so either supports different bridges or excitation /// public IDASCommunication[] UnitsWithChannelTypeIssue { get; private set; } = new IDASCommunication[0]; private void AddUnitWithChannelTypeIssue(IDASCommunication das) { if (UnitsWithChannelTypeIssue.Contains(das)) { return; } var list = new List(); list.AddRange(UnitsWithChannelTypeIssue); list.Add(das); UnitsWithChannelTypeIssue = list.ToArray(); StatusExAction?.Invoke((int)StatusValues.UnitWithChannelTypeIssue, das); } /// /// indicates there are hardware channels associated with the test that are available /// public bool HaveChannels { get; private set; } = false; /// /// this is an array of units that are connected which were not expected /// public IDASCommunication[] ExtraUnits { get; private set; } = new IDASCommunication[0]; private void AddExtraUnit(IDASCommunication das) { if (ExtraUnits.Contains(das)) { return; } var list = new List(); list.AddRange(ExtraUnits); list.Add(das); ExtraUnits = list.ToArray(); StatusExAction?.Invoke((int)StatusValues.ExtraUnit, das); } /// /// this keeps track of the ping task that is run when ping starts /// private Task _PingTask = null; /// /// this keeps track of the RequeryDevice task taht is running when RequeryDevice starts /// private Task _RequeryTask = null; /// /// sets the cancel event, waits for done to be signaled, /// sets status cancelled /// /// public async Task Cancel() { CancelEvent.Set(); StatusAction?.Invoke((int)StatusValues.Cancelling); //simulate time spent waiting for cancel to be acknowledged Thread.Sleep(100); DoneEvent.WaitOne(); StatusAction?.Invoke((int)StatusValues.Cancelled); } /// /// returns true if multicast should be run /// /// private bool DoMultiCast() { return States.Instance.HardwareDiscoveryStart.Status.GlobalStatusParameters.AllowUDPMulticast && States.Instance.HardwareDiscoveryStart.Status.HardwareDiscoveryParams.UseMulticastDiscover; } private const int MULTICAST_DISCOVERY_INTERVAL = 100; private const int EXPECTED_MULTICAST_TIME = 6000; /// /// performs the multi cast discovery via UDP /// private void DoMulticastDiscovery() { _discoveredDevices.Clear(); StatusAction.Invoke((int)StatusValues.MulticastDiscovery); try { if (null != States.Instance.HardwareDiscoveryStart.DASFactory) { var mre = new ManualResetEvent(false); Task.Run(() => { var ct = new CancellationToken(); var discovered = States.Instance.HardwareDiscoveryStart.DASFactory.AutoDiscoverMulticast(ct); if (discovered.Any()) { foreach (var device in discovered) { AddDevice(device); } } mre.Set(); }); var timeWaited = 0; while (!mre.WaitOne(MULTICAST_DISCOVERY_INTERVAL, false)) { timeWaited += MULTICAST_DISCOVERY_INTERVAL; var percent = 100D * timeWaited / EXPECTED_MULTICAST_TIME; percent = Math.Min(percent, 100); ProgressAction?.Invoke(percent); } } } catch (Exception ex) { APILogger.Log(ex); StatusAction?.Invoke((int)StatusValues.UDPUnavailable); } } /// /// holds all the devices discovered via UDP/multicast /// private List _discoveredDevices = new List(); /// /// adds a device to the list of multicast discovered devices /// signals consumer that a device was discovered /// /// private void AddDevice(IDiscoveredDevice device) { _discoveredDevices.Add(device); StatusExAction?.Invoke((int)StatusValues.DiscoveredDevice, device); } /// /// returns a list of all ips that we should try to ping and then potentially connect to /// /// private IList GetIpList() { var list = new List(); var param = States.Instance.HardwareDiscoveryStart.Status.HardwareDiscoveryParams; if (null != param.Addresses) { list.AddRange(param.Addresses); } if (param.AddressRanges.Any()) { foreach (var addressRange in param.AddressRanges) { try { var start = GetIPAddress(addressRange.Item1); var end = GetIPAddress(addressRange.Item2); //for now just assume we only care about the last byte ...? for (var lastByte = start[3]; lastByte < end[3]; lastByte++) { list.Add($"{start[0]}.{start[1]}.{start[2]}.{lastByte}"); } } catch (Exception ex) { APILogger.Log($"skipping address range {addressRange.Item1}-{addressRange.Item2} {ex}"); } } } return list; } /// /// converts a string in the form of aaa.bbb.ccc.ddd to byte,byte,byte,byte /// throws invalidcastexception if not in the right form /// /// /// private byte[] GetIPAddress(string ip) { var tokens = ip.Split(new[] { '.' }); if (4 == tokens.Length) { return new[] { Convert.ToByte(tokens[0]), Convert.ToByte(tokens[1]), Convert.ToByte(tokens[2]), Convert.ToByte(tokens[3]) }; } throw new InvalidCastException($"{ip} is not in the form of aaa.bbb.ccc.ddd"); } /// /// gets list of all known tdas ip addresses /// an ip address is known as ip if we've auto discovered it or we were told ahead of time it is TDAS /// knowing that an ip address is slice or TDAS can save us having to try connecting as both /// /// private HashSet GetTDASIPHash() { var hash = new HashSet(); var param = States.Instance.HardwareDiscoveryStart.Status.HardwareDiscoveryParams; if (null != param.KnownTDASIPAddresses) { foreach (var ip in param.KnownTDASIPAddresses) { hash.Add(ip); } } return hash; } /// /// returns all known slice ip addresses /// an ip address is known as slice if we were told about it ahead of time or we've auto discovered it /// knowning that an ip address is slice or TDAS can save us having to try connecting as both /// /// private HashSet GetSLICEIPHash() { var hash = new HashSet(); var param = States.Instance.HardwareDiscoveryStart.Status.HardwareDiscoveryParams; if (null != param.KnownSLICEIPAddresses) { foreach (var ip in param.KnownSLICEIPAddresses) { hash.Add(ip); } } foreach (var device in _discoveredDevices) { switch (device.DevClass) { case Common.Enums.DASFactory.DFConstantsAndEnums.MultiCastDeviceClasses.Slice6: case Common.Enums.DASFactory.DFConstantsAndEnums.MultiCastDeviceClasses.SDB: case Common.Enums.DASFactory.DFConstantsAndEnums.MultiCastDeviceClasses.ECM: case Common.Enums.DASFactory.DFConstantsAndEnums.MultiCastDeviceClasses.S6DB: case Common.Enums.DASFactory.DFConstantsAndEnums.MultiCastDeviceClasses.Slice6Air: case Common.Enums.DASFactory.DFConstantsAndEnums.MultiCastDeviceClasses.PowerPro: //all these devices fall into the slice communication family var ip = device.Ip.ToLower(); if (!hash.Contains(ip)) { hash.Add(ip); } break; //these are probably slice, but we don't technically know case Common.Enums.DASFactory.DFConstantsAndEnums.MultiCastDeviceClasses.Unknown: case Common.Enums.DASFactory.DFConstantsAndEnums.MultiCastDeviceClasses.NextOne: case Common.Enums.DASFactory.DFConstantsAndEnums.MultiCastDeviceClasses.Any: default: break; } } return hash; } /// /// requires a specific device, returns immediately /// internal void RequeryDevice() { _RequeryTask = Task.Run(() => { var param = States.Instance.HardwareDiscoveryStart.Status.HardwareDiscoveryParams; var global = States.Instance.HardwareDiscoveryStart.Status.GlobalStatusParameters; var dasFactory = States.Instance.HardwareDiscoveryStart.DASFactory; if (!CancelEvent.WaitOne(1, false)) { if (GetConfiguration(dasFactory)) { CompleteAction?.Invoke(); } else { //getconfiguration failed ... StatusAction?.Invoke((int)StatusValues.FailedToGetConfiguration); } } StatusAction?.Invoke((int)StatusValues.Complete); DoneEvent.Set(); }); } private void CheckUnitsNotInDb() { var param = States.Instance.HardwareDiscoveryStart.Status.HardwareDiscoveryParams; foreach (var unit in UnitsWithConfiguration) { if (!param.UnitIsInDbQuery(unit)) { AddUnitNotInDb(unit); } } } private void CheckFirmware() { var param = States.Instance.HardwareDiscoveryStart.Status.HardwareDiscoveryParams; foreach (var unit in UnitsWithConfiguration) { if (param.UnitIsInDbQuery(unit)) { if (unit.FirmwareVersion != param.UnitExpectedFirmwareQuery(unit)) { AddUnitWithWrongFirmware(unit); } } } } private void CheckChannelCounts() { var param = States.Instance.HardwareDiscoveryStart.Status.HardwareDiscoveryParams; foreach (var das in UnitsWithConfiguration) { if (das.IsEthernetDistributor()) { continue; } if (null == das.ConfigData.Modules) { AddUnitChannelCountIssue(das); continue; } var matches = from hardware in param.ExpectedHardware where hardware.SerialNumber == das.SerialNumber select hardware; if (!matches.Any()) { continue; } //doesn't match any expected hardware in our test var expected = matches.First(); var channels = expected.GetIHardwareChannels(); var idaschannels = new List(); foreach (var module in das.ConfigData.Modules) { idaschannels.AddRange(module.Channels); } if (channels.Length != das.NumberOfChannels()) { AddUnitChannelCountIssue(das); } else { var isOK = true; for (var i = 0; i < channels.Length && isOK; i++) { var idaschannel = idaschannels[i]; switch (idaschannel) { case AnalogInputDASChannel _aic: if (_aic.DigitalInputChannel) { if (!channels[i].IsDigitalIn) { isOK = false; } else { isOK = CheckAnalogChannel(_aic, channels[i]); } } break; case OutputSquibChannel _squib: if (!channels[i].IsSquib) { isOK = false; } break; case OutputTOMDigitalChannel _dOut: break; case TimestampDASChannel _time: break; } } if (!isOK) { AddUnitWithChannelTypeIssue(das); } } } } private bool CheckAnalogChannel(AnalogInputDASChannel aic, IHardwareChannel hardwareChannel) { var analogBridges = new[] { SensorConstants.BridgeType.FullBridge, SensorConstants.BridgeType.HalfBridge, SensorConstants.BridgeType.HalfBridge_SigPlus, SensorConstants.BridgeType.IEPE, SensorConstants.BridgeType.QuarterBridge }; foreach (var bridge in analogBridges) { var aicSupports = aic.SupportedBridges.Contains(bridge); var hardwareChannelSupports = hardwareChannel.IsSupportedBridgeType(bridge); if (aicSupports != hardwareChannelSupports) { return false; } } var excitations = new[] { ExcitationVoltageOptions.ExcitationVoltageOption.Volt10, ExcitationVoltageOptions.ExcitationVoltageOption.Volt2_5, ExcitationVoltageOptions.ExcitationVoltageOption.Volt2, ExcitationVoltageOptions.ExcitationVoltageOption.Volt5 }; foreach (var excitation in excitations) { var aicSupports = aic.SupportedExcitation.Contains(excitation); var hardwareChannelSupports = hardwareChannel.IsSupportedExcitation(excitation); if (aicSupports != hardwareChannelSupports) { return false; } } return true; } private void CheckCalDates() { var param = States.Instance.ConfigureStart.Status.HardwareDiscoveryParams; foreach (var unit in UnitsWithConfiguration) { if (param.CalDateExpiredQuery(unit)) { AddUnitWithExpiredCalDate(unit); } } } private void CheckModuleArrayIndices() { var param = States.Instance.ConfigureStart.Status.HardwareDiscoveryParams; foreach (var das in UnitsWithConfiguration) { if (!(das is EthernetTDAS ethernetTDAS)) { continue; } var matches = from unit in param.ExpectedHardware where unit.SerialNumber == das.SerialNumber select unit; if (!matches.Any()) { continue; } if (null == das.ConfigData || null == das.ConfigData.Modules) { continue;//caught by other checks we only want to check module array indices } var expected = matches.First(); var expectedChannels = expected.GetIHardwareChannels(); var channelIDX = 0; var ok = true; foreach (var m in das.ConfigData.Modules) { if (!ok) { break; } foreach (var channel in m.Channels) { var expectedChannel = expectedChannels[channelIDX]; if (m.SerialNumber() != expectedChannel.ModuleSerialNumber) { ok = false; break; } channelIDX++; } } if (!ok) { AddUnitWithChannelTypeIssue(das); } } } private void CheckNAND() { foreach (var das in UnitsWithConfiguration) { if (das is EthernetSlice6 || das is EthernetSlice6Air || das is EthernetSlice6AirBridge) { continue; } if (0 == das.MaxMemory()) { AddUnitWithNANDIssue(das); } } } private void CheckStorage() { var param = States.Instance.HardwareDiscoveryStart.Status.HardwareDiscoveryParams; foreach (var das in UnitsWithConfiguration) { if (!param.UnitIsInDbQuery(das)) { continue; } var actualMaxMemory = das.MaxMemory(); var expectedMaxMemory = param.UnitExpectedMaxMemoryQuery(das); if (0 == expectedMaxMemory) { param.UpdateMaxMemoryAction(das); } else if (expectedMaxMemory != actualMaxMemory) { AddUnitWithWrongMaxMemory(das); } } } private void CheckHaveChannels() { HaveChannels = false; var param = States.Instance.HardwareDiscoveryStart.Status.HardwareDiscoveryParams; foreach (var expected in param.ExpectedHardware) { var matches = from unit in UnitsWithConfiguration where unit.SerialNumber == expected.SerialNumber select unit; if (!matches.Any()) { continue; } var match = matches.First(); if (match.NumberOfChannels() > 1) { HaveChannels = true; break; } } } private void CheckExtraUnits() { var param = States.Instance.HardwareDiscoveryStart.Status.HardwareDiscoveryParams; foreach (var unit in UnitsConnected) { var matches = from expected in param.ExpectedHardware where expected.SerialNumber == unit.SerialNumber select expected; if (!matches.Any()) { AddExtraUnit(unit); } } } /// /// returns true if all hardware passes tests /// returns false if any of the hardware fails a test /// /// private bool DoHardwareChecks() { StatusAction?.Invoke((int)StatusValues.PerformingHardwareChecks); DoG5LostDockCheck(); CheckDASStates(); CheckUnitsNotInDb(); CheckFirmware(); CheckCalDates(); CheckModuleArrayIndices(); CheckNAND(); CheckStorage(); CheckHaveChannels(); CheckExtraUnits(); return true; } /// /// goes through all das which have a configuration and checks to see if we have any missing hardware /// private void GetMissingHardware() { var param = States.Instance.HardwareDiscoveryStart.Status.HardwareDiscoveryParams; foreach (var expected in param.ExpectedHardware) { if (!Array.Exists(UnitsWithConfiguration, das => das.SerialNumber == expected.SerialNumber)) { AddMissingHardware(expected); } } } /// /// determines if any units have auto arm set /// private void GetAutoArmed() { var mre = new ManualResetEvent(false); try { using (var armingService = new ArmingService()) { var list = UnitsWithConfiguration.ToList(); armingService.GetAutoArmStatus(list, (ServiceBase.CallbackData data) => { switch (data.Status) { case ServiceBase.CallbackData.CallbackStatus.AllFinished: mre.Set(); break; case ServiceBase.CallbackData.CallbackStatus.Failure: StatusExAction?.Invoke((int)StatusValues.UnitIsAutoArmed, data.Target, data.ErrorException, data.ErrorMessage); break; } }, list); } } catch (Exception ex) { APILogger.Log(ex); mre.Set(); } mre.WaitOne(); foreach (var unit in UnitsWithConfiguration) { if (unit.AutoArmed) { AddUnitInAutoArm(unit); if (unit.ConfigData.Modules[0].RecordingMode == Common.Enums.DASFactory.DFConstantsAndEnums .RecordingMode.S6A_DeviceStreamingOnly) { AddUnitReadyToStream(unit); } } } } private const int INPUT_VOLTAGE_CUTOFF = 11000; //no idea where this came from ... stole it from the existing call... /// /// determines if any units are currently armed /// private void GetArmed() { var mre = new ManualResetEvent(false); using (var armService = new ArmingService()) { try { var list = UnitsWithConfiguration.ToList(); armService.GetArmStatus(list, (ServiceBase.CallbackData data) => { switch (data.Status) { case ServiceBase.CallbackData.CallbackStatus.AllFinished: mre.Set(); break; case ServiceBase.CallbackData.CallbackStatus.Failure: break; } }, list, INPUT_VOLTAGE_CUTOFF); } catch (Exception ex) { APILogger.Log(ex); StatusExAction?.Invoke((int)StatusValues.UnitIsArmed, ex); mre.Set(); } } mre.WaitOne(); var global = States.Instance.HardwareDiscoveryStart.Status.GlobalStatusInformation; foreach (var das in UnitsWithConfiguration) { if (das.DASArmStatus.IsArmed) { global.AddUnitInArm(das); StatusExAction?.Invoke((int)StatusValues.UnitIsArmed, das); } } } /// /// checks the das state for all das /// private void CheckDASStates() { StatusAction?.Invoke((int)StatusValues.CheckingDASStates); if (!UnitsWithConfiguration.Any()) { return; } ProgressAction?.Invoke(0); GetMissingHardware(); ProgressAction?.Invoke(25); GetAutoArmed(); ProgressAction?.Invoke(50); GetArmed(); ProgressAction?.Invoke(75); GetRealtime(); ProgressAction?.Invoke(100); } /// /// determines if any units are in realtime /// private void GetRealtime() { var global = States.Instance.HardwareDiscoveryStart.Status.GlobalStatusInformation; foreach (var das in UnitsWithConfiguration) { if (null != das.DASArmStatus && das.DASArmStatus.IsInRealtime) { global.AddUnitInRealtime(das); StatusExAction?.Invoke((int)StatusValues.UnitIsInRealtime, das); if (das.ConfigData.Modules[0].RecordingMode == Common.Enums.DASFactory.DFConstantsAndEnums .RecordingMode.S6A_DeviceStreamingOnly) { AddUnitStreaming(das); } } } } /// /// checks if any G5 have lost docks, this is a condition where the G5 does not respond with dock, but we /// expect it should ... /// private void DoG5LostDockCheck() { var param = States.Instance.HardwareDiscoveryStart.Status.HardwareDiscoveryParams; var indummyG5s = new List(); foreach (var das in UnitsWithConfiguration) { var type = das.GetHardwareType(); if (type == Common.Enums.Hardware.HardwareTypes.G5INDUMMY) { indummyG5s.Add(das); } } if (indummyG5s.Any()) { foreach (var g5 in indummyG5s) { var matches = from das in param.ExpectedHardware where das.SerialNumber == g5.SerialNumber select das; if (matches.Any()) { if (matches.First().DASTypeEnum != Common.Enums.Hardware.HardwareTypes.G5INDUMMY) { //it was supposed to be vds, but is in dummy, conclude the dock is lost StatusExAction?.Invoke((int)StatusValues.G5DockLost, g5); } } } } } /// /// returns true as long as all voltages were retrieved, returns false if we failed to get /// a voltage (error/exception, we don't make judgments on the values here) /// /// private bool DoVoltageChecks() { var units = UnitsWithConfiguration.ToList(); try { bool anyFailed = false; var mre = new ManualResetEvent(false); using (var ds = new DiagnosticsService()) { ds.PerformVoltageCheck(units, (ServiceBase.CallbackData data) => { switch (data.Status) { case ServiceBase.CallbackData.CallbackStatus.AllFinished: StatusAction?.Invoke((int)StatusValues.VoltageCheckFinished); mre.Set(); break; case ServiceBase.CallbackData.CallbackStatus.Failure: anyFailed = true; StatusExAction?.Invoke((int)StatusValues.VoltageCheckFailure, data.Target); break; case ServiceBase.CallbackData.CallbackStatus.Success: StatusExAction?.Invoke((int)StatusValues.VoltageCheckSuccess, data.Target); break; } }, units); } while (!mre.WaitOne(VOLTAGE_CHECK_SPIN_TIME_MS, false)) { //we could feedback progress here.. } return !anyFailed; } catch (Exception ex) { APILogger.Log(ex); return false; } return true; } private const int VOLTAGE_CHECK_SPIN_TIME_MS = 200; /// /// starts pinging, returns immediately /// internal void Ping() { _PingTask = Task.Run(() => { var param = States.Instance.HardwareDiscoveryStart.Status.HardwareDiscoveryParams; var global = States.Instance.HardwareDiscoveryStart.Status.GlobalStatusParameters; var dasFactory = States.Instance.HardwareDiscoveryStart.DASFactory; if (null != dasFactory && DoMultiCast()) { DoMulticastDiscovery(); } //get list of all ips requested to ping var ipList = GetIpList(); //eliminate any local ip addresses var host = System.Net.Dns.GetHostName(); var ipEntry = System.Net.Dns.GetHostEntry(host); foreach (var entry in ipEntry.AddressList) { ipList.Remove(entry.ToString().ToLower()); } //notify consumer we having started pinging StatusAction?.Invoke((int)StatusValues.Pinging); //only ping ... if we have something to ping ... if (ipList.Any()) { var p = new PingUtils.PingDevice(); var pinged = new HashSet(); var ptd = new PingThreadData() { CancelEvent = CancelEvent, //any external cancel will cancel the ping process DoneEvent = new ManualResetEvent(false), //we aren't running as a separate task, so this is just for the ping func benefit AvoidConnectPortion = false, //this doesn't appear to actually do anything? entryProgressCallback = (Entry entry, PingProgressStates state, long msRoundTrip) => { StatusExAction?.Invoke((int)StatusValues.PingStatus, entry, state, msRoundTrip); switch (state) { case PingProgressStates.Ping_Good: //make a note that we successfully pinged this ip pinged.Add(entry.IPAddress.ToLower()); break; } }, completeCallback = () => { }, //we don't need this progressCallback = (double d) => { ProgressAction?.Invoke(d); } }; //build the list of ips to ping to provide to the ping func var list = new List(ipList.Count); foreach (var ip in ipList) { list.Add(new Entry() { IPAddress = ip }); } ptd.Entries = list.ToArray(); //call the ping func p.PingFunction(ptd); //we are done pinging, remove any ip addresses which did not respond for (var i = ipList.Count - 1; i >= 0; i--) { if (!pinged.Contains(ipList[i])) { ipList.RemoveAt(i); } } } if (param.Connect) { if (!CancelEvent.WaitOne(1, false)) { //if we aren't cancelled move on to connecting Connect(ipList, dasFactory); } if (!CancelEvent.WaitOne(1, false)) { //we have now connected to all ips we should have, however note //with ECMs that may mean we've connected to the ECM and not the SPS yet //if we are connected to any distributors, ECM or not, give it some additional time //to allow connecting to any children WaitIfAnyECMsConnected(dasFactory); } // if autosense fails for some reason, don't try to get configuration var autoSenseOK = true; if (!CancelEvent.WaitOne(1, false)) { var autoSenseUnits = from unit in UnitsConnected where unit.SupportsAutoDetect select unit; if (param.ReadIds && param.RunAutoSense && !global.DisableAutoSense && autoSenseUnits.Any()) { //we've determined we want to run autosense, so do so, then notify of success/fail autoSenseOK = AutoSense(dasFactory, autoSenseUnits.ToList()); if (autoSenseOK) { StatusAction?.Invoke((int)StatusValues.AutoSenseSuccess); } else { StatusAction?.Invoke((int)StatusValues.AutoSenseFailed); } StatusExAction?.Invoke((int)StatusValues.AutoSenseComplete); } } if (!CancelEvent.WaitOne(1, false) && autoSenseOK) { if (GetConfiguration(dasFactory)) { var voltageChecksFailed = false; if (param.DoVoltageChecks) { voltageChecksFailed = DoVoltageChecks(); } //for now we don't really care much if the voltage check failed, it's //not fatal, but hold onto the value incase we change our mind in the future if (param.DoHardwareChecks) { if (DoHardwareChecks()) { CompleteAction?.Invoke(); } else { StatusAction?.Invoke((int)StatusValues.FailedHardwareChecks); } } else { CompleteAction?.Invoke(); } } else { //getconfiguration failed ... this is only ok if there were no units were were trying to get... if (!param.Addresses.Any()) { CompleteAction?.Invoke(); } else { StatusAction?.Invoke((int)StatusValues.FailedToGetConfiguration); } } } } else { CompleteAction?.Invoke(); } StatusAction?.Invoke((int)StatusValues.Complete); DoneEvent.Set(); }); } /// /// runs autosense on units that support it /// autosense is needed if we have to check ids and the channel type may have been switched (iepe/analog) /// /// private bool AutoSense(IDASFactory factory, List autoSenseUnits) { StatusAction?.Invoke((int)StatusValues.AutoSense); bool ranOK = true; //we want to make sure all units have ConfigData before calling autosense, so complete for any units //without ConfigData var unitsNeedingConfig = from unit in autoSenseUnits where null == unit.ConfigData select unit; if (unitsNeedingConfig.Any()) { ManualResetEvent mre = new ManualResetEvent(false); try { using (var configService = new ConfigurationService()) { var list = unitsNeedingConfig.ToList(); configService.GetConfiguration(list, false, (ServiceBase.CallbackData data) => { switch (data.Status) { case ServiceBase.CallbackData.CallbackStatus.AllFinished: mre.Set(); break; case ServiceBase.CallbackData.CallbackStatus.Failure: ranOK = false; StatusExAction?.Invoke((int)StatusValues.AutoSenseFailed, data.Target, data.ErrorMessage, data.ErrorException); break; case ServiceBase.CallbackData.CallbackStatus.Success: break; } }, list); } } catch (Exception ex) { APILogger.Log(ex); return false; } mre.WaitOne(); } if (CancelEvent.WaitOne(1, false)) { //we were cancelled, we are done return false; } //some unit failed, don't bother going on if (!ranOK) { return false; } try { var mre = new ManualResetEvent(false); using (var configService = new ConfigurationService()) { configService.AutoDetect(autoSenseUnits, false, (ServiceBase.CallbackData data) => { switch (data.Status) { case ServiceBase.CallbackData.CallbackStatus.AllFinished: mre.Set(); break; case ServiceBase.CallbackData.CallbackStatus.Failure: ranOK = false; StatusExAction?.Invoke((int)StatusValues.AutoSenseFailed, data.Target, data.ErrorMessage, data.ErrorException); break; case ServiceBase.CallbackData.CallbackStatus.Success: StatusExAction?.Invoke((int)StatusValues.AutoSenseSuccess, data.Target); break; } }, autoSenseUnits); } mre.WaitOne(); } catch (Exception ex) { APILogger.Log(ex); return false; } return ranOK; } /// /// retrieves the configuration from any units /// returns false if configuration could not be obtained or an exception was thrown /// /// /// private bool GetConfiguration(IDASFactory factory) { var param = States.Instance.ConfigureStart.Status.HardwareDiscoveryParams; var global = States.Instance.ConfigureStart.Status.GlobalStatusInformation; var bReturnValue = true; var devices = factory.GetDASList(); //eliminate devices we've already queried foreach (var device in UnitsWithConfiguration) { if (devices.Contains(device)) { devices.Remove(device); } } //eliminate any devices which are in arm or realtime state ... var deviceArray = devices.ToArray(); foreach (var device in deviceArray) { if (device.GetIsInArm()) { devices.Remove(device); global.AddUnitInArm(device); StatusExAction?.Invoke((int)StatusValues.UnitIsInArm, device); } else if (device.GetIsInRealtime()) { devices.Remove(device); global.AddUnitInRealtime(device); StatusExAction?.Invoke((int)StatusValues.UnitIsInRealtime, device); } } //a specific device is to be queried, query just that device if (null != param.RequeryDevice) { devices.Clear(); devices.Add(param.RequeryDevice); } if (!devices.Any()) { return UnitsWithConfiguration.Any(); } //this will hold the wait handle for the getconfig which will spawn off into multiple threads var waitHandle = new ManualResetEvent(false); //notify consumer we've started getting configurations StatusAction?.Invoke((int)StatusValues.GettingConfiguration); //set status back to 0 since this is a new process ProgressAction?.Invoke(0D); try { var bReadIds = States.Instance.ConfigureStart.Status.HardwareDiscoveryParams.ReadIds; using (var configService = new ConfigurationService()) { //spawn off the work threads configService.GetConfiguration(devices, bReadIds, (ServiceBase.CallbackData data) => { switch (data.Status) { case ServiceBase.CallbackData.CallbackStatus.AllFinished: //everybody is done, continue on your way waitHandle.Set(); break; case ServiceBase.CallbackData.CallbackStatus.Failure: //record the failure, notify consumer bReturnValue = false; StatusExAction?.Invoke((int)StatusValues.FailedToGetConfiguration, data.Target, data.ErrorMessage, data.ErrorException); break; case ServiceBase.CallbackData.CallbackStatus.Success: //this unit is done, notify consumer StatusExAction?.Invoke((int)StatusValues.UnitGotConfiguration, data.Target); AddUnitWithConfiguration(data.Target); break; case ServiceBase.CallbackData.CallbackStatus.Progress: //there's progress, notify consumer (assuming this is already aggregated, but we //might need to notify for individual units as well) ProgressAction?.Invoke(data.ProgressValue); break; } }, devices); } } catch (Exception ex) { bReturnValue = false; //notify of the exception StatusExAction?.Invoke((int)StatusValues.FailedToGetConfiguration, ex); } //wait for everybody to finish waitHandle.WaitOne(); return bReturnValue; } /// /// this is the amount of time after we've connected to a distributor to wait /// some distributors will notify of connected devices so we have to wait for additional connections /// sometimes /// private const int DISTRIBUTOR_WAIT_TIME_MS = 2000; /// /// waits for a little while if there were any distributors that connected /// /// private void WaitIfAnyECMsConnected(IDASFactory factory) { var devices = factory.GetDASList(); var bAnyDistributors = devices.Exists(d => d.IsEthernetDistributor()); if (bAnyDistributors) { Thread.Sleep(DISTRIBUTOR_WAIT_TIME_MS); } } /// /// connects to the given list of ips /// /// /// private void Connect(IList ipList, IDASFactory dasFactory) { StatusAction?.Invoke((int)StatusValues.Connecting); var listTDAS = new List(); var listSLICE = new List(); //to cut down on the number of connections we have to make //if we know a device is SLICE or TDAS, only connect as SLICE or TDAS //otherwise try to connect as both var tdasIPHash = GetTDASIPHash(); var sliceIPHash = GetSLICEIPHash(); //remove any duplicates for sanity sake ipList = ipList.Distinct().ToList(); //this will hold any ips that we are still waiting for //while we are still waiting for an ip to connect we'll spin around and wait var hashWaitingFor = new HashSet(); //this will hold devices we've notified that we've connected to //so that we only send one notification to consumer var hashDevicesWeveNotifiedOn = new HashSet(); foreach (var ip in ipList) { hashWaitingFor.Add(ip); StatusExAction?.Invoke((int)StatusValues.Connecting, ip); if (tdasIPHash.Contains(ip)) { listTDAS.Add(ip); } else if (sliceIPHash.Contains(ip)) { listSLICE.Add(ip); } else { //we don't know if it's slice or tdas, we'll have to try to connect both ways listTDAS.Add(ip); listSLICE.Add(ip); } } if (null != dasFactory) { dasFactory.SliceDBHostNames = listSLICE.ToArray(); dasFactory.TDASHostNames = listTDAS.ToArray(); var timeWaited = 0; var maxTime = States.Instance.HardwareDiscoveryStart.Status.HardwareDiscoveryParams.ConnectTimeoutMS; var bDone = false; dasFactory.Refresh(() => { }); ProgressAction?.Invoke(0); var sliceEthernetWaitNeeded = false; var ecmWaitTime = 0; bool bWaitingForECMConnects = false; while (timeWaited < maxTime && !bDone && !CancelEvent.WaitOne(0, false)) { Thread.Sleep(TIME_WAIT_CONNECT); var devices = dasFactory.GetDASList(); foreach (var device in devices) { var connection = ((ICommunication)device).Transport.ConnectString; var index = connection.IndexOf(':'); var ip = connection; if (index > 0) { ip = connection.Substring(0, index); } if (hashWaitingFor.Contains(ip)) { //we've got a new ip connected, notify consumer hashWaitingFor.Remove(ip); } if (!hashDevicesWeveNotifiedOn.Contains(device)) { if (device.IsEthernetDistributor()) { sliceEthernetWaitNeeded = true; } StatusExAction?.Invoke((int)StatusValues.Connected, connection, device); AddUnitConnected(device); } } timeWaited += TIME_WAIT_CONNECT; if (bWaitingForECMConnects) { //we've connected to all the ips we connect, we just have to wait for any //devices connected to the ecm to connect ecmWaitTime += timeWaited; if (ecmWaitTime >= SLICE_ETHERNET_WAIT_PERIOD) { bDone = true; } ProgressAction?.Invoke(100D * timeWaited / maxTime); } else if (!hashWaitingFor.Any()) { //if there's no ips we are waiting for, but //ecms take some amount of time to connect to notify and connect to modules //so we might not be done yet ... //to make things worse how many modules are connected to the can be different than expected //and can even change (usb events..) if (sliceEthernetWaitNeeded) { bWaitingForECMConnects = true; } else { //there were no ecms, so we are officially done ProgressAction?.Invoke(100D); bDone = true; } } else { //we are still waiting for some ips to connect ProgressAction?.Invoke(100D * timeWaited / maxTime); } } } } /// /// when we connect to an ECM/DB it takes a while before all the units are actually connected /// to fix this we allow a wait period after all ips are connected as long as one of the ips is slice db /// private const int SLICE_ETHERNET_WAIT_PERIOD = 7000; private static object LocalLock = new object(); private void AddUnitConnected(IDASCommunication device) { lock (LocalLock) { if (UnitsConnected.Contains(device)) { return; } var units = new List(UnitsConnected); if (units.Exists(d => d.SerialNumber == device.SerialNumber)) { //device disconnect and reconnected ... throw out the old reference units.RemoveAll(d => d.SerialNumber == device.SerialNumber); var unitsWithConfig = new List(); unitsWithConfig.AddRange(UnitsWithConfiguration); unitsWithConfig.RemoveAll(d => d.SerialNumber == device.SerialNumber); UnitsWithConfiguration = unitsWithConfig.ToArray(); } units.Add(device); UnitsConnected = units.ToArray(); } } private void AddUnitWithConfiguration(IDASCommunication device) { lock (LocalLock) { if (UnitsWithConfiguration.Contains(device)) { return; } var units = new List(UnitsWithConfiguration); units.Add(device); UnitsWithConfiguration = units.ToArray(); } } private const int TIME_WAIT_CONNECT = 500;//500ms public void Reset() { AllDASFound = false; SomeUnitsInArmState = false; CancelEvent.Reset(); DoneEvent.Reset(); CompleteAction = null; StatusAction = null; ProgressAction = null; StatusExAction = null; _discoveredDevices.Clear(); UnitsConnected = new IDASCommunication[0]; UnitsWithConfiguration = new IDASCommunication[0]; UnitsWithLostDocks = new IDASCommunication[0]; UnitsMissing = new IDASHardware[0]; UnitsInAutoArm = new IDASCommunication[0]; UnitsReadyToStream = new IDASCommunication[0]; UnitsStreaming = new IDASCommunication[0]; UnitsNotInDb = new IDASCommunication[0]; UnitsWithWrongFirmware = new IDASCommunication[0]; UnitsWithExpiredCalDates = new IDASCommunication[0]; UnitsWithNANDIssues = new IDASCommunication[0]; UnitsWithWrongChannelCount = new IDASCommunication[0]; UnitsWithChannelTypeIssue = new IDASCommunication[0]; HaveChannels = false; ExtraUnits = new IDASCommunication[0]; } } }