1713 lines
69 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|