Files
DP44/DataPRO/SLICECommands/MulticastCommands/MulticastCommandBase.cs
2026-04-17 14:55:32 -04:00

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;
}
}
}