对 `AuxPLC` 进行了重构,使其完全基于 `DriverBase` 提供的 `NoLockExecute` 机制进行底层字节收发,同时保留原有的字符串指令接口
对AuxPLC进行了重构使其完全基于DriverBase提供的NoLockExecute机制进行底层字节收发同时保留原有的字符串指令接口。主要优化点移除 HslCommunication 依赖– 不再使用外部库直接通过DriverBase的 TCP 连接器发送/接收原始字节。实现协议解析– 重写ExtractPayload方法支持基于 Modbus TCP 协议的帧完整性判断您可根据实际 PLC 协议自行调整。提供字节级接口– 新增SendReceive方法内部调用EnqueueExecute包装NoLockExecute确保线程安全。字符串指令适配– 修改WriteData/QueryData将指令转换为 Modbus TCP 报文通过SendReceive执行并解析响应。保留连接管理– 添加Connect/Disconnect方法显式控制底层连接器的状态。以下是优化后的AuxPLC完整代码usingRobot.Communication.Base;usingRobot.Communication.Enums;usingSystem;usingSystem.Linq;namespaceRobot.Device{/// summary/// 辅助 PLC 驱动基于 Modbus TCP 协议可扩展/// /summarypublicclassAuxPLC:DriverBase{// ------------------- 构造函数 -------------------publicAuxPLC(EtherNetParameterparameter):base(parameter,ConnectMode.Tcp){}publicAuxPLC(stringip,ushortport):this(newEtherNetParameter(){IPip,Portport}){ReadTimeout1000;}// ------------------- 连接管理 -------------------publicvoidConnect()EnqueueExecute(()base.Connect());publicvoidDisconnect()EnqueueExecute(()base.DisConnect());// ------------------- 核心字节收发线程安全 -------------------/// summary/// 发送命令并接收响应自动加锁/// /summary/// param namecommand完整的请求报文/param/// returns有效载荷已去除协议头/尾/returns/// exception crefException超时或协议错误时抛出/exceptionpublicbyte[]SendReceive(byte[]command){returnEnqueueExecute((){varresultNoLockExecute(command);// 内部会循环读取直到 ExtractPayload 返回非空if(result.Payloadnull)thrownewException(响应解析失败未提取到有效数据);returnresult.Payload;});}/// summary/// 仅发送不等待响应如写命令无需确认时/// /summarypublicvoidSendOnly(byte[]command){EnqueueExecute(()NoLockExecuteNoResponse(command));}// ------------------- 实现 DriverBase 的抽象方法 -------------------/// summary/// 判断接收到的数据是否完整若完整则提取有效载荷去除协议头/尾/// 此处以 Modbus TCP 为例您可根据实际 PLC 协议修改此方法/// /summary/// param namewriteData发送的命令/param/// param namereadData当前累计接收的原始数据/param/// returns有效数据若未完成则返回 null/returnsprotectedoverridebyte[]?ExtractPayload(byte[]writeData,byte[]readData){if(readData.Length6)returnnull;// 至少需要事务标识符 协议标识符 长度字段// Modbus TCP 帧结构事务标识(2) 协议标识(2) 长度(2) 单元标识(1) 功能码(1) 数据(n)intlength(readData[4]8)readData[5];// 后面数据的字节数含单元标识、功能码、数据inttotalRequired6length;// 完整帧总长度不含事务标识实际 6 已含头if(readData.LengthtotalRequired)returnnull;// 尚未收完整// 提取有效数据跳过 Modbus 头 6 字节保留单元标识、功能码及后续数据byte[]payloadnewbyte[length];Array.Copy(readData,6,payload,0,length);returnpayload;}// ------------------- 字符串指令接口兼容旧代码 -------------------/// summary/// 写入 PLC 变量格式 M01 或 D1001234/// /summary/// returns0成功负数错误码/returnspublicintWriteData(stringinstruction){if(!Connected||string.IsNullOrWhiteSpace(instruction))return-1;try{varpartsinstruction.Split();if(parts.Length!2)return-2;stringaddressparts[0].Trim().ToUpper();stringvalueStrparts[1].Trim();// 构建 Modbus 写请求报文byte[]commandBuildWriteCommand(address,valueStr);if(commandnull)return-4;byte[]responseSendReceive(command);// 发送并等待响应returnValidateWriteResponse(response)?0:-3;}catch{return-5;}}/// summary/// 查询 PLC 变量如 M0 或 D100/// /summary/// returnsbool 或 short 值失败返回 null/returnspublicobject?QueryData(stringinstruction){if(!Connected||string.IsNullOrWhiteSpace(instruction))returnnull;try{stringaddressinstruction.Trim().ToUpper();byte[]commandBuildReadCommand(address);if(commandnull)returnnull;byte[]responseSendReceive(command);returnParseReadResponse(response,address);}catch{returnnull;}}// ------------------- 协议辅助方法Modbus TCP 示例请按实际修改 -------------------privatestaticreadonlyushort_nextTransId0;// 实际使用应递增此处简化privatestaticushortGetNextTransactionId()(ushort)(newRandom().Next(1,65535));privatebyte[]BuildReadCommand(stringaddress){// 解析地址例如 M0 - 线圈, D100 - 保持寄存器boolisCoiladdress.StartsWith(M)||address.StartsWith(X)||address.StartsWith(Y);ushortstartAddrParseAddress(address);ushortquantity1;bytefuncCodeisCoil?(byte)0x01:(byte)0x03;byte[]datanewbyte[4];data[0](byte)(startAddr8);data[1](byte)(startAddr0xFF);data[2](byte)(quantity8);data[3](byte)(quantity0xFF);returnBuildModbusRequest(funcCode,data);}privatebyte[]BuildWriteCommand(stringaddress,stringvalueStr){boolisCoiladdress.StartsWith(M)||address.StartsWith(X)||address.StartsWith(Y);ushortstartAddrParseAddress(address);if(isCoil){boolvalvalueStr1||valueStr.Equals(true,StringComparison.OrdinalIgnoreCase);byte[]datanewbyte[4];data[0](byte)(startAddr8);data[1](byte)(startAddr0xFF);data[2](byte)(val?0xFF:0x00);data[3]0x00;returnBuildModbusRequest(0x05,data);// 写单个线圈}else{if(!short.TryParse(valueStr,outshortval))thrownewArgumentException(寄存器值必须是整数);byte[]datanewbyte[4];data[0](byte)(startAddr8);data[1](byte)(startAddr0xFF);data[2](byte)(val8);data[3](byte)(val0xFF);returnBuildModbusRequest(0x06,data);// 写单个寄存器}}privatebyte[]BuildModbusRequest(bytefuncCode,byte[]data){ushorttransIdGetNextTransactionId();ushortprotoId0;ushortlength(ushort)(11data.Length);// 单元标识(1) 功能码(1) 数据byteunitId1;byte[]requestnewbyte[611data.Length];request[0](byte)(transId8);request[1](byte)(transId0xFF);request[2](byte)(protoId8);request[3](byte)(protoId0xFF);request[4](byte)(length8);request[5](byte)(length0xFF);request[6]unitId;request[7]funcCode;Array.Copy(data,0,request,8,data.Length);returnrequest;}privateboolValidateWriteResponse(byte[]payload){// 写响应单元标识功能码起始地址数据内容参考 Modbus 规范returnpayload!nullpayload.Length4(payload[1]0x05||payload[1]0x06);}privateobject?ParseReadResponse(byte[]payload,stringaddress){if(payloadnull||payload.Length2)returnnull;boolisCoiladdress.StartsWith(M)||address.StartsWith(X)||address.StartsWith(Y);if(isCoil){// 读线圈响应功能码 字节数(1) 线圈状态(1)if(payload.Length3payload[1]0x01)return(payload[2]0x01)0x01;// 取最低位}else{// 读寄存器响应功能码 字节数(2) 寄存器值(2)if(payload.Length3payload[1]0x03payload[2]2){shortvalue(short)((payload[3]8)payload[4]);returnvalue;}}returnnull;}privateushortParseAddress(stringaddress){// 提取数字部分如 M10 - 10, D100 - 100stringnumStrnewstring(address.SkipWhile(c!char.IsDigit(c)).ToArray());if(!ushort.TryParse(numStr,outushortaddr))thrownewArgumentException($无效地址:{address});returnaddr;}}}关键优化说明功能实现方式线程安全SendReceive内部使用EnqueueExecute包装NoLockExecute所有收发自动排队协议完整性ExtractPayload根据 Modbus TCP 长度字段判断帧是否完整否则返回null继续读取字节级接口对外提供SendReceive(byte[] command)可直接发送任意自定义协议包字符串指令WriteData/QueryData将字符串指令转换为 Modbus TCP 报文复用SendReceive连接控制添加Connect()/Disconnect()显式调用后Connected状态才有效使用示例varplcnewAuxPLC(192.168.1.100,502);plc.Connect();// 字符串方式plc.WriteData(D1001234);objectvalplc.QueryData(D100);// 直接字节方式例如发送自定义命令0x01 0x03 0x00 0x00 0x00 0x01byte[]cmd{0x00,0x01,0x00,0x00,0x00,0x06,0x01,0x03,0x00,0x00,0x00,0x01};byte[]respplc.SendReceive(cmd);plc.Disconnect();适配其他协议如果您的 PLC 不是 Modbus TCP只需修改ExtractPayload、BuildReadCommand、BuildWriteCommand、ParseReadResponse等方法的内部逻辑外围结构无需变动。此优化完全兼容DriverBase的现有架构并充分利用了NoLockExecute的循环读取与超时机制。如有特定协议细节需要调整欢迎进一步沟通。