init
This commit is contained in:
328
DataPRO/SLICECommands/MulticastCommands/MulticastCommandBase.cs
Normal file
328
DataPRO/SLICECommands/MulticastCommands/MulticastCommandBase.cs
Normal file
@@ -0,0 +1,328 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user