Files
HawkeyeVision/Plugins/Siemens/B24SiemensEmulator/TcpClient.cs
2025-07-28 09:52:29 +02:00

167 lines
4.7 KiB
C#

using System;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Timer = System.Threading.Timer;
namespace B24SiemensEmulator
{
public class SiemensTcpClient : IDisposable
{
private TcpClient? _tcpClient;
private NetworkStream? _stream;
private Timer? _sendTimer;
private bool _isConnected = false;
private bool _isRunning = false;
private readonly object _lock = new object();
public event Action<bool>? ConnectionStatusChanged;
public event Action<PLCPacket>? ResponseReceived;
public event Action<string>? ErrorOccurred;
private readonly string _serverHost = "localhost";
private readonly int _serverPort = 42010;
private readonly int _sendIntervalMs = 1000;
public Func<PLCPacket>? GetPacketToSend;
public bool IsConnected => _isConnected;
public bool IsRunning => _isRunning;
public async Task<bool> ConnectAsync()
{
try
{
if (_isConnected)
return true;
_tcpClient = new TcpClient();
await _tcpClient.ConnectAsync(_serverHost, _serverPort);
_stream = _tcpClient.GetStream();
lock (_lock)
{
_isConnected = true;
}
ConnectionStatusChanged?.Invoke(true);
return true;
}
catch (Exception ex)
{
ErrorOccurred?.Invoke($"Connection failed: {ex.Message}");
await DisconnectAsync();
return false;
}
}
public async Task DisconnectAsync()
{
lock (_lock)
{
_isConnected = false;
}
_stream?.Close();
_stream?.Dispose();
_stream = null;
_tcpClient?.Close();
_tcpClient?.Dispose();
_tcpClient = null;
ConnectionStatusChanged?.Invoke(false);
}
public void StartSending()
{
if (_isRunning)
return;
_isRunning = true;
_sendTimer = new Timer(SendPacketCallback, null, 0, _sendIntervalMs);
}
public void StopSending()
{
_isRunning = false;
_sendTimer?.Dispose();
_sendTimer = null;
}
private async void SendPacketCallback(object? state)
{
if (!_isConnected || GetPacketToSend == null)
return;
try
{
var packet = GetPacketToSend();
await SendPacketAsync(packet);
}
catch (Exception ex)
{
ErrorOccurred?.Invoke($"Send failed: {ex.Message}");
await HandleConnectionLoss();
}
}
private async Task SendPacketAsync(PLCPacket packet)
{
if (_stream == null || !_isConnected)
throw new InvalidOperationException("Not connected");
var data = packet.Serialize();
await _stream.WriteAsync(data, 0, data.Length);
// Read response
var responseBuffer = new byte[19]; // PLCPacket is 19 bytes
int totalBytesRead = 0;
while (totalBytesRead < responseBuffer.Length)
{
int bytesRead = await _stream.ReadAsync(responseBuffer, totalBytesRead,
responseBuffer.Length - totalBytesRead);
if (bytesRead == 0)
throw new Exception("Server disconnected");
totalBytesRead += bytesRead;
}
var responsePacket = PLCPacket.Deserialize(responseBuffer);
ResponseReceived?.Invoke(responsePacket);
}
private async Task HandleConnectionLoss()
{
await DisconnectAsync();
// Wait 1 second before trying to reconnect
await Task.Delay(1000);
if (_isRunning)
{
var reconnected = await ConnectAsync();
if (!reconnected)
{
// Schedule another reconnection attempt
_ = Task.Run(async () =>
{
await Task.Delay(1000);
if (_isRunning && !_isConnected)
{
await HandleConnectionLoss();
}
});
}
}
}
public void Dispose()
{
StopSending();
DisconnectAsync().Wait(1000);
}
}
}