Files
MewtocolNet/MewtocolNet/ComCassette/CassetteFinder.cs
2023-07-27 19:03:02 +02:00

145 lines
4.6 KiB
C#

using MewtocolNet.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace MewtocolNet.ComCassette {
/// <summary>
/// Provides a interface to modify and find PLC network cassettes also known as COM5
/// </summary>
public class CassetteFinder {
public static async Task<IEnumerable<CassetteInformation>> FindClientsAsync(string ipSource = null, int timeoutMs = 100) {
Logger.Log("Scanning for cassettes over UDP");
var from = new IPEndPoint(IPAddress.Any, 0);
var interfacesTasks = new List<Task<List<CassetteInformation>>>();
var usableInterfaces = Mewtocol.GetUseableNetInterfaces();
if (ipSource == null) {
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface netInterface in usableInterfaces) {
IPInterfaceProperties ipProps = netInterface.GetIPProperties();
var unicastInfo = ipProps.UnicastAddresses
.FirstOrDefault(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
var ep = new IPEndPoint(unicastInfo.Address, 0);
interfacesTasks.Add(FindClientsForEndpoint(ep, timeoutMs, netInterface.Name));
}
} else {
from = new IPEndPoint(IPAddress.Parse(ipSource), 0);
var netInterface = usableInterfaces.FirstOrDefault(x => x.GetIPProperties().UnicastAddresses.Any(y => y.Address.ToString() == ipSource));
if (netInterface == null)
throw new NotSupportedException($"The host endpoint {ipSource}, is not available");
interfacesTasks.Add(FindClientsForEndpoint(from, timeoutMs, netInterface.Name));
}
//run the interface querys
var grouped = await Task.WhenAll(interfacesTasks);
var decomposed = new List<CassetteInformation>();
foreach (var grp in grouped) {
foreach (var cassette in grp) {
if (decomposed.Any(x => x.MacAddress.SequenceEqual(cassette.MacAddress))) continue;
decomposed.Add(cassette);
}
}
Logger.Log($"Found {decomposed.Count} cassettes");
return decomposed.OrderBy(x => x.IPAddress.ToString());
}
private static async Task<List<CassetteInformation>> FindClientsForEndpoint(IPEndPoint from, int timeoutMs, string ipEndpointName) {
var cassettesFound = new List<CassetteInformation>();
int plcPort = 9090;
// Byte msg to request the status transmission of all plcs
byte[] requestCode = new byte[] { 0x88, 0x40, 0x00 };
// The start code of the status transmission response
byte[] startCode = new byte[] { 0x88, 0xC0, 0x00 };
using (var udpClient = new UdpClient()) {
udpClient.EnableBroadcast = true;
udpClient.Client.Bind(from);
//broadcast packet to all devices (plc specific package)
udpClient.Send(requestCode, requestCode.Length, "255.255.255.255", plcPort);
//canceling after no new data was read
CancellationTokenSource tSource = new CancellationTokenSource();
var tm = new System.Timers.Timer(timeoutMs);
tm.Elapsed += (s, e) => {
tSource.Cancel();
tm.Stop();
};
tm.Start();
//wait for devices to send response
try {
byte[] recvBuffer = null;
while (!tSource.Token.IsCancellationRequested) {
var res = await udpClient.ReceiveAsync().WithCancellation(tSource.Token);
if (res.Buffer == null) break;
recvBuffer = res.Buffer;
if (recvBuffer.SearchBytePattern(startCode) == 0) {
tm.Stop();
tm.Start();
var parsed = CassetteInformation.FromBytes(recvBuffer, from, ipEndpointName);
if (parsed != null) cassettesFound.Add(parsed);
}
}
} catch (OperationCanceledException) { } catch (SocketException) { }
}
return cassettesFound;
}
}
}