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 Match)> _signatures = new(); public SerialDeviceProbe AddSignature(string name, Func match) { _signatures.Add((name, match)); return this; } public async Task 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(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 ProbeAllAsync( IEnumerable ports, int baudRate, byte[] probeBytes, CancellationToken ct = default) { var tasks = ports.Select(p => ProbeAsync(p, baudRate, probeBytes, ct: ct)); return await Task.WhenAll(tasks); } }