328 lines
13 KiB
C#
328 lines
13 KiB
C#
using System;
|
|
using System.Net;
|
|
using System.Net.NetworkInformation;
|
|
using System.Net.Sockets;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using DTS.Common.Enums.DASFactory;
|
|
using DTS.Common.Utilities.Logging;
|
|
using DTS.Common.Utils;
|
|
|
|
namespace DTS.DASLib.Command.SLICE.MulticastCommands
|
|
{
|
|
public abstract class MulticastCommandBase : CommandBase
|
|
{
|
|
protected const int IP_ADDR_SIZE = 16;
|
|
protected const int MAC_ADDR_SIZE = 18;
|
|
protected const int DOUBLE_MAC_ADDR_SIZE = 2 * MAC_ADDR_SIZE;
|
|
|
|
protected const int HOST_MAC_ADDRESS_OFFSET = 0;
|
|
protected const int CLIENT_MAC_ADDRESS_OFFSET = MAC_ADDR_SIZE;
|
|
protected const int FIRST_PARAMETER_OFFSET = CLIENT_MAC_ADDRESS_OFFSET + MAC_ADDR_SIZE;
|
|
protected const int DEFAULT_COMMAND_PAYLOAD_SIZE = DOUBLE_MAC_ADDR_SIZE;
|
|
protected const int DEFAULT_RESPONSE_PAYLOAD_SIZE = DOUBLE_MAC_ADDR_SIZE;
|
|
|
|
protected enum Commands
|
|
{
|
|
Reserved = 0x00,
|
|
AutoDiscover = 0x01,
|
|
SetIpAddress = 0x02,
|
|
GetIpAddress = 0x03,
|
|
SetSubnetAddress = 0x04,
|
|
GetSubnetAddress = 0x05,
|
|
SetGatewayAddress = 0x06,
|
|
GetGatewayAddress = 0x07,
|
|
SetDnsAddress = 0x08,
|
|
GetDnsAddress = 0x09,
|
|
SetDhcp = 0x0A,
|
|
GetDhcp = 0x0B,
|
|
ResetMcu = 0x0C,
|
|
WakeupBootloader = 0x0D,
|
|
Identify = 0x0E,
|
|
GetMACTable = 0x0F,
|
|
GetMACTable_Reserved = 0x10, // created in FW but not being used. Just ignore this command for now.
|
|
UdpQueryQTAS = 0x11, // query Arm-Trigger status via UDP multicast or unicast message via discovery route.
|
|
UdpAutoQATS = 0x12, // message command ID for auto-arm self-start UDP multicast QATS
|
|
UdpQATSInit = 0x13, // host command to send to DAS to start/stop UdpAutoQats message.
|
|
UdpAutoTiltSensorData = 0x14, //Periodic UDP response when enabled by cmd ID 0x13
|
|
UdpPrepareToShutdown = 0x15, //Command UUT to preserve auto-arm configuration and go to idle state ready for power down.
|
|
}
|
|
|
|
public enum Ports
|
|
{
|
|
Command = 8501,
|
|
Response = 8503,
|
|
}
|
|
|
|
public enum DeviceClasses
|
|
{
|
|
Unknown = 0,
|
|
Slice6 = 1 << 0,
|
|
SDB = 1 << 1,
|
|
ECM = 1 << 2,
|
|
S6DB = 1 << 3,
|
|
|
|
Slice6Air = 1 << 4,
|
|
PowerPro = 1 << 5,
|
|
TsrAir = 1 << 6,
|
|
S6DB3 = 1 << 7,
|
|
Any = 0xFFFF,
|
|
}
|
|
|
|
public enum ResponseOptions
|
|
{
|
|
AlwaysRespond = 0,
|
|
RespondIfNotBusy = 1,
|
|
RespondIfNotConnected = 2
|
|
}
|
|
|
|
public static int DEFAULT_RECEIVE_TIMEOUT_MS = 3000;
|
|
public const string DEFAULT_MULTICAST_ADDRESS = "239.1.2.3";
|
|
public const string DEFAULT_RECEIVE_ADDRESS = "239.4.5.6";
|
|
|
|
public static string MulticastAddress = DEFAULT_MULTICAST_ADDRESS;
|
|
public static string MulticastReceiveAddress = DEFAULT_RECEIVE_ADDRESS;
|
|
|
|
public static int CommandPort = (int)Ports.Command;
|
|
public static int ResponsePort = (int)Ports.Response;
|
|
|
|
protected abstract Commands Command { get; }
|
|
|
|
protected MulticastCommandBase(DTS.Common.Interface.DASFactory.ICommunication sock)
|
|
: base(sock)
|
|
{
|
|
command.Type = CommandPacket.CommandType.Multicast;
|
|
command.SetCommand((byte)Command, Command.ToString());
|
|
|
|
command.Parameter = new byte[DEFAULT_COMMAND_PAYLOAD_SIZE];
|
|
}
|
|
|
|
protected MulticastCommandBase(DTS.Common.Interface.DASFactory.ICommunication sock, int timeoutMillisec)
|
|
: base(sock, timeoutMillisec)
|
|
{
|
|
command.Type = CommandPacket.CommandType.Multicast;
|
|
command.SetCommand((byte)Command, Command.ToString());
|
|
|
|
command.Parameter = new byte[DEFAULT_COMMAND_PAYLOAD_SIZE];
|
|
}
|
|
|
|
public void BuildPacket()
|
|
{
|
|
baseCommand.ComputeCRCs();
|
|
}
|
|
|
|
protected virtual bool StopAfterFirstMessage => true;
|
|
|
|
public int ReceiveTimeoutMs { get; set; } = DEFAULT_RECEIVE_TIMEOUT_MS;
|
|
|
|
private string _commandHostMac;
|
|
protected string CommandClientMac;
|
|
protected string ResponseHostMac;
|
|
protected string ResponseClientMac;
|
|
|
|
public string HostMac
|
|
{
|
|
get => _commandHostMac;
|
|
set { _commandHostMac = value; command.SetParameter(HOST_MAC_ADDRESS_OFFSET, _commandHostMac); }
|
|
}
|
|
public string ClientMac { set { CommandClientMac = value; command.SetParameter(CLIENT_MAC_ADDRESS_OFFSET, CommandClientMac); } }
|
|
|
|
protected override CommandReceiveAction WholePackage()
|
|
{
|
|
if (response.Status != DFConstantsAndEnums.CommandStatus.StatusNoError)
|
|
return CommandReceiveAction.StopReceiving;
|
|
response.GetParameter(HOST_MAC_ADDRESS_OFFSET, out ResponseHostMac);
|
|
response.GetParameter(CLIENT_MAC_ADDRESS_OFFSET, out ResponseClientMac);
|
|
|
|
if (!CommandClientMac.Equals(ResponseClientMac) || !HostMac.Equals(ResponseHostMac))
|
|
{
|
|
response.Status = DFConstantsAndEnums.CommandStatus.StatusInvalidPacket;
|
|
}
|
|
|
|
return CommandReceiveAction.StopReceiving;
|
|
}
|
|
public IPAddress BindToAdapterIPAddress { get; set; } = IPAddress.Any;
|
|
private void SendRequest()
|
|
{
|
|
var txGroupAddress = IPAddress.Parse(MulticastAddress);
|
|
var remoteEndPoint = new IPEndPoint(txGroupAddress, CommandPort);
|
|
|
|
UdpClient client = null;
|
|
|
|
if (BindToAdapterIPAddress == IPAddress.Any)
|
|
{
|
|
client = new UdpClient();
|
|
}
|
|
else
|
|
{
|
|
client = new UdpClient(new IPEndPoint(BindToAdapterIPAddress, CommandPort));
|
|
}
|
|
|
|
client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
|
client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.IpTimeToLive, 4);
|
|
|
|
command.SequenceNumber = 0;
|
|
command.ComputeCRCs();
|
|
var data = command.ToBytes();
|
|
|
|
client.Send(data, data.Length, remoteEndPoint);
|
|
client.Close();
|
|
}
|
|
/// <summary>
|
|
/// alternative to MulticastExecute,
|
|
/// just sends the multicast request, relies on
|
|
/// StartListening to have been called to process any incoming data
|
|
/// </summary>
|
|
public void SendCommand()
|
|
{
|
|
SendRequest();
|
|
}
|
|
/// <summary>
|
|
/// used to signal Listen() function that it should terminate
|
|
/// </summary>
|
|
protected ManualResetEvent _stopListening = new ManualResetEvent(false);
|
|
|
|
/// <summary>
|
|
/// starts a listening thread
|
|
/// </summary>
|
|
public void StartListening()
|
|
{
|
|
CancellationToken ct;
|
|
Task.Run(() => ReceiveThread(null, true, ct)).ConfigureAwait(false);
|
|
}
|
|
/// <summary>
|
|
/// stops any listening thread
|
|
/// </summary>
|
|
public void StopListening() => _stopListening.Set();
|
|
public void MulticastExecute(CancellationToken ct, bool waitForResponse = true)
|
|
{
|
|
if (ct.IsCancellationRequested) { return; }
|
|
//It's easier to kick off a worker thread to handle the receiver
|
|
using (var signalComplete = new ManualResetEvent(true))
|
|
{
|
|
if (true == waitForResponse)
|
|
{
|
|
signalComplete.Reset();
|
|
Task.Run(() => ReceiveThread(signalComplete, false, ct));
|
|
|
|
//Give it time to come up
|
|
Thread.Sleep(100);
|
|
}
|
|
|
|
//Send request once receive socket is set up
|
|
SendRequest();
|
|
WaitHandle.WaitAny(new[] { signalComplete, ct.WaitHandle });
|
|
}
|
|
}
|
|
|
|
protected virtual void ProcessResponse(byte[] data)
|
|
{
|
|
response = new CommandPacket(data);
|
|
WholePackage();
|
|
}
|
|
private const int UDP_ASYNC_RESPONSETIME_MS = 1000;
|
|
/// <summary>
|
|
/// Listens for UDP responses. Can signal when complete and can listen until _stopListening MRE is set.
|
|
/// </summary>
|
|
/// <param name="signalComplete">This MRE is set when the process is complete</param>
|
|
/// <param name="listenUntilStopped">This will cause the listening loop to continue untill stopped by the _stopListening MRE</param>
|
|
private void ReceiveThread(ManualResetEvent signalComplete, bool listenUntilStopped, CancellationToken ct)
|
|
{
|
|
var rxGroupAddress = IPAddress.Parse(MulticastReceiveAddress);
|
|
var endPoint = new IPEndPoint(BindToAdapterIPAddress, ResponsePort);
|
|
var receiver = new UdpClient();
|
|
|
|
receiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
|
receiver.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, ReceiveTimeoutMs);
|
|
|
|
_stopListening.Reset();
|
|
|
|
if (BindToAdapterIPAddress == IPAddress.Any) { receiver.ExclusiveAddressUse = false; }
|
|
|
|
try
|
|
{
|
|
receiver.Client.Bind(endPoint);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
APILogger.Log(ex);
|
|
if (!ct.IsCancellationRequested)
|
|
{
|
|
signalComplete?.Set();
|
|
}
|
|
return;
|
|
}
|
|
if (ct.IsCancellationRequested) { return; }
|
|
try
|
|
{
|
|
if (BindToAdapterIPAddress == IPAddress.Any)
|
|
{
|
|
receiver.JoinMulticastGroup(rxGroupAddress);
|
|
}
|
|
else
|
|
{
|
|
receiver.JoinMulticastGroup(rxGroupAddress, BindToAdapterIPAddress);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
APILogger.Log(ex);
|
|
}
|
|
|
|
var timeToWait = TimeSpan.FromMilliseconds(UDP_ASYNC_RESPONSETIME_MS);
|
|
byte[] data = new byte[0];
|
|
IAsyncResult asyncResult = null;
|
|
do
|
|
{
|
|
if (BindToAdapterIPAddress != IPAddress.Any && !NetworkUtils.IsNetworkInterfaceUp(BindToAdapterIPAddress))
|
|
{
|
|
Thread.Sleep(100);
|
|
continue;
|
|
}
|
|
asyncResult = receiver.BeginReceive(null, null);
|
|
asyncResult.AsyncWaitHandle.WaitOne(timeToWait);
|
|
|
|
if (asyncResult.IsCompleted)
|
|
{
|
|
try
|
|
{
|
|
IPEndPoint remoteEP = null;
|
|
|
|
|
|
data = receiver.EndReceive(asyncResult, ref remoteEP);
|
|
// EndReceive worked and we have received data and remote endpoint
|
|
System.Diagnostics.Debug.WriteLine("Got " + data.Length + " bytes from " + remoteEP);
|
|
ProcessResponse(data);
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
APILogger.Log(ex);
|
|
}
|
|
}
|
|
} while (!_stopListening.WaitOne(0, false) && (asyncResult == null || asyncResult.IsCompleted) && !ct.IsCancellationRequested);
|
|
receiver.Close();
|
|
if (ct.IsCancellationRequested) { return; }
|
|
if (null != signalComplete && !signalComplete.WaitOne(0))
|
|
{
|
|
signalComplete.Set();
|
|
}
|
|
}
|
|
|
|
//This is a little hokey. It returns the first interface that's multicast compatible AND 'up'. It may not actually be the interface
|
|
//that we're interested in.
|
|
public static string GetMacAddress()
|
|
{
|
|
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
|
|
foreach (var i in interfaces)
|
|
{
|
|
if (OperationalStatus.Up != i.OperationalStatus || !i.SupportsMulticast) continue;
|
|
var address = BitConverter.ToString(i.GetPhysicalAddress().GetAddressBytes());
|
|
address = address.Replace("-", ":");
|
|
return address;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
} |