对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 协议,可扩展)/// </summary>publicclassAuxPLC:DriverBase{// ------------------- 构造函数 -------------------publicAuxPLC(EtherNetParameterparameter):base(parameter,ConnectMode.Tcp){}publicAuxPLC(stringip,ushortport):this(newEtherNetParameter(){IP=ip,Port=port}){ReadTimeout=1000;}// ------------------- 连接管理 -------------------publicvoidConnect()=>EnqueueExecute(()=>base.Connect());publicvoidDisconnect()=>EnqueueExecute(()=>base.DisConnect());// ------------------- 核心字节收发(线程安全) -------------------/// <summary>/// 发送命令并接收响应(自动加锁)/// </summary>/// <param name="command">完整的请求报文</param>/// <returns>有效载荷(已去除协议头/尾)</returns>/// <exception cref="Exception">超时或协议错误时抛出</exception>publicbyte[]SendReceive(byte[]command){returnEnqueueExecute(()=>{varresult=NoLockExecute(command);// 内部会循环读取直到 ExtractPayload 返回非空if(result.Payload==null)thrownewException("响应解析失败:未提取到有效数据");returnresult.Payload;});}/// <summary>/// 仅发送,不等待响应(如写命令无需确认时)/// </summary>publicvoidSendOnly(byte[]command){EnqueueExecute(()=>NoLockExecuteNoResponse(command));}// ------------------- 实现 DriverBase 的抽象方法 -------------------/// <summary>/// 判断接收到的数据是否完整,若完整则提取有效载荷(去除协议头/尾)/// 此处以 Modbus TCP 为例,您可根据实际 PLC 协议修改此方法/// </summary>/// <param name="writeData">发送的命令</param>/// <param name="readData">当前累计接收的原始数据</param>/// <returns>有效数据,若未完成则返回 null</returns>protectedoverridebyte[]?ExtractPayload(byte[]writeData,byte[]readData){if(readData.Length<6)returnnull;// 至少需要事务标识符 + 协议标识符 + 长度字段// Modbus TCP 帧结构:事务标识(2) + 协议标识(2) + 长度(2) + 单元标识(1) + 功能码(1) + 数据(n)intlength=(readData[4]<<8)+readData[5];// 后面数据的字节数(含单元标识、功能码、数据)inttotalRequired=6+length;// 完整帧总长度(不含事务标识?实际 6 已含头)if(readData.Length<totalRequired)returnnull;// 尚未收完整// 提取有效数据:跳过 Modbus 头 6 字节,保留单元标识、功能码及后续数据byte[]payload=newbyte[length];Array.Copy(readData,6,payload,0,length);returnpayload;}// ------------------- 字符串指令接口(兼容旧代码) -------------------/// <summary>/// 写入 PLC 变量(格式 "M0=1" 或 "D100=1234")/// </summary>/// <returns>0=成功,负数=错误码</returns>publicintWriteData(stringinstruction){if(!Connected||string.IsNullOrWhiteSpace(instruction))return-1;try{varparts=instruction.Split('=');if(parts.Length!=2)return-2;stringaddress=parts[0].Trim().ToUpper();stringvalueStr=parts[1].Trim();// 构建 Modbus 写请求报文byte[]command=BuildWriteCommand(address,valueStr);if(command==null)return-4;byte[]response=SendReceive(command);// 发送并等待响应returnValidateWriteResponse(response)?0:-3;}catch{return-5;}}/// <summary>/// 查询 PLC 变量(如 "M0" 或 "D100")/// </summary>/// <returns>bool 或 short 值,失败返回 null</returns>publicobject?QueryData(stringinstruction){if(!Connected||string.IsNullOrWhiteSpace(instruction))returnnull;try{stringaddress=instruction.Trim().ToUpper();byte[]command=BuildReadCommand(address);if(command==null)returnnull;byte[]response=SendReceive(command);returnParseReadResponse(response,address);}catch{returnnull;}}// ------------------- 协议辅助方法(Modbus TCP 示例,请按实际修改) -------------------privatestaticreadonlyushort_nextTransId=0;// 实际使用应递增,此处简化privatestaticushortGetNextTransactionId()=>(ushort)(newRandom().Next(1,65535));privatebyte[]BuildReadCommand(stringaddress){// 解析地址,例如 M0 -> 线圈, D100 -> 保持寄存器boolisCoil=address.StartsWith("M")||address.StartsWith("X")||address.StartsWith("Y");ushortstartAddr=ParseAddress(address);ushortquantity=1;bytefuncCode=isCoil?(byte)0x01:(byte)0x03;byte[]data=newbyte[4];data[0]=(byte)(startAddr>>8);data[1]=(byte)(startAddr&0xFF);data[2]=(byte)(quantity>>8);data[3]=(byte)(quantity&0xFF);returnBuildModbusRequest(funcCode,data);}privatebyte[]BuildWriteCommand(stringaddress,stringvalueStr){boolisCoil=address.StartsWith("M")||address.StartsWith("X")||address.StartsWith("Y");ushortstartAddr=ParseAddress(address);if(isCoil){boolval=valueStr=="1"||valueStr.Equals("true",StringComparison.OrdinalIgnoreCase);byte[]data=newbyte[4];data[0]=(byte)(startAddr>>8);data[1]=(byte)(startAddr&0xFF);data[2]=(byte)(val?0xFF:0x00);data[3]=0x00;returnBuildModbusRequest(0x05,data);// 写单个线圈}else{if(!short.TryParse(valueStr,outshortval))thrownewArgumentException("寄存器值必须是整数");byte[]data=newbyte[4];data[0]=(byte)(startAddr>>8);data[1]=(byte)(startAddr&0xFF);data[2]=(byte)(val>>8);data[3]=(byte)(val&0xFF);returnBuildModbusRequest(0x06,data);// 写单个寄存器}}privatebyte[]BuildModbusRequest(bytefuncCode,byte[]data){ushorttransId=GetNextTransactionId();ushortprotoId=0;ushortlength=(ushort)(1+1+data.Length);// 单元标识(1) + 功能码(1) + 数据byteunitId=1;byte[]request=newbyte[6+1+1+data.Length];request[0]=(byte)(transId>>8);request[1]=(byte)(transId&0xFF);request[2]=(byte)(protoId>>8);request[3]=(byte)(protoId&0xFF);request[4]=(byte)(length>>8);request[5]=(byte)(length&0xFF);request[6]=unitId;request[7]=funcCode;Array.Copy(data,0,request,8,data.Length);returnrequest;}privateboolValidateWriteResponse(byte[]payload){// 写响应:单元标识+功能码+起始地址+数据内容(参考 Modbus 规范)returnpayload!=null&&payload.Length>=4&&(payload[1]==0x05||payload[1]==0x06);}privateobject?ParseReadResponse(byte[]payload,stringaddress){if(payload==null||payload.Length<2)returnnull;boolisCoil=address.StartsWith("M")||address.StartsWith("X")||address.StartsWith("Y");if(isCoil){// 读线圈响应:功能码 + 字节数(1) + 线圈状态(1)if(payload.Length>=3&&payload[1]==0x01)return(payload[2]&0x01)==0x01;// 取最低位}else{// 读寄存器响应:功能码 + 字节数(2) + 寄存器值(2)if(payload.Length>=3&&payload[1]==0x03&&payload[2]==2){shortvalue=(short)((payload[3]<<8)+payload[4]);returnvalue;}}returnnull;}privateushortParseAddress(stringaddress){// 提取数字部分,如 "M10" -> 10, "D100" -> 100stringnumStr=newstring(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状态才有效 |
使用示例
varplc=newAuxPLC("192.168.1.100",502);plc.Connect();// 字符串方式plc.WriteData("D100=1234");objectval=plc.QueryData("D100");// 直接字节方式(例如发送自定义命令:0x01 0x03 0x00 0x00 0x00 0x01)byte[]cmd={0x00,0x01,0x00,0x00,0x00,0x06,0x01,0x03,0x00,0x00,0x00,0x01};byte[]resp=plc.SendReceive(cmd);plc.Disconnect();适配其他协议
如果您的 PLC 不是 Modbus TCP,只需修改ExtractPayload、BuildReadCommand、BuildWriteCommand、ParseReadResponse等方法的内部逻辑,外围结构无需变动。
此优化完全兼容DriverBase的现有架构,并充分利用了NoLockExecute的循环读取与超时机制。如有特定协议细节需要调整,欢迎进一步沟通。