#nullable enable using System; using System.IO.Ports; using System.Linq; using System.Threading; public class SerialManager : IDisposable { private SerialPort? port; private DateTime lastResponse = DateTime.UtcNow; private const int BaudRate = 115200; private const int PingTimeoutMs = 300; private const int WatchdogTimeoutMs = 3000; public bool IsConnected => port != null && port.IsOpen; // ---------------- DEVICE DISCOVERY ---------------- public void DiscoverDevice() { DisposePort(); foreach (string com in SerialPort.GetPortNames().OrderBy(s => s)) { string? response = null; try { using (var testPort = CreatePort(com, PingTimeoutMs)) { testPort.Open(); Thread.Sleep(350); // RP2040 resets on open testPort.DiscardInBuffer(); testPort.DiscardOutBuffer(); testPort.Write("PING\n"); try { if (testPort.BytesToRead > 0) response = testPort.ReadLine(); } catch (TimeoutException) { // No response — move to next port } } if (response != null && response.StartsWith("Analog_System_Monitor_")) { port = CreatePort(com, 500); port.Open(); Thread.Sleep(350); // allow RP2040 reboot again lastResponse = DateTime.UtcNow; return; } } catch { // Silent fail — move to next COM port } } port = null; } // ---------------- PORT FACTORY ---------------- private SerialPort CreatePort(string com, int timeout) { return new SerialPort(com, BaudRate) { Parity = Parity.None, DataBits = 8, StopBits = StopBits.One, Handshake = Handshake.None, NewLine = "\n", Encoding = System.Text.Encoding.ASCII, ReadTimeout = timeout, WriteTimeout = timeout, DtrEnable = true, // REQUIRED for RP2040 USB CDC RtsEnable = true }; } // ---------------- WATCHDOG ---------------- public bool WatchdogExpired => (DateTime.UtcNow - lastResponse).TotalMilliseconds > WatchdogTimeoutMs; // ---------------- SEND COMMAND ---------------- public void SendSetAll(string cmd) { if (!IsConnected) return; try { port!.Write(cmd + "\n"); string? response = null; try { if (port.BytesToRead > 0) response = port.ReadLine(); } catch (TimeoutException) { // No response this tick — totally fine } if (response != null && (response.StartsWith("OK") || response.StartsWith("ERROR"))) { lastResponse = DateTime.UtcNow; } } catch { // Silent fail — watchdog will trigger reconnect } } // ---------------- CLEANUP ---------------- private void DisposePort() { try { if (port != null) { port.Close(); port.Dispose(); } } catch { // ignore } port = null; } public void Dispose() { DisposePort(); } }