diff --git a/DeviceCommand/Base/Visa.cs b/DeviceCommand/Base/Visa.cs index 59380e1..ac78003 100644 --- a/DeviceCommand/Base/Visa.cs +++ b/DeviceCommand/Base/Visa.cs @@ -195,13 +195,21 @@ namespace DeviceCommand.Base } } + /// + /// 读取定长原始二进制字节(用于 IT6720 等自定义二进制帧协议)。 + /// VISA 会话在 中默认启用了以 \n(0x0A) 为终止符的文本读取; + /// 而二进制帧的数据区可能恰好包含 0x0A 字节,若不禁用终止符,RawIO.Read 会在该字节处提前结束, + /// 导致返回长度不足、帧校验失败。故本方法读取期间临时关闭终止符,读完再由 finally 恢复。 + /// 单次 RawIO.Read(count) 只保证返回「至多 count」字节,可能因分包而少于请求长度; + /// 这里参照 的做法循环补足,直到读满 length 或发生超时/END/流终止。 + /// public async Task ReadAsync(int length, CancellationToken ct = default) { await _commLock.WaitAsync(ct); try { EnsureConnected(); - return await Task.Run(() => _session!.RawIO.Read(length), ct); + return await Task.Run(() => ReadExactBytes(length), ct); } finally { @@ -209,6 +217,50 @@ namespace DeviceCommand.Base } } + /// + /// 同步读取定长二进制字节:关闭终止符后循环补足,直到读满 字节; + /// 超时/END/流终止导致读不满时按实到长度截断返回(由上层按帧长校验判定成功与否)。 + /// + private byte[] ReadExactBytes(int length) + { + if (length <= 0) return Array.Empty(); + + bool prevTermEnabled = _session!.TerminationCharacterEnabled; + // 二进制帧数据可能含 0x0A,读取全程禁用终止符,避免被误判为帧结束而截断 + _session!.TerminationCharacterEnabled = false; + try + { + var buffer = new byte[length]; + int offset = 0; + while (offset < length) + { + byte[] chunk; + try + { + chunk = _session!.RawIO.Read(length - offset); + } + catch (Exception) when (offset > 0) + { + // 已收到部分数据后再超时/异常:返回已收字节,交由上层按帧长校验处理 + break; + } + if (chunk == null || chunk.Length == 0) break; + Buffer.BlockCopy(chunk, 0, buffer, offset, chunk.Length); + offset += chunk.Length; + } + + if (offset == length) return buffer; + // 实际读到的字节少于请求长度:按实到长度截断返回,避免尾部填充 0 + var trimmed = new byte[offset]; + Buffer.BlockCopy(buffer, 0, trimmed, 0, offset); + return trimmed; + } + finally + { + _session!.TerminationCharacterEnabled = prevTermEnabled; + } + } + public async Task ReadAsync(string delimiter = "\n", CancellationToken ct = default) { await _commLock.WaitAsync(ct); diff --git a/DeviceCommand/Devices/IT6720.cs b/DeviceCommand/Devices/IT6720.cs index 53043ba..fb637ed 100644 --- a/DeviceCommand/Devices/IT6720.cs +++ b/DeviceCommand/Devices/IT6720.cs @@ -13,14 +13,14 @@ namespace DeviceCommand.Devices /// 低压电源 一拖三 /// [ACPCommand] - public class IT6720: Serial_Port // 直接继承您提供的 Serial_Port 基类 + public class IT6720 : Visa // 继承 VISA 基类(USB VISA 资源字符串寻址,如 USB0::VID::PID::SN::INSTR) { // 帧格式:同步头 AA + 地址 + 命令字 + 数据[4-25] + 校验和,共 26 字节 private const byte FrameSyncByte = 0xAA; private const int FrameLength = 26; private readonly byte _deviceAddress = 0x00; // 电源地址 - public IT6720(SerialPortConfig config) : base(config) + public IT6720(VisaConfig config) : base(config) { } @@ -109,12 +109,12 @@ namespace DeviceCommand.Devices { byte[] sendFrame = BuildFrame(command, dataBytes); - // 使用基类提供的 WriteBytesAsync 发送二进制数据 - await WriteBytesAsync(sendFrame, ct).ConfigureAwait(false); + // 使用基类提供的 SendAsync(byte[]) 通过 VISA 会话发送二进制数据帧 + await SendAsync(sendFrame, ct).ConfigureAwait(false); - // 使用基类提供的 ReadBytesAsync 读取定长 26 字节二进制数据 - // 注意:这里的 ReadTimeout 由基类统一控制 - return await ReadBytesAsync(FrameLength, ct).ConfigureAwait(false); + // 使用基类提供的 ReadAsync(int) 读取定长 26 字节二进制数据帧 + // 注意:读写超时(IOTimeout)与终止符处理均由 VISA 基类统一控制 + return await ReadAsync(FrameLength, ct).ConfigureAwait(false); } #endregion