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();
}
}
}