using System; using System.Diagnostics; using System.IO.Ports; using System.Threading; namespace AutoFocusTool.Serial { /// /// 单舱室串口控制器(马达 + LED)。自包含,无外部实体依赖。 /// 采用同步请求-响应:写命令帧 → 按该命令固定回复长度阻塞读 → 校验。 /// 比原工程的事件+环形缓冲简单,适合调试台单线程操作。 /// 串口参数固定:9600 8N1,读写超时 3000ms。 /// public class SerialMotor : IDisposable { private readonly SerialPort _port; private readonly object _ioLock = new object(); /// 日志回调(发送/接收/错误) public Action Log; public string PortName { get; } public bool IsOpen => _port != null && _port.IsOpen; public SerialMotor(string portName) { PortName = portName; _port = new SerialPort { PortName = portName, BaudRate = 9600, DataBits = 8, StopBits = StopBits.One, Parity = Parity.None, ReadTimeout = 3000, WriteTimeout = 3000, }; } public bool Open() { try { if (!_port.IsOpen) _port.Open(); _port.DiscardInBuffer(); _port.DiscardOutBuffer(); return true; } catch (Exception ex) { Log?.Invoke($"[{PortName}] 打开串口失败: {ex.Message}"); return false; } } public void Close() { try { if (_port.IsOpen) _port.Close(); } catch { } } /// /// 发送命令帧并读取固定长度回复。返回回复字节(失败返回 null)。 /// /// 完整命令帧(含校验) /// 读到回复后额外等待(电机到位延时) public byte[] Send(byte[] frame, int extraWaitMs = 0) { if (!IsOpen && !Open()) return null; int replyLen = Protocol.ReplyLength(frame[1]); lock (_ioLock) { try { _port.DiscardInBuffer(); _port.Write(frame, 0, frame.Length); Log?.Invoke($"[{PortName}] 发送: {ToHex(frame)}"); byte[] reply = ReadFixed(replyLen, 3000); if (reply == null) { Log?.Invoke($"[{PortName}] 接收超时(期望{replyLen}字节)"); return null; } Log?.Invoke($"[{PortName}] 接收: {ToHex(reply)}{(Protocol.CheckChecksum(reply) ? "" : " [校验失败]")}"); // 回复 [n-2] 为下位机结果位,非0视为失败(返回null表示操作失败) if (replyLen >= 2 && reply[replyLen - 2] != 0) { Log?.Invoke($"[{PortName}] 下位机结果位非0(操作失败)"); return null; } if (extraWaitMs > 0) Thread.Sleep(extraWaitMs); return reply; } catch (Exception ex) { Log?.Invoke($"[{PortName}] 收发异常: {ex.Message}"); return null; } } } /// 阻塞读取指定字节数,超时返回 null。 private byte[] ReadFixed(int count, int timeoutMs) { byte[] buf = new byte[count]; int got = 0; var sw = Stopwatch.StartNew(); while (got < count) { if (sw.ElapsedMilliseconds > timeoutMs) return null; try { int n = _port.Read(buf, got, count - got); got += n; } catch (TimeoutException) { if (sw.ElapsedMilliseconds > timeoutMs) return null; } } return buf; } private static string ToHex(byte[] b) { if (b == null) return "(null)"; var sb = new System.Text.StringBuilder(b.Length * 3); foreach (var x in b) sb.Append(x.ToString("X2")).Append(' '); return sb.ToString().TrimEnd(); } public void Dispose() { Close(); _port?.Dispose(); } } }