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

1713 lines
69 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using DTS.Common.DASResource;
using DTS.Common.Interface.DASFactory;
using DTS.DASLib.Command.Classes;
using DTS.DASLib.Service;
using DTS.Common.Utilities.Logging;
using DTS.DASLib.Command.SLICE.MulticastCommands;
using DTS.Common.Utilities;
using System.Net;
using DTS.Common.Interface.Communication;
using DTS.Common.Enums.Communication;
using DTS.Common.Enums.DASFactory;
using DTS.Common.Interface.StatusAndProgressBar;
using System.Net.Sockets;
using System.Threading.Tasks;
using DTS.Common.Enums;
using System.Collections.Concurrent;
using static DTS.DASLib.DASFactory.DeviceHandling;
using DASFactoryDb;
using DTS.Common.Utils;
using DTS.DASLib.Service.Classes.CAN;
using DTS.DASLib.Connection;
// ReSharper disable once CheckNamespace
namespace DTS.DASLib.DASFactory
{
/// <summary>
/// Delegate for event handler to handle the device events
/// </summary>
/// <param name="sender">who?</param>
/// <param name="e">stuff</param>
public delegate void DASFactoryEventHandler(Object sender, DASFactoryEventArgs e);
/// <summary>
/// Our class for passing in custom arguments to our event handlers
///
/// </summary>
public class DASFactoryEventArgs : EventArgs
{
public string[] SerialNumbers { get; set; }
public CommunicationConstantsAndEnums.CommunicationResult Result { get; set; }
/// <summary>
/// construct a new <see cref="DTS.DASLib.DASFactory.DASFactoryEventArgs" /> object
/// </summary>
/// <param name="serialNumbers"><see cref="string[]" />das serial numbers</param>
/// <param name="result"><see cref="CommunicationResult" />communication result</param>
public DASFactoryEventArgs(string[] serialNumbers, CommunicationConstantsAndEnums.CommunicationResult result)
{
SerialNumbers = serialNumbers;
Result = result;
}
}
/// <summary>
/// This exception is thrown when the user tries to run more than one instance.
/// </summary>
public class SingletonFaultException : ApplicationException
{
/// <summary>
/// construct exception with a message
/// </summary>
/// <param name="msg">description of error</param>
public SingletonFaultException(string msg)
: base(msg)
{
}
/// <summary>
/// Construct an exception with message and throwing exception
/// </summary>
/// <param name="msg">description of error</param>
/// <param name="inner">exception causing error</param>
public SingletonFaultException(string msg, Exception inner)
: base(msg, inner)
{
}
}
internal static class SocketExtension
{
public static Task ConnectAsync(this Socket socket, string host, int port)
{
if (socket == null)
throw new ArgumentNullException("socket");
return Task.Factory.FromAsync(socket.BeginConnect, socket.EndConnect, host,
port, null);
}
public static Task<int> ReceiveAsync(this Socket socket, byte[] buffer, int offset, int size, SocketFlags socketFlags)
{
if (socket == null)
throw new ArgumentNullException("socket");
return Task<int>.Factory.FromAsync(
(callback, state) => socket.BeginReceive(buffer, offset, size, socketFlags, callback, state),
socket.EndReceive, state: null
);
}
}
/// <summary>
/// the DAS factory handles acquiring, listing, enumerating DAS devices.
/// this factory abstracts communicating with groups of hardware and notifications of
/// new devices or removed devices.
/// </summary>
public class DASFactory : IDASFactory, IDisposable
{
public void StartMulticastAutoDiscovery() { _autoDiscovery.StartMulticastAutoDiscovery(); }
public void StopMulticastAutoDiscovery() { _autoDiscovery.StopMulticastAutoDiscovery(); }
/// <summary>
/// returns any discovered devices
/// </summary>
/// <returns></returns>
public IDiscoveredDevice[] GetDiscoveredDevices()
{
return _autoDiscovery.GetDiscoveredDevices();
}
private readonly AutoDiscovery _autoDiscovery;
#region Private Area
private BlockingCollection<Tuple<QueueActions, DeviceHandling>> _queueActionPerDevice;
private TextLogger _autoDiscoveryLog;
private static void OnLoggerException(Exception ex)
{
}
private static string _language;
private const int MAX_LOG_SIZE = 4194304;
public string Language
{
get => _language;
set
{
_language = value;
if (_language.Length > 0)
{
Thread.CurrentThread.CurrentUICulture = new CultureInfo(_language);
}
}
}
private WinUSBHandling _winUsbHandler;
private WinUSBHandling _cdcusbHandler;
private WinUSBHandling _winUsb15Handler;
private WinUSBHandling _winTsrAirHandler;
private readonly List<EthernetHandling> _ethernetHandler = new List<EthernetHandling>();
private EthernetTDASHandling _ethernetTDASHandler;
private SerialTDASHandling SerialTDASHandler;
private RESTSPFDHandling _restSPFDHandler;
#endregion
#region Singleton enforcement
/// <summary>
/// This mutex prevents multiple instances. Not only inside one application.
/// </summary>
private static Mutex _singletonFlag;
private static readonly object SingletonFlagLock = new object();
private const string SINGLETON_FLAG_NAME = "DTSRecorderMutex";
private static void TakeDASFactoryOwnership()
{
lock (SingletonFlagLock)
{
if (_singletonFlag != null)
{
// There's already one running
throw new SingletonFaultException("You can only run one instance of DASFactory at a time, exiting");
}
bool createdNew;
try
{
_singletonFlag = new Mutex(true, SINGLETON_FLAG_NAME, out createdNew);
}
catch (Exception ex)
{
throw new SingletonFaultException("DASFactory failed to create mutex", ex);
}
if (!createdNew)
{
// Application not ok to start
_singletonFlag = null;
throw new SingletonFaultException("You can only run one of the DTS applications at a time, exiting");
}
}
}
public void TakeOwnership()
{
TakeDASFactoryOwnership();
}
public static bool CanDASFactoryStart()
{
return CanDASFactoryStart(false);
}
/// <summary>
/// tests whether it is okay for DASFactory to start
/// </summary>
/// <returns>true if DASFactory can start, false otherwise</returns>
/// <remarks>briefly creates and destroys a named mutex to determine whether
/// an instance of the application is running or not.
/// </remarks>
public static bool CanDASFactoryStart(bool bTakeControl)
{
lock (SingletonFlagLock)
{
if (_singletonFlag != null)
{
// There's already one running
return false;
}
Mutex tmpFlag = null;
try
{
bool createdNew;
tmpFlag = new Mutex(false, SINGLETON_FLAG_NAME, out createdNew);
// did we create it?
if (createdNew) return true;
bTakeControl = false;
// no, someone already had
return false;
// yes!
}
catch (Exception ex)
{
APILogger.Log("MessageBox", Strings.DASFactory_FailedToCreateMutex, ex);
return false;
}
finally
{
if (!bTakeControl)
{
// we just needed to know if we can create it so close it now
if (tmpFlag != null)
{
tmpFlag.Close();
}
}
else { _singletonFlag = tmpFlag; }
}
}
}
#endregion
private bool _bAllowSDBCommandPort = true;
public bool AllowSDBCommandPort
{
get => _bAllowSDBCommandPort;
set => _bAllowSDBCommandPort = value;
}
//FB 25642 & 18152 reference to the DasToHost dictionary
public ConcurrentDictionary<string, Common.Utils.HostInfo> DasToHost { get; set; } = Common.Utils.PingUtils.DasToHost;
public double S6ConnectNewTimeout { get; set; } = 60000;
private void ProcessConnectionQueue()
{
while (!_queueActionPerDevice.IsCompleted)
{
Tuple<QueueActions, DeviceHandling> queueActionPerDevice;
try
{
queueActionPerDevice = _queueActionPerDevice.Take();
}
catch
{
break;
}
switch (queueActionPerDevice.Item1)
{
case QueueActions.Connect:
queueActionPerDevice.Item2.UpdateConnectedDevices();
break;
case QueueActions.Disconnect:
queueActionPerDevice.Item2.UpdateDisconnectedDevices();
break;
case QueueActions.DisconnectAndExit:
Task.Run(() =>
{
queueActionPerDevice.Item2.UpdateDisconnectedDevices();
});
break;
case QueueActions.Exit:
continue;
}
UpdateFinishedCallback(queueActionPerDevice.Item2);
}
}
/// <summary>
/// host names for SLICE Distributors
/// </summary>
public string[] SliceDBHostNames
{
get
{
lock (MyLock)
{
var existingIPs = new List<string>();
for (var i = _ethernetHandler.Count - 1; i >= 0; i--)
{
existingIPs.Add(_ethernetHandler[i].SliceDBHostName);
}
return existingIPs.ToArray();
}
}
set
{
lock (MyLock)
{
var existingIPs = new List<string>();
var buckets = GetBuckets(value);
var removed = false;
for (var i = _ethernetHandler.Count - 1; i >= 0; i--)
{
var matches = Array.FindAll(value, s => s.Equals(_ethernetHandler[i].SliceDBHostName));
if (matches.Length < 1)
{
//FB 17556 disconnect when exit
_ethernetHandler[i].EnqueueDisconnectAndExit();
_ethernetHandler[i].Dispose();
_ethernetHandler.RemoveAt(i);
removed = true;
}
else { existingIPs.Add(_ethernetHandler[i].SliceDBHostName); }
}
if (removed)
{
ReportRemoved();
}
var waitingHandlers = new List<EthernetHandling>();
foreach (var s in value)
{
if (existingIPs.Contains(s))
{
APILogger.Log($"Existing IPs already contains {s} - skipping");
continue;
}
else { APILogger.Log($"Adding new IP {s}"); }
var eh = new EthernetHandling(this, UpdateFinishedCallback,
new EthernetSliceHandling(_inUtilityMode),
new RibeyeHandling(_inUtilityMode),
new EthernetSlice2Handling(_inUtilityMode),
new EthernetSlice1_5Handling(_inUtilityMode),
new EthernetSlice6Handling(_inUtilityMode),
new EthernetSlice6AirHandling(_inUtilityMode),
new EthernetSlice6AirBridgeHandling(_inUtilityMode),
new EthernetSlice6DBHandling(_inUtilityMode, ConnectedDevicesUpdated),
new EthernetPowerProHandling(_inUtilityMode),
new EthernetTsrAirHandling(_inUtilityMode),
new EthernetSlice6DB3Handling(_inUtilityMode, ConnectedDevicesUpdated),
new EthernetSliceProDBHandling(_inUtilityMode, SLICEPRODBConnectedDevicesUpdated),
new EthernetSlice6AirThermocouplerHandling(_inUtilityMode),
_queueActionPerDevice
)
{
HandlerIndex = null == buckets ? 0 : GetBucket(buckets, s),
SliceDBHostName = s
};
eh.UpdateDeviceSetups();
_ethernetHandler.Add(eh);
waitingHandlers.Add(eh);
}
Task.Run(() => ConnectHosts(waitingHandlers.ToArray())).ConfigureAwait(false);
}
}
}
/// <summary>
/// tries to ping the given ip address
/// this is for http://manuscript.dts.local/f/cases/30297/ISF-import-not-working-correctly-with-S6DB3-and-ISF
/// </summary>
/// <param name="ipAddress"></param>
public static void TryPing(string ipAddress)
{
try
{
var p = new PingUtils.PingDevice();
p.PingDevices(new List<string>(new[] { ipAddress }));
}
catch (Exception ex)
{
APILogger.Log(ex);
}
}
private const int RECEIVE_BUFFER_SIZE = 65536;
private void ConnectHosts(EthernetHandling[] waitingHandlings)
{
if (!waitingHandlings.Any())
{
return;
}
//var ips = waitingHandlings.Select(wh => wh.SliceDBHostName).Distinct().ToArray();
//var buckets = GetBuckets(ips);
//var r = new Random(DateTime.Now.Second);
//Parallel.ForEach(waitingHandlings, h=>
for (var idx = 0; idx < waitingHandlings.Length; idx++)
{
var h = waitingHandlings[idx];
Task.Run(async () =>
{
//we may have to tear down and construct sockets multiple times, so
//we'll loop through till we have a connection
bool bTryingToConnect = true;
Socket sock = null;
while (bTryingToConnect && !h.SLICEDBShouldDisconnect)
{
//var bucket = GetBucket(buckets, h.SliceDBHostName);
if (idx > 0)
{
var delay = idx * DFConstantsAndEnums.WaitTimeBetweenUnitConnects;
await Task.Delay(delay);
}
sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
sock.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
uint dummy = 0; //length = 4
var inOptionValues = new byte[System.Runtime.InteropServices.Marshal.SizeOf(dummy) * 3];
var onOff = true;
var keepAliveTimeOutMs = DFConstantsAndEnums.LocalKeepAliveTimeOutMS;
var keepAliveRetryIntervalMs = DFConstantsAndEnums.LocalKeepAliveRetryIntervalMS;
BitConverter.GetBytes((uint)(onOff ? 1 : 0)).CopyTo(inOptionValues, 0);
BitConverter.GetBytes(keepAliveTimeOutMs)
.CopyTo(inOptionValues, System.Runtime.InteropServices.Marshal.SizeOf(dummy));
BitConverter.GetBytes(keepAliveRetryIntervalMs)
.CopyTo(inOptionValues, System.Runtime.InteropServices.Marshal.SizeOf(dummy) * 2);
sock.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
//FB 25642 & 18152 Get the host ip
if (!DasToHost.ContainsKey(h.SliceDBHostName))
{
APILogger.Log($"no known host for {h.SliceDBHostName} we'll try pinging for it");
TryPing(h.SliceDBHostName);
}
else
{
APILogger.Log($"we have a known host for {h.SliceDBHostName} and it's {DasToHost[h.SliceDBHostName].HostIpAddress}");
}
string hostIpAddress = string.Empty;
if (DasToHost.ContainsKey(h.SliceDBHostName))
{
hostIpAddress = DasToHost[h.SliceDBHostName].HostIpAddress;
}
if (!string.IsNullOrEmpty(hostIpAddress))
{
APILogger.Log($"DASFactory.ConnectHosts binding to {hostIpAddress}");
sock.Bind(new IPEndPoint(IPAddress.Parse(hostIpAddress), 0));
}
//before connecting a heartbeat port, make sure any corresponding
//command ports are disconnected, if there's one active, disconnect it!
//as SLICE doesn't really handle talking on the HB port while command port is still
//open
try
{
var commandSocket = DeviceHandling.GetCommandPortSocket(h.SliceDBHostName);
if (null != commandSocket)
{
if (commandSocket.Connected)
{
APILogger.Log(
$"Found a command port still active while attempting to connect heartbeat port {h.SliceDBHostName}");
commandSocket.Disconnect(false);
DeviceHandling.RemoveCommandPortSocket(h.SliceDBHostName);
}
else
{
RemoveCommandPortSocket(h.SliceDBHostName);
}
}
}
catch (Exception ex)
{
APILogger.Log($"Exception in ConnectHosts, commandSocket {ex}");
}
//connect heartbeat port
Task connectTask = null;
try
{
APILogger.Log($"Connecting to {h.SliceDBHostName} on {hostIpAddress}");
connectTask = sock.ConnectAsync(h.SliceDBHostName, 8200);
connectTask.Wait(DFConstantsAndEnums.HeartbeatAsyncConnectTimeoutMS);
if (connectTask.IsCompleted)
{
APILogger.Log($"Failed to connect to {h.SliceDBHostName} will retry");
bTryingToConnect = false;
}
else
{
//FB 18389
sock.Shutdown(SocketShutdown.Both);
sock.Close();
sock.Dispose();
}
//FB18389 Only dispose the tasks in one these states, same as using statement except we don't throw the exception
//in case the task is still working. disposing the sock should cause the wrapper connectTask finishes eventually and the .net grabage collector should take care of the task later
if (connectTask.IsCompleted || connectTask.IsFaulted || connectTask.IsCanceled)
{
connectTask.Dispose();
}
}
catch (Exception ex)
{
APILogger.Log($"Failed to connect to {h.SliceDBHostName}", ex);
}
if (!bTryingToConnect)
{
try
{
var buffer = new byte[RECEIVE_BUFFER_SIZE];
int ret = 0;
//FB18389 make sure the sock is connected before receive
if (sock.Connected)
{
ret = await sock.ReceiveAsync(buffer, 0, RECEIVE_BUFFER_SIZE, SocketFlags.None);
}
// only dispose the sock if the sock is not connected and can not process the data
if (sock.Connected)
{
//FB18389 make sure the sock is connected before process
await ProcessResult(ret, sock, h, buffer);
}
else
{
sock.Dispose();
}
}
catch (Exception ex)
{
//hit an error trying to read, even though we connected, so try again
if (sock != null)
{
sock.Dispose();
}
APILogger.Log("Exception in ConnectHosts, bTryingToConnect = false ", ex);
bTryingToConnect = true;
}
}
}
}).ConfigureAwait(false);
}
}
//FB43867 applied the patch
public const string COMMAND_PORT_CONNECT_OVERRIDE_MESSAGE = "commandPort";
private async Task ProcessResult(int ret, Socket sock, EthernetHandling h, byte[] buffer)
{
//FB18389 Make sure socket is not null, there is no point to continue if the sock is null
if (sock == null)
{
APILogger.Log("sock is null");
return;
}
var distributorSocket = new DistributorSocket(sock, h.SliceDBHostName, "logs");
if (0 == ret && null != sock)
{
APILogger.Log("DistributorSocket 0 bytes, disposing");
sock.Dispose();
APILogger.Log("DistributorSocket 0 bytes, disposed");
return;
}
var bytes = buffer.Take(ret).ToArray();
//17649 Issues connecting/disconnecting to SLICE Distributor
//a disconnect has been requested, do it ...
if (h.SLICEDBShouldDisconnect)
{
APILogger.Log($"Disconnect requested for {h.SliceDBHostName}");
try
{
// https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.shutdown?view=net-5.0
//FB 18389 Call Shutdown before Close to disable any send and receives on socket
sock.Shutdown(SocketShutdown.Both);
sock.Close();
sock.Dispose();
}
catch (Exception ex)
{
APILogger.Log($"Exception in ProcessResult closing & disposing the sock {ex}");
}
}
var msg = System.Text.Encoding.ASCII.GetString(bytes);
//FB18389 make sure msg has value
if (string.IsNullOrEmpty(msg))
{
APILogger.Log($"ProcessResult msg is empty.");
return;
}
var lines = msg.Split(new[] { "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
var thisMsg = line;
if (string.IsNullOrWhiteSpace(line)) { continue; }
//FB43867 applied the patch
if (line.Equals("heartbeat") && !h.CommandPortConnected && !h.CommandPortConnectSignaled && !line.EndsWith("8301"))
{
//Get the Distributor Connected
h.CommandPortConnectSignaled = true;
}
if (line.Equals("heartbeat") && !h.CommandPortConnected && !h.CommandPortConnectSignaled && !line.EndsWith("8301"))
{
//Get the Distributor Connected
thisMsg = $"{COMMAND_PORT_CONNECT_OVERRIDE_MESSAGE} {h.SliceDBHostName}:8201";
h.CommandPortConnectSignaled = true;
}
if (EthernetHandling.DistributorMsg.IsValidMsg(thisMsg))
{
var dbMessage = new EthernetHandling.DistributorMsg(thisMsg);
distributorSocket.SendAck();
if (dbMessage.SupportsKeepAlive)
{
h.KeepAliveEnabled = distributorSocket.KeepAliveEnabled();
}
else
{
h.KeepAliveEnabled = false;
}
if (h.UpdateConnectedDBDevices(h.SliceDBHostName, dbMessage))
{
if (dbMessage.IsConnect)
{
h.EnqueueConnect();
}
else
{
h.EnqueueDisconnect();
}
}
if (h.KeepAliveEnabled)
{
try
{
// https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.shutdown?view=net-5.0
//FB 18389 Call Shutdown before Close to disable any send and receives on socket
sock.Shutdown(SocketShutdown.Both);
sock.Close();
sock.Dispose();
break;
}
catch (Exception ex)
{
APILogger.Log($"Exception in ProcessResult, the KeepAliveEnabled = true {ex}");
break;
}
}
}
else
{
//FB 18389 make sure the socket is connected before using it
if (distributorSocket.IsConnected())
{
distributorSocket.SendNak();
}
}
}
if (!h.KeepAliveEnabled)
{
try
{
if (!sock.Connected) { return; }
int res = await sock.ReceiveAsync(buffer, 0, RECEIVE_BUFFER_SIZE, SocketFlags.None);
if (!sock.Connected) { return; }
await ProcessResult(res, sock, h, buffer);
}
catch (Exception ex)
{
APILogger.Log($"Exception in ProcessResult, the KeepAliveEnabled = false {ex}");
}
}
}
/// <summary>
/// if all the input strings are in ip form (aaa.bbb.ccc.ddd)
/// returns all the unique ccc portion of the ip addresses
/// otherwise returns null
/// </summary>
/// <param name="array"></param>
/// <returns></returns>
private static string[] GetBuckets(string[] array)
{
var useSubnets = Array.TrueForAll(array, s => s.Split('.').Length == 4);
if (useSubnets)
{
var list = new List<string>();
foreach (var s in array)
{
var subnet = s.Split('.')[2];
if (!list.Contains(subnet))
{
list.Add(subnet);
}
}
return list.ToArray();
}
return null;
}
/// <summary>
/// if target is in ip form (aaa.bbb.ccc.ddd)
/// returns which index the subnet of target is in among the subnets passed in
/// </summary>
/// <param name="subnets"></param>
/// <param name="target"></param>
/// <returns></returns>
private static int GetBucket(string[] subnets, string target)
{
var tokens = target.Split('.');
if (tokens.Length < 4)
{
return 0;
}
return Array.IndexOf(subnets, tokens[2]);
}
/// <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
/// </summary>
/// <returns></returns>
public string[] GetConnectedDevices()
{
var devices = new List<string>();
foreach (var eh in _ethernetHandler)
{
var list = eh.GetConnectedEthernetDeviceStrings();
devices.AddRange(list.ToArray());
}
var spfd = _restSPFDHandler.GetDASList();
return devices.ToArray();
}
/// <summary>
/// callback when discovery was performed on a device and we discovered more devices
/// this function starts the communication with the newly discovered devices
/// </summary>
/// <param name="devices"></param>
private void SLICEPRODBConnectedDevicesUpdated(IDASConnectedDevice[] devices)
{
if (RunTestVariables.InRunTest) { return; }
if (devices.Any())
{
lock (MyLock)
{
var existingIPs = new HashSet<string>();
var ips = SliceDBHostNames.ToList();
foreach (var ip in ips) { existingIPs.Add(ip); }
var connectedDevices = devices.Select(device => device.IPAddress).ToArray();
APILogger.Log($"SLICE PRO DB has attached {string.Join(", ", connectedDevices)}");
var added = false;
foreach (var ip in connectedDevices)
{
if (!existingIPs.Contains(ip))
{
existingIPs.Add(ip);
ips.Add(ip);
added = true;
}
}
if (added)
{
SliceDBHostNames = ips.ToArray();
}
}
}
}
/// <summary>
/// callback when discovery was performed on a device and we discovered more devices
/// this function starts the communication with the newly discovered devices
/// </summary>
/// <param name="devices"></param>
private void ConnectedDevicesUpdated(IDASConnectedDevice[] devices)
{
if (RunTestVariables.InRunTest) { return; }
if (devices.Any())
{
lock (MyLock)
{
var existingIPs = new HashSet<string>();
var ips = SliceDBHostNames.ToList();
foreach (var ip in ips) { existingIPs.Add(ip); }
var connectedDevices = devices.Select(device => device.IPAddress).ToArray();
APILogger.Log($"S6DB has attached {string.Join(", ", connectedDevices)}");
var added = false;
foreach (var ip in connectedDevices)
{
if (!existingIPs.Contains(ip))
{
existingIPs.Add(ip);
ips.Add(ip);
added = true;
}
}
if (added)
{
SliceDBHostNames = ips.ToArray();
}
}
}
}
private static readonly object MyLock = new object();
/// <summary>
/// TDAS Host Names
/// </summary>
public string[] TDASHostNames
{
get => _ethernetTDASHandler.TDASHostNames;
set
{
var toSet = value;
if ( null != value && value.Length > 1) { toSet = value.Distinct().ToArray(); }
_ethernetTDASHandler.TDASHostNames = toSet;
}
}
public string[] SPFDHostNames
{
get => _restSPFDHandler.SPFDHostNames;
set
{
var toSet = value;
if (null != value && value.Length > 1) { toSet = value.Distinct().ToArray(); }
_restSPFDHandler.SPFDHostNames = toSet;
}
}
public string[] TDASSerialPortNames
{
get { return SerialTDASHandler.TDASPortNames; }
set { SerialTDASHandler.TDASPortNames = value; }
}
public string TDASSerialRackSerialNumber
{
get { return SerialTDASHandler.RackSerialNumber; }
set { SerialTDASHandler.RackSerialNumber = value; }
}
#region Events for arrived, removed and failed
/// <summary>
/// Event signalized to the client app.
/// Add handler for this event in your form to be notified of new device events
/// </summary>
public event DASFactoryEventHandler DeviceArrived;
internal void ReportArrived(ConnectedDevice device)
{
if (DASFactoryDb.DbWrapper.Connected && device.Dev.Comm.SerialNumber != null)
{
InsertDeviceIfNeeded(device);
}
//FB14628: Improve "connected units have changed" message
//Send added serial up with event
DeviceArrived?.Invoke(this, new DASFactoryEventArgs(null != device?.Dev?.Comm?.SerialNumber ? new string[] { device.Dev.Comm.SerialNumber } : new string[] { }, CommunicationConstantsAndEnums.CommunicationResult.ConnectOK));
}
private void InsertDeviceIfNeeded(ConnectedDevice device)
{
try
{
var id = DASFactoryDb.DbWrapper.GetDeviceId(device.Dev.Comm.SerialNumber);
if (id > 0)
{
((IDASCommunication)device.Dev.Comm).RecordId = id;
return;
}
InsertRecord(device);
}
catch (Exception ex) { APILogger.Log(ex); }
}
/// <summary>
/// inserts a new record into DASFactory.IDASCommunicationTable
/// </summary>
/// <param name = "device" ></ param >
private void InsertRecord(ConnectedDevice device)
{
try
{
if (!DbWrapper.Connected) { return; }
var id = DASFactoryDb.DAS.DAS.InsertDASSimple(device.Dev.Comm.SerialNumber,
device.Dev.Comm.FirmwareVersion,
device.Dev.Comm.ConnectString);
((IDASCommunication)device.Dev.Comm).RecordId = id;
}
catch (Exception ex)
{
APILogger.Log(ex);
}
}
/// <summary>
/// Event signalized to the client app.
/// Add handler for this event in your form to be notified of removed device events
/// </summary>
public event DASFactoryEventHandler DeviceRemoved;
internal void ReportRemoved(object o = null)
{
//FB14628: Improve "connected units have changed" message
//Send removed serials up with event
var serials = new string[] { };
switch (o)
{
case string[] _:
serials = o as string[];
break;
case ICommunication icom:
serials = new string[] { icom.SerialNumber };
if (ShouldReconnect(icom))
{
var connection = ((DTS.Common.EthernetConnection)icom.Transport);
var host = connection.ConnectString.Substring(0, connection.ConnectString.IndexOf(':'));
foreach (var handler in _ethernetHandler)
{
if (handler.SliceDBHostName == host && !handler.SLICEDBShouldDisconnect)
{
Task.Run(() =>
{
ConnectHosts(new[] { handler });
}).ConfigureAwait(false);
break;
}
}
}
break;
}
DeviceRemoved?.Invoke(this, new DASFactoryEventArgs(serials, CommunicationConstantsAndEnums.CommunicationResult.DisconnectOK));
}
private bool ShouldReconnect(ICommunication icom)
{
return icom is EthernetSlice6 || icom is EthernetSlice6Air || icom is EthernetSlice6AirBridge || icom is EthernetSlice6DB || icom is EthernetTsrAir
|| icom is EthernetSlice6AirThermocoupler;
}
/// <summary>
/// Event signalized to the client app.
/// Add handler for this event in your form to be notified of failed device events
/// </summary>
public event DASFactoryEventHandler DeviceFailed;
internal void ReportFailed(object o = null)
{
//FB14628: Improve "connected units have changed" message
DeviceFailed?.Invoke(this, new DASFactoryEventArgs(new string[] { }, CommunicationConstantsAndEnums.CommunicationResult.ConnectFailed));
}
#endregion
#region Constructor
private bool _inUtilityMode;
// http://manuscript.dts.local/f/cases/18700/Add-ability-to-instantiate-DASFactory-without-USB
// Mostly done for use in Linux without Windows USB drivers and listeners
private bool _bUsingUSB = true;
/// <summary>
/// The easiest way to use DASFactory.
/// It will create hidden form for processing Windows messages about USB drives
/// You do not need to override WndProc in your form.
/// </summary>
public DASFactory(bool inUtilityMode)
{
_autoDiscovery = new AutoDiscovery(this);
_autoDiscoveryLog = new TextLogger(@"Logs\AutoDiscovery.log", OnLoggerException, MAX_LOG_SIZE);
Init(inUtilityMode, true);
}
public DASFactory(bool inUtilityMode, bool takeControl)
{
_autoDiscovery = new AutoDiscovery(this);
_autoDiscoveryLog = new TextLogger(@"Logs\AutoDiscovery.log", OnLoggerException, MAX_LOG_SIZE);
Init(inUtilityMode, takeControl);
}
public DASFactory(bool inUtilityMode, bool takeControl, bool bUsingUSB)
{
_autoDiscovery = new AutoDiscovery(this);
_autoDiscoveryLog = new TextLogger(@"Logs\AutoDiscovery.log", OnLoggerException, MAX_LOG_SIZE);
Init(inUtilityMode, takeControl, bUsingUSB);
}
private void Init(bool inUtilityMode, bool takeOwnership, bool bUsingUSB = true)
{
_inUtilityMode = inUtilityMode;
// http://manuscript.dts.local/f/cases/18700/Add-ability-to-instantiate-DASFactory-without-USB
// Mostly done for use in Linux without Windows USB drivers and listeners
_bUsingUSB = bUsingUSB;
// first make sure we're allowed to start. Will throw if we can't
if (takeOwnership) { TakeDASFactoryOwnership(); }
_queueActionPerDevice = new BlockingCollection<Tuple<QueueActions, DeviceHandling>>();
Task.Factory.StartNew(ProcessConnectionQueue, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).ConfigureAwait(false);
if (_bUsingUSB)
{
// now do WinUSB
_winUsbHandler = new WinUSBHandling(this, UpdateFinishedCallback, new WinUSBSliceHandling(inUtilityMode), _queueActionPerDevice);
// now do CDCUSB
_cdcusbHandler = new WinUSBHandling(this, UpdateFinishedCallback, new WinUSBSlice2Handling(inUtilityMode), _queueActionPerDevice);
// now do WinUSB1.5
_winUsb15Handler = new WinUSBHandling(this, UpdateFinishedCallback, new WinUSBSlice1_5Handling(inUtilityMode), _queueActionPerDevice);
// now do TSR AIR
_winTsrAirHandler = new WinUSBHandling(this, UpdateFinishedCallback, new WinUSBTsrAirHandling(inUtilityMode), _queueActionPerDevice);
}
// now ethernet
_ethernetTDASHandler = new EthernetTDASHandling(this, UpdateFinishedCallback, new TDASEthernetSetup(inUtilityMode), _queueActionPerDevice);
_ethernetTDASHandler.UpdateDeviceSetups();
//now SPFD
_restSPFDHandler = new RESTSPFDHandling(this, UpdateFinishedCallback, new SPFDRESTSetup(), _queueActionPerDevice);
_restSPFDHandler.UpdateDeviceSetups();
}
#endregion
/// <summary>
/// gets a list of all connected DAS devices.
/// </summary>
/// <returns>List of all connected DAS hardware</returns>
public List<IDASCommunication> GetDASList()
{
var dasList = new List<IDASCommunication>();
if (null != _winUsbHandler) { dasList.AddRange(_winUsbHandler.GetDASList()); }
if (null != _cdcusbHandler) { dasList.AddRange(_cdcusbHandler.GetDASList()); }
if (null != _winUsb15Handler) { dasList.AddRange(_winUsb15Handler.GetDASList()); }
if (null != _winTsrAirHandler) { dasList.AddRange(_winTsrAirHandler.GetDASList()); }
lock (MyLock)
{
foreach (var eh in _ethernetHandler)
{
dasList.AddRange(eh.GetDASList());
}
}
dasList.AddRange(_ethernetTDASHandler.GetDASList());
dasList.AddRange(_restSPFDHandler.GetDASList());
return dasList;
}
public List<IDASCommunication> GetSortedDASList()
{
var list = GetDASList();
var sdbs = new List<IDASCommunication>();
for (var i = list.Count - 1; i >= 0; i--)
{
if (!list[i].IsEthernetDistributor()) continue;
sdbs.Add(list[i]);
list.RemoveAt(i);
}
list.Sort(CompareDas);
foreach (var t in sdbs)
{
var ip = ((ICommunication)t).ConnectString.Split(':')[0];
for (var z = 0; z < list.Count; z++)
{
if (!((ICommunication)list[z]).ConnectString.Contains(ip)) continue;
list.Insert(z, t);
break;
}
}
return list;
}
public static int CompareDas(IDASCommunication left, IDASCommunication right)
{
if (Equals(left, right)) { return 0; }
if (null == left.ConfigData) { return -1; }
if (null == right.ConfigData) { return 1; }
return left.ConfigData.DasDisplayOrder == right.ConfigData.DasDisplayOrder ?
string.Compare(left.SerialNumber, right.SerialNumber, StringComparison.Ordinal) : left.ConfigData.DasDisplayOrder.CompareTo(right.ConfigData.DasDisplayOrder);
}
/// <summary>
/// Retrieves the ICommunication object of all hardware currently connected.
/// </summary>
/// <returns>List of ICommunications representing all hardware connected.</returns>
public List<ICommunication> GetDevList()
{
var devList = new List<ICommunication>();
if (null != _winTsrAirHandler) { devList.AddRange(_winTsrAirHandler.GetDevList()); }
if (null != _cdcusbHandler) { devList.AddRange(_cdcusbHandler.GetDevList()); }
if (null != _winUsb15Handler) { devList.AddRange(_winUsb15Handler.GetDevList()); }
if (null != _winUsbHandler) { devList.AddRange(_winUsbHandler.GetDevList()); }
lock (MyLock)
{
foreach (var eh in _ethernetHandler)
{
devList.AddRange(eh.GetDevList());
}
}
devList.AddRange(_ethernetTDASHandler.GetDevList());
devList.AddRange(_restSPFDHandler.GetDevList());
return devList;
}
/// <summary>
/// Make DASFactory forget about all devices.
/// </summary>
public void DetachAllDevices(bool detachUSB = false)
{
// FB14290: Hardware step fails with USB SLICE the first time, succeeds the second.
// Detaching USB devices every call is unnecessary
var devicesWereRemoved = false;
if (detachUSB)
{
if (_bUsingUSB)
{
devicesWereRemoved = _winUsbHandler.DetachAllDevices()
|| _cdcusbHandler.DetachAllDevices()
|| _winUsb15Handler.DetachAllDevices()
|| _winTsrAirHandler.DetachAllDevices();
}
}
lock (MyLock)
{
foreach (var eh in _ethernetHandler)
{
if (eh.DetachAllDevices()) { devicesWereRemoved = true; }
}
}
if (_ethernetTDASHandler.DetachAllDevices())
{
devicesWereRemoved = true;
}
if (_restSPFDHandler.DetachAllDevices())
{
devicesWereRemoved = true;
}
// notify our subscribers if something was removed
if (devicesWereRemoved)
{
ReportRemoved();
}
//these should be the same instance, I'm just being extra careful here.
PingUtils.DasToHost.Clear();
DasToHost.Clear();
}
/// <summary>
/// Unregister and close the file we may have opened on the removable drive.
/// Garbage collector will call this method.
/// </summary>
public void Dispose()
{
// Prevent garbage collector getting rid of your mutex object...
GC.KeepAlive(_singletonFlag);
if (_winUsbHandler != null)
{
_winUsbHandler.Dispose();
_winUsbHandler = null;
}
if (_cdcusbHandler != null)
{
_cdcusbHandler.Dispose();
_cdcusbHandler = null;
}
if (_winUsb15Handler != null)
{
_winUsb15Handler.Dispose();
_winUsb15Handler = null;
}
if (_winTsrAirHandler != null)
{
_winTsrAirHandler.Dispose();
_winTsrAirHandler = null;
}
foreach (var eh in _ethernetHandler)
{
eh.Dispose();
}
_ethernetHandler.Clear();
if (_ethernetTDASHandler != null)
{
_ethernetTDASHandler.Dispose();
_ethernetTDASHandler = null;
}
_restSPFDHandler?.Dispose();
_restSPFDHandler = null;
_queueActionPerDevice.CompleteAdding();
}
#region Refresh handling
/// <summary>
/// Lock variable to serialize calls to Refresh
/// </summary>
private int _refreshLock;
/// <summary>
/// What to call after refresh is done (only not null when a refresh is active)
/// </summary>
private ActionCompleteDelegate _currentRefreshFinishedAction;
/// <summary>
/// Lock to serialize calls from HID and WinUSB
/// </summary>
private static readonly object UpdateFinishedCallbackLock = new object();
/// <summary>
/// This keeps track of the state of one device
/// </summary>
private class RefreshFlags
{
private bool ConnectDone { get; set; }
private bool DisconnectDone { get; set; }
private DeviceHandling Target { get; set; }
public RefreshFlags(DeviceHandling dev)
{
Target = dev;
ConnectDone = false;
DisconnectDone = false;
}
public void Start()
{
Target.EnqueueConnect();
}
public void DoNext()
{
Target.EnqueueDisconnect();
}
public void ActionFinished()
{
if (ConnectDone)
{
DisconnectDone = true;
}
else
{
ConnectDone = true;
}
}
public bool AllFinished()
{
return ConnectDone && DisconnectDone;
}
}
/// <summary>
/// Indicates if the HID disconnect is done
/// </summary>
private Dictionary<DeviceHandling, RefreshFlags> _deviceStates;
/// <summary>
/// Callback function for HID and WinUSB updates.
/// If we need to support more than HID and WinUSB, this function and the *Done
/// variables above needs to be changed to a proper statemachine mechanism.
/// </summary>
/// <param name="dev">the <see cref="DTS.DASLib.DASFactory.DeviceHandling" /> device belonging
/// to this update
/// </param>
private void UpdateFinishedCallback(DeviceHandling dev)
{
// only one at a time
lock (UpdateFinishedCallbackLock)
{
if (_currentRefreshFinishedAction == null)
{
// not currently running refresh
return;
}
if (!_deviceStates.ContainsKey(dev)) { return; }
_deviceStates[dev].ActionFinished();
if (!_deviceStates[dev].AllFinished())
{
_deviceStates[dev].DoNext();
}
else
{
// check if all done
if (_deviceStates.Count(flags => flags.Value.AllFinished()) != _deviceStates.Count) return;
// yes we are, unhook and call
var temp = _currentRefreshFinishedAction;
_currentRefreshFinishedAction = null;
temp();
}
}
}
/// <summary>
/// Setup the "statemachines" above to do a refresh
/// </summary>
/// <param name="action">The action to perform when finished</param>
private void SetupRefreshActions(ActionCompleteDelegate action)
{
lock (UpdateFinishedCallbackLock)
{
if (_bUsingUSB)
{
_deviceStates = new Dictionary<DeviceHandling, RefreshFlags>
{
{_winUsbHandler, new RefreshFlags(_winUsbHandler)},
{_cdcusbHandler, new RefreshFlags(_cdcusbHandler)},
{_winUsb15Handler, new RefreshFlags(_winUsb15Handler)},
{_winTsrAirHandler, new RefreshFlags(_winTsrAirHandler)}
};
}
else
{
_deviceStates = new Dictionary<DeviceHandling, RefreshFlags>();
}
foreach (var eh in _ethernetHandler)
{
_deviceStates.Add(eh, new RefreshFlags(eh));
}
_deviceStates.Add(_ethernetTDASHandler, new RefreshFlags(_ethernetTDASHandler));
_deviceStates.Add(_restSPFDHandler, new RefreshFlags(_restSPFDHandler));
// it's all ours
_currentRefreshFinishedAction = action;
if (_bUsingUSB)
{
// start the action
_deviceStates[_winUsbHandler].Start();
_deviceStates[_cdcusbHandler].Start();
_deviceStates[_winUsb15Handler].Start();
_deviceStates[_winTsrAirHandler].Start();
}
foreach (var eh in _ethernetHandler)
{
_deviceStates[eh].Start();
}
_deviceStates[_ethernetTDASHandler].Start();
_deviceStates[_restSPFDHandler].Start();
}
}
/// <summary>
/// Initiate a refresh of the DASFactory (i.e. make sure GetDASList() reflects what's actually connected)
/// </summary>
/// <param name="action">The action to perform when the refresh is done</param>
public void Refresh(ActionCompleteDelegate action)
{
if (action == null)
{
throw new ArgumentException("Refresh: You must provide an action");
}
// only one call at a time
if (Interlocked.Exchange(ref _refreshLock, 1) != 0)
{
// no, we're too late
throw new BusyException("only one refresh at a time");
}
// we can only have one refresh running at a time
if (_currentRefreshFinishedAction != null)
{
// someone is already waiting for refresh to finish
Interlocked.Exchange(ref _refreshLock, 0);
throw new BusyException("only one refresh at a time");
}
// get going
SetupRefreshActions(action);
// let go off the lock
Interlocked.Exchange(ref _refreshLock, 0);
}
#endregion
#region Multicast AutoDiscovery
private readonly SortableBindingList<IDiscoveredDevice> _discoveredSlices =
new SortableBindingList<IDiscoveredDevice>();
/// <summary>
/// configures the default timeout in ms for Multicast auto discovery receive functions
/// </summary>
public int MultiCastAutoDiscoveryDefaultTimeoutMS
{
get => MulticastCommandBase.DEFAULT_RECEIVE_TIMEOUT_MS;
set => MulticastCommandBase.DEFAULT_RECEIVE_TIMEOUT_MS = value;
}
/// <summary>
/// configures the transmit address for Multicast auto discovery functions
/// </summary>
public string MulticastAutoDiscoveryAddress
{
get => MulticastCommandBase.MulticastAddress;
set => MulticastCommandBase.MulticastAddress = value;
}
/// <summary>
/// configures the transmit port for Multicast auto discovery functions
/// </summary>
public int MulticastAutoDiscoveryPort
{
get => MulticastCommandBase.CommandPort;
set => MulticastCommandBase.CommandPort = value;
}
/// <summary>
/// configures the receive port for Multicast auto discovery functions
/// </summary>
public int MulticastAutoDiscoveryResponsePort
{
get => MulticastCommandBase.ResponsePort;
set => MulticastCommandBase.ResponsePort = value;
}
private void ParseDiscoveryReturns(DiscoveredConnectedSlice[] ConnectedDevices, ref Dictionary<string, IDiscoveredDevice> macAddressToDevice)
{
foreach (var record in ConnectedDevices)
{
var hostMac = record.DeviceMAC.Replace('-', ':').ToUpper();
if (!macAddressToDevice.ContainsKey(hostMac))
{
continue;
}
var device = macAddressToDevice[hostMac];
var connections = new List<ConnectedEthernetDevice>();
foreach (var connection in record.ConnectedDevices)
{
var strMac = MulticastDiscoverSlice6.MACAddressToString(connection.MAC);
if (strMac == hostMac) { continue; }
if (!macAddressToDevice.ContainsKey(strMac))
{
continue;
}
connections.Add(
new ConnectedEthernetDevice(strMac, connection.Port)
{
SerialNumber = macAddressToDevice[strMac].Serial
});
}
device.Connections = connections.ToArray();
}
}
//FB 25590 support multiple udp hosts
private readonly List<MulticastUdpQueryQATS> _udpQATSList = new List<MulticastUdpQueryQATS>();
/// <summary>
/// starts the qats listening thread
/// </summary>
public void StartQATSListening()
{
if (!_udpQATSList.Any())
{
UpdateQATSList();
}
else
{
//FB 25590 first stop each
foreach (var qats in _udpQATSList)
{
//just in case there's already a QATS listening (there shouldn't be), stop it and then recreate
//so that we have updated binding address, etc
qats.StopListening();
}
_udpQATSList.Clear();
UpdateQATSList();
}
//FB 25590 then start each
foreach (var qats in _udpQATSList)
{
qats.StartListening();
}
}
private void UpdateQATSList()
{
//FB 25590 support multiple udp hosts, create multiple MulticastUdpQueryQATS for each host ip that supports multicast
var hosts = Common.Utils.NetworkUtils.GetAvailableHosts(true);
foreach (var host in hosts)
{
var bindToAdapterIPAddress = IPAddress.Any;
if (IPAddress.TryParse(host.HostIpAddress, out var newAddress)) { bindToAdapterIPAddress = newAddress; }
var queryQATS = new MulticastUdpQueryQATS(host.HostMacAddress) { BindToAdapterIPAddress = bindToAdapterIPAddress };
_udpQATSList.Add(queryQATS);
}
}
/// <summary>
/// stops QATS listening
/// </summary>
public void StopQATSListening()
{
//FB 25590 stop each
foreach (var qats in _udpQATSList)
{
qats?.StopListening();
}
}
/// <summary>
/// broadcasts a request for QATS
/// </summary>
public void SendQATSRequest()
{
//FB 25590 send to each one
foreach (var qats in _udpQATSList)
{
qats?.SendCommand();
}
}
/// <summary>
/// return any stored QATS, clear list
/// </summary>
/// <returns></returns>
public IUDPQATSEntry[] GetQATS()
{
List<IUDPQATSEntry> qatsEntryList = new List<IUDPQATSEntry>();
if (!_udpQATSList.Any()) { return new IUDPQATSEntry[0]; }
//Add all the result in one list
foreach (var qats in _udpQATSList)
{
qatsEntryList.AddRange(qats.GetUDPQATs());
}
return qatsEntryList.ToArray();
}
public SortableBindingList<IDiscoveredDevice> AutoDiscoverMulticast(CancellationToken cancelToken, bool discoverParents = true)
{
var list = new List<IDiscoveredDevice>();
//FB 25642 support multiple NICs
var hostInfos = Common.Utils.NetworkUtils.GetAvailableHosts(true);
foreach (var hostInfo in hostInfos)
{
IPAddress bindToAdapterIPAddress = IPAddress.Any;
if (IPAddress.TryParse(hostInfo.HostIpAddress, out var newAddress)) { bindToAdapterIPAddress = newAddress; }
var cmd = new MulticastAutoDiscover(hostInfo.HostMacAddress) { Logger = _autoDiscoveryLog, BindToAdapterIPAddress = bindToAdapterIPAddress };
cmd.MulticastExecute(cancelToken);
var macAddressToDevice = new Dictionary<string, IDiscoveredDevice>();
foreach (var device in cmd.DiscoveredDevices)
{
if (!list.Any(p => p.Ip == device.Ip))
{
list.Add(device);
if (discoverParents)
{
macAddressToDevice[device.Mac.Replace('-', ':').ToUpper()] = device;
}
}
}
if (discoverParents)
{
var cmd2 = new MulticastDiscoverSlice6(MulticastCommandBase.DeviceClasses.Any, hostInfo.HostMacAddress)
{
MACAddressToDevice = macAddressToDevice,
BindToAdapterIPAddress = bindToAdapterIPAddress,
Logger = _autoDiscoveryLog
};
cmd2.MulticastExecute(cancelToken);
ParseDiscoveryReturns(cmd2.ConnectedDevices, ref macAddressToDevice);
//this will determine everyone's parents
foreach (var device in cmd.DiscoveredDevices)
{
DiscoverParent(device, macAddressToDevice);
}
//now we can determine port on SDB
foreach (var device in cmd.DiscoveredDevices)
{
var ultimateParent = DiscoveredDevice.GetParent(device);
if (null != ultimateParent)
{
device.Port = ultimateParent.GetPort(device);
}
}
//now we can set the "slot" for each device
foreach (var device in cmd.DiscoveredDevices)
{
DiscoverSlot(device, macAddressToDevice);
}
}
}
_discoveredSlices.Clear();
foreach( var d in list) { _discoveredSlices.Add(d); }
return _discoveredSlices;
}
private static void DiscoverSlot(IDiscoveredDevice device, Dictionary<string, IDiscoveredDevice> lookup)
{
if (null == device.Parent)
{
device.PositionOnDistributor = 0;
return;
}
//we want to know what slot we are on our ULTIMATE parent, so we have to go up through the list of parents till there is no parent
var ultimateParent = DiscoveredDevice.GetParent(device);
if (ultimateParent.DevClass == DFConstantsAndEnums.MultiCastDeviceClasses.S6DB)
{
device.IsModule = true;
device.PositionOnDistributor = ultimateParent.GetSlot(device, lookup);
device.PositionOnChain = ultimateParent.GetSlotOnPort(device, lookup);
}
else
{
device.IsModule = false;
}
}
private static void DiscoverParent(IDiscoveredDevice device, Dictionary<string, IDiscoveredDevice> lookup)
{
if (null == device) { return; }
var eAllDevices = lookup.GetEnumerator();
while (eAllDevices.MoveNext())
{
if (eAllDevices.Current.Value.IsParent(device))
{
device.Parent = eAllDevices.Current.Value;
}
}
}
#endregion
/// <summary>
/// returns IP address for connected device
/// </summary>
/// <param name="das"></param>
/// <returns></returns>
private string GetIPAddress(IDASCommunication das)
{
if (!(das is ICommunication icom)) { return string.Empty; }
var s = icom.ConnectString;
if (string.IsNullOrWhiteSpace(s)) { return string.Empty; }
var idx = s.IndexOf(':');
if (idx > 0) { s = s.Substring(0, idx); }
return s;
}
/// <summary>
/// returns all unique IP addresses minus USB and empty connection strings
/// </summary>
/// <returns></returns>
private string[] GetUniqueIPAddresses()
{
var devices = GetDASList();
var ipList = devices.Select(device => GetIPAddress(device)).AsEnumerable().Distinct().Where(ip => !ip.ToUpper().Contains("USB") && !string.IsNullOrWhiteSpace(ip));
return ipList.ToArray();
}
/// <summary>
/// pings all attached devices, returns true if there are only USB devices connected or all attached devices
/// ping successfully
/// </summary>
/// <returns></returns>
public bool PingAll()
{
var pingResult = false;
try
{
var ping = new PingUtils.PingDevice();
var ipList = GetUniqueIPAddresses().ToList();
if (ipList.Any()) { APILogger.Log("IPS: ", string.Join(",", ipList)); }
//FB 18152 host selction will be automatic
pingResult = ipList.Count < 1 || ping.PingDevices(ipList);
}
catch (Exception ex)
{
APILogger.LogException(ex);
// Temporarily, if we're using the windows API to ping, dont rely on it for this functionality
return pingResult;
}
return pingResult;
}
}
}