Files
MewtocolNet/MewtocolNet/MewtocolInterfaceTcp.cs
2023-08-07 18:05:02 +02:00

276 lines
8.1 KiB
C#

using MewtocolNet.Logging;
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace MewtocolNet {
/// <summary>
/// The PLC com interface class
/// </summary>
public sealed class MewtocolInterfaceTcp : MewtocolInterface, IPlcEthernet {
//TCP
internal TcpClient client;
private IPAddress ipAddr;
/// <inheritdoc/>
public string IpAddress => ipAddr.ToString();
/// <inheritdoc/>
public int Port { get; private set; }
/// <inheritdoc/>
public IPEndPoint HostEndpoint { get; internal set; }
internal MewtocolInterfaceTcp() : base() { }
#region TCP connection state handling
/// <inheritdoc/>
public void ConfigureConnection(string ip, int port = 9094, int station = 0xEE) {
if (IsConnected)
throw new NotSupportedException("Can't change the connection settings while the PLC is connected");
if (!IPAddress.TryParse(ip, out ipAddr))
throw new NotSupportedException($"The ip: {ip} is no valid ip address");
if (stationNumber != 0xEE && stationNumber > 99)
throw new NotSupportedException("Station number can't be greater than 99");
Port = port;
stationNumber = station;
Disconnect();
}
/// <inheritdoc/>
public void ConfigureConnection(IPAddress ip, int port = 9094, int station = 0xEE) {
if (IsConnected)
throw new NotSupportedException("Can't change the connection settings while the PLC is connected");
ipAddr = ip;
Port = port;
if (stationNumber != 0xEE && stationNumber > 99)
throw new NotSupportedException("Station number can't be greater than 99");
stationNumber = station;
Disconnect();
}
/// <inheritdoc/>
public override async Task ConnectAsync(Func<Task> callBack = null) => await ConnectAsyncPriv(callBack);
private void BuildTcpClient () {
if (HostEndpoint != null) {
var hasEndpoint = Mewtocol
.GetSourceEndpoints()
.Any(x => x.Address.ToString() == HostEndpoint.Address.ToString());
if (!hasEndpoint)
throw new NotSupportedException($"The specified source endpoint: " +
$"{HostEndpoint}, doesn't exist on the device, " +
$"use 'Mewtocol.GetSourceEndpoints()' to find applicable ones");
client = new TcpClient(HostEndpoint) {
ReceiveBufferSize = RecBufferSize,
NoDelay = false,
ReceiveTimeout = sendReceiveTimeoutMs,
SendTimeout = sendReceiveTimeoutMs,
};
var ep = (IPEndPoint)client.Client.LocalEndPoint;
Logger.Log($"Connecting [MAN] endpoint: {ep.Address}:{ep.Port}", LogLevel.Info, this);
} else {
client = new TcpClient() {
ReceiveBufferSize = RecBufferSize,
NoDelay = false,
ReceiveTimeout = sendReceiveTimeoutMs,
SendTimeout = sendReceiveTimeoutMs,
};
}
}
private async Task ConnectAsyncPriv(Func<Task> callBack = null) {
try {
firstPollTask = new Task(() => { });
Logger.Log($">> Intial connection start <<", LogLevel.Verbose, this);
isConnectingStage = true;
BuildTcpClient();
var result = client.BeginConnect(ipAddr, Port, null, null);
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(ConnectTimeout));
if (!success || !client.Connected) {
Logger.Log("The PLC connection timed out", LogLevel.Error, this);
OnMajorSocketExceptionWhileConnecting();
return;
}
Logger.LogVerbose("TCP/IP Client connected", this);
if (HostEndpoint == null) {
var ep = (IPEndPoint)client.Client.LocalEndPoint;
Logger.Log($"Connecting [AUTO] from: {ep.Address.MapToIPv4()}:{ep.Port} to {GetConnectionInfo()}", LogLevel.Info, this);
}
//get the stream
stream = client.GetStream();
stream.ReadTimeout = 1000;
//get plc info
var plcinf = await GetInfoAsync();
if (plcinf != null) {
if(alwaysGetMetadata) await GetMetadataAsync();
IsConnected = true;
await base.ConnectAsync(callBack);
OnConnected(plcinf);
} else {
Logger.Log("Initial connection failed", LogLevel.Error, this);
OnDisconnect();
}
await Task.CompletedTask;
} catch (SocketException) {
OnMajorSocketExceptionWhileConnecting();
isConnectingStage = false;
}
}
protected override async Task ReconnectAsync (int conTimeout) {
try {
firstPollTask = new Task(() => { });
Logger.Log($">> Reconnect start <<", LogLevel.Verbose, this);
isReconnectingStage = true;
isConnectingStage = true;
IsConnected = false;
BuildTcpClient();
var result = client.BeginConnect(ipAddr, Port, null, null);
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(ConnectTimeout));
if (!success || !client.Connected) {
Logger.Log("The PLC connection timed out", LogLevel.Error, this);
OnMajorSocketExceptionWhileConnecting();
return;
}
Logger.LogVerbose("TCP/IP Client connected", this);
if (HostEndpoint == null) {
var ep = (IPEndPoint)client.Client.LocalEndPoint;
Logger.Log($"Connecting [AUTO] from: {ep.Address.MapToIPv4()}:{ep.Port} to {GetConnectionInfo()}", LogLevel.Info, this);
}
//get the stream
stream = client.GetStream();
stream.ReadTimeout = 1000;
isMessageLocked = false;
//null ongoing tasks
regularSendTask = null;
reconnectTask = Task.CompletedTask;
//try to abort any non read message
//await SendNoResponseCommandAsync($"%{GetStationNumber()}#AB");
//get plc info 2 times to clear old stuff from the buffer
OnReconnected();
//var plcinf = await SendCommandAsync($"%{GetStationNumber()}#RT");
//if (plcinf != null) {
// Logger.Log("Reconnect successfull");
// OnReconnected();
// //await base.ConnectAsync();
// //OnConnected(plcinf);
//} else {
// Logger.Log("Initial connection failed", LogLevel.Error, this);
// OnDisconnect();
//}
await Task.CompletedTask;
} catch (SocketException) {
OnMajorSocketExceptionWhileConnecting();
isConnectingStage = false;
isReconnectingStage = false;
}
}
/// <summary>
/// Gets the connection info string
/// </summary>
public override string GetConnectionInfo() {
return $"{IpAddress}:{Port}";
}
private protected override void OnDisconnect() {
base.OnDisconnect();
if (client != null && client.Connected) {
client.Dispose();
}
}
#endregion
}
}