Files
OMControlCs/OMControl/Serial/SerialDeviceProbe.cs

113 lines
3.6 KiB
C#
Raw Permalink Normal View History

2025-12-24 16:41:31 +01:00
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
public sealed class SerialDeviceProbe
{
public record ProbeResult(string Port, bool Success, string? DeviceId, byte[]? RawResponse, string? Error);
// Define known signatures here
// Example: response contains ASCII "DEV:XYZ" or starts with 0xAA 0x55 etc.
private readonly List<(string Name, Func<byte[], bool> Match)> _signatures = new();
public SerialDeviceProbe AddSignature(string name, Func<byte[], bool> match)
{
_signatures.Add((name, match));
return this;
}
public async Task<ProbeResult> ProbeAsync(
string portName,
int baudRate,
byte[] probeBytes,
int readTimeoutMs = 250,
int writeTimeoutMs = 250,
int settleDelayMs = 80,
int maxReadBytes = 256,
CancellationToken ct = default)
{
try
{
return await Task.Run(() =>
{
ct.ThrowIfCancellationRequested();
using var sp = new SerialPort(portName, baudRate)
{
ReadTimeout = readTimeoutMs,
WriteTimeout = writeTimeoutMs,
Encoding = Encoding.ASCII,
DtrEnable = true, // often needed for Arduino-like boards
RtsEnable = true
};
sp.Open();
// Some devices reboot on open (Arduino). Give them a moment.
Thread.Sleep(settleDelayMs);
sp.DiscardInBuffer();
sp.DiscardOutBuffer();
sp.Write(probeBytes, 0, probeBytes.Length);
// Read available bytes up to timeout
var buffer = new List<byte>(maxReadBytes);
var start = Environment.TickCount;
while (buffer.Count < maxReadBytes && Environment.TickCount - start < readTimeoutMs)
{
ct.ThrowIfCancellationRequested();
try
{
int b = sp.ReadByte(); // blocks until byte or timeout
if (b >= 0) buffer.Add((byte)b);
// Optional: break early if you know response length or terminator
// e.g. if buffer ends with '\n'
if (buffer.Count >= 2 && buffer[^1] == (byte)'\n') break;
}
catch (TimeoutException)
{
break;
}
}
var resp = buffer.ToArray();
if (resp.Length == 0)
return new ProbeResult(portName, false, null, resp, "No response");
// Identify by signature
foreach (var sig in _signatures)
{
if (sig.Match(resp))
return new ProbeResult(portName, true, sig.Name, resp, null);
}
// Unknown but responded
return new ProbeResult(portName, true, "Unknown", resp, null);
}, ct);
}
catch (Exception ex)
{
return new ProbeResult(portName, false, null, null, ex.Message);
}
}
public async Task<ProbeResult[]> ProbeAllAsync(
IEnumerable<string> ports,
int baudRate,
byte[] probeBytes,
CancellationToken ct = default)
{
var tasks = ports.Select(p => ProbeAsync(p, baudRate, probeBytes, ct: ct));
return await Task.WhenAll(tasks);
}
}