153 lines
4.9 KiB
C#
153 lines
4.9 KiB
C#
using System;
|
|
using System.IO.Ports;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace KVMote.Transport
|
|
{
|
|
internal sealed class SerialTransport : IKvmTransport
|
|
{
|
|
private const int BaudRate = 9600;
|
|
private const int MaxTimeouts = 3;
|
|
|
|
private SerialPort? _port;
|
|
private string _portName = "";
|
|
private readonly object _lock = new();
|
|
private int _timeoutCount;
|
|
private bool _disposed;
|
|
|
|
public string DeviceLabel => _portName.Length > 0 ? _portName : "—";
|
|
public bool IsConnected => _port?.IsOpen == true;
|
|
public int ClipboardMaxChars => 500;
|
|
public int ClipboardDelayMs => 8;
|
|
|
|
public event Action<string>? DataReceived;
|
|
|
|
// ── Detection ──────────────────────────────────────────────────────────
|
|
|
|
public Task<bool> DetectAsync(CancellationToken ct) =>
|
|
Task.Run(() => Probe(ct), ct);
|
|
|
|
private bool Probe(CancellationToken ct)
|
|
{
|
|
foreach (string port in SerialPort.GetPortNames())
|
|
{
|
|
if (ct.IsCancellationRequested) return false;
|
|
try
|
|
{
|
|
using var sp = new SerialPort(port, BaudRate)
|
|
{
|
|
WriteTimeout = 200,
|
|
ReadTimeout = 600,
|
|
Encoding = Encoding.ASCII
|
|
};
|
|
sp.Open();
|
|
Thread.Sleep(150);
|
|
sp.Write("~");
|
|
Thread.Sleep(500);
|
|
string resp = sp.ReadExisting();
|
|
sp.Close();
|
|
if (resp.Contains("[PONG]")) { _portName = port; return true; }
|
|
}
|
|
catch { }
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ── Lifecycle ──────────────────────────────────────────────────────────
|
|
|
|
public Task<bool> ConnectAsync() => Task.FromResult(ConnectInternal());
|
|
|
|
private bool ConnectInternal()
|
|
{
|
|
try
|
|
{
|
|
_port = new SerialPort(_portName, BaudRate)
|
|
{
|
|
WriteTimeout = 200,
|
|
Encoding = Encoding.ASCII
|
|
};
|
|
_port.DataReceived += OnDataReceived;
|
|
_port.Open();
|
|
_timeoutCount = 0;
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
_port?.Dispose();
|
|
_port = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public void Disconnect()
|
|
{
|
|
if (_port is null) return;
|
|
try
|
|
{
|
|
_port.DataReceived -= OnDataReceived;
|
|
if (_port.IsOpen) _port.Close();
|
|
}
|
|
catch { }
|
|
finally { _port.Dispose(); _port = null; }
|
|
}
|
|
|
|
// ── Send ───────────────────────────────────────────────────────────────
|
|
|
|
public void Send(byte[] data)
|
|
{
|
|
if (_port is null || !_port.IsOpen) return;
|
|
lock (_lock)
|
|
{
|
|
try
|
|
{
|
|
_port.Write(data, 0, data.Length);
|
|
_timeoutCount = 0;
|
|
}
|
|
catch (TimeoutException)
|
|
{
|
|
if (++_timeoutCount >= MaxTimeouts) MarkDisconnected();
|
|
}
|
|
catch
|
|
{
|
|
MarkDisconnected();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SendLossy(byte[] data)
|
|
{
|
|
if (_port is null || !_port.IsOpen) return;
|
|
if (!Monitor.TryEnter(_lock)) return;
|
|
try { _port.Write(data, 0, data.Length); _timeoutCount = 0; }
|
|
catch { }
|
|
finally { Monitor.Exit(_lock); }
|
|
}
|
|
|
|
// ── Private ────────────────────────────────────────────────────────────
|
|
|
|
private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
string data = _port?.ReadExisting() ?? "";
|
|
if (!string.IsNullOrEmpty(data)) DataReceived?.Invoke(data);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
private void MarkDisconnected()
|
|
{
|
|
try { _port?.Close(); } catch { }
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed) return;
|
|
_disposed = true;
|
|
Disconnect();
|
|
}
|
|
}
|
|
}
|