| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- using System;
- using System.Diagnostics;
- using System.IO.Ports;
- using System.Threading;
- namespace AutoFocusTool.Serial
- {
- /// <summary>
- /// 单舱室串口控制器(马达 + LED)。自包含,无外部实体依赖。
- /// 采用同步请求-响应:写命令帧 → 按该命令固定回复长度阻塞读 → 校验。
- /// 比原工程的事件+环形缓冲简单,适合调试台单线程操作。
- /// 串口参数固定:9600 8N1,读写超时 3000ms。
- /// </summary>
- public class SerialMotor : IDisposable
- {
- private readonly SerialPort _port;
- private readonly object _ioLock = new object();
- /// <summary>日志回调(发送/接收/错误)</summary>
- public Action<string> 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 { }
- }
- /// <summary>
- /// 发送命令帧并读取固定长度回复。返回回复字节(失败返回 null)。
- /// </summary>
- /// <param name="frame">完整命令帧(含校验)</param>
- /// <param name="extraWaitMs">读到回复后额外等待(电机到位延时)</param>
- 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;
- }
- }
- }
- /// <summary>阻塞读取指定字节数,超时返回 null。</summary>
- 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();
- }
- }
- }
|