using LB_SmartVisionCommon; using S7.Net; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading; namespace LB_VisionProcesses.Communicators.SiemensS7 { public class SiemensLBS7 : BaseCommunicator { private Plc plc; // 默认变量地址 public string variable = "DB1.DBD0"; // 数据类型 private string dataType = "String"; // 缓存连接参数 private string ip = "127.0.0.1"; private short rack = 0; private short slot = 1; private CpuType cpuType = CpuType.S71500; public SiemensLBS7(string name = "西门子S7") : base(name) { CommunicatorName = name; CommunicatorBrand = CommunicatorBrand.SiemensS7; // 初始化默认参数 if (!CommunicatorConnections.Contains("地址")) CommunicatorConnections.Add("地址", "192.168.0.1"); if (!CommunicatorConnections.Contains("机架号")) CommunicatorConnections.Add("机架号", "0"); if (!CommunicatorConnections.Contains("插槽号")) CommunicatorConnections.Add("插槽号", "1"); if (!CommunicatorConnections.Contains("型号")) CommunicatorConnections.Add("型号", CpuType.S71500); if (!CommunicatorConnections.Contains("变量地址")) CommunicatorConnections.Add("变量地址", "DB1.DBD0"); if (!CommunicatorConnections.Contains("数据类型")) CommunicatorConnections.Add("数据类型", "String"); // 兼容旧配置 "端口" if (CommunicatorConnections.Contains("端口")) { CommunicatorConnections["插槽号"] = CommunicatorConnections["端口"]; } // 设置默认心跳消息 strHeartbeat = "HEARTBEAT"; } public override bool Connect() { try { // 更新参数 if (CommunicatorConnections.Contains("地址")) ip = CommunicatorConnections["地址"].ToString(); if (CommunicatorConnections.Contains("机架号")) short.TryParse(CommunicatorConnections["机架号"].ToString(), out rack); if (CommunicatorConnections.Contains("插槽号")) short.TryParse(CommunicatorConnections["插槽号"].ToString(), out slot); else if (CommunicatorConnections.Contains("端口")) short.TryParse(CommunicatorConnections["端口"].ToString(), out slot); if (CommunicatorConnections.Contains("型号")) { if (CommunicatorConnections["型号"] is CpuType type) cpuType = type; else Enum.TryParse(CommunicatorConnections["型号"].ToString(), out cpuType); } if (CommunicatorConnections.Contains("变量地址")) variable = CommunicatorConnections["变量地址"].ToString(); if (CommunicatorConnections.Contains("数据类型")) dataType = CommunicatorConnections["数据类型"].ToString(); // 关闭旧连接 plc?.Close(); plc = new Plc(cpuType, ip, rack, slot); plc.Open(); if (plc.IsConnected) { bConnected = true; AsyncLogHelper.Info($"Device:[{CommunicatorName}] 已连接到 {ip} 机架:{rack} 插槽:{slot}"); return true; } else { bConnected = false; AsyncLogHelper.Error($"Device:[{CommunicatorName}] 连接失败: IsConnected 为 false"); return false; } } catch (Exception ex) { bConnected = false; AsyncLogHelper.Error($"Device:[{CommunicatorName}] 连接错误: {ex.Message}"); return false; } } public override bool Disconnect() { try { if (plc != null) { plc.Close(); bConnected = false; AsyncLogHelper.Info($"Device:[{CommunicatorName}] 已断开连接"); } return true; } catch (Exception ex) { AsyncLogHelper.Error($"Device:[{CommunicatorName}] 断开连接错误: {ex.Message}"); return false; } } public override bool SendMessage(string message) { if (plc == null || !plc.IsConnected) { Msg = "连接未开启"; return false; } if (message == strHeartbeat) return plc.IsConnected; try { string targetVar = variable; string strValue = message; // 简单的协议解析:地址:值 if (message.Contains(":")) { var parts = message.Split(new char[] { ':' }, 2); if (parts.Length == 2 && !string.IsNullOrWhiteSpace(parts[0])) { targetVar = parts[0]; strValue = parts[1]; } } object valueToWrite = strValue; // 获取当前数据类型配置 string currentDataType = CommunicatorConnections.Contains("数据类型") ? CommunicatorConnections["数据类型"].ToString() : "String"; // 根据配置的数据类型进行转换 try { switch (currentDataType) { case "Bool": if (strValue == "1") valueToWrite = true; else if (strValue == "0") valueToWrite = false; else valueToWrite = bool.Parse(strValue); break; case "Byte": valueToWrite = byte.Parse(strValue); break; case "Int": // 16-bit valueToWrite = short.Parse(strValue); break; case "DInt": // 32-bit valueToWrite = int.Parse(strValue); break; case "Word": // 16-bit unsigned valueToWrite = ushort.Parse(strValue); break; case "DWord": // 32-bit unsigned valueToWrite = uint.Parse(strValue); break; case "Real": // Float valueToWrite = float.Parse(strValue); break; case "Double": // LReal valueToWrite = double.Parse(strValue); break; case "String": default: valueToWrite = strValue; break; } } catch (FormatException) { Msg = $"无效的{currentDataType}值,请输入正确格式。"; if (currentDataType == "Bool") Msg += " (true/false 或 1/0)"; AsyncLogHelper.Error($"Device:[{CommunicatorName}] {Msg}"); return false; } catch (Exception castEx) { Msg = $"数据转换错误({currentDataType}): {castEx.Message}"; AsyncLogHelper.Error($"Device:[{CommunicatorName}] {Msg}"); return false; } // 尝试写入 plc.Write(targetVar, valueToWrite); AsyncLogHelper.Info($"Device:[{CommunicatorName}] 写入({currentDataType}) {targetVar} = {valueToWrite}"); return true; } catch (Exception ex) { Msg = $"发送消息错误: {ex.Message}"; AsyncLogHelper.Error($"Device:[{CommunicatorName}] {Msg}"); return false; } } public override string ReceiveMsg() { if (plc == null || !plc.IsConnected) return string.Empty; try { // 获取当前数据类型配置 string currentDataType = CommunicatorConnections.Contains("数据类型") ? CommunicatorConnections["数据类型"].ToString() : "String"; if (currentDataType == "String") { var match = Regex.Match(variable, @"DB(\d+)\.DB[B|W|D|X]?(\d+)", RegexOptions.IgnoreCase); if (match.Success) { try { int db = int.Parse(match.Groups[1].Value); int startByte = int.Parse(match.Groups[2].Value); // 读取头部 (MaxLen, ActLen) byte[] header = plc.ReadBytes(DataType.DataBlock, db, startByte, 2); if (header != null && header.Length >= 2) { int actLen = header[1]; if (actLen > 0) { // 读取实际字符数据 byte[] strBytes = plc.ReadBytes(DataType.DataBlock, db, startByte + 2, actLen); strReceiveMsg = Encoding.ASCII.GetString(strBytes); return strReceiveMsg; } else { strReceiveMsg = string.Empty; return strReceiveMsg; } } } catch (Exception ex) { AsyncLogHelper.Error($"Device:[{CommunicatorName}] 读取S7String失败: {ex.Message}"); } } // 如果正则不匹配或读取失败,回退到默认读取 } var result = plc.Read(variable); if (result != null) { // 尝试根据 dataType 格式化输出 (S7.Net 读出来的类型可能与预期不符,特别是 DWord/Real) // 例如 DBD0 默认读出来是 UInt32,如果 dataType 是 Real,需要转换 if (currentDataType == "Real" && (result is uint || result is int)) { byte[] bytes = BitConverter.GetBytes(Convert.ToUInt32(result)); float f = BitConverter.ToSingle(bytes, 0); strReceiveMsg = f.ToString(); } else { strReceiveMsg = result.ToString(); } return strReceiveMsg; } } catch (Exception ex) { AsyncLogHelper.Error($"Device:[{CommunicatorName}] 接收消息错误: {ex.Message}"); } return string.Empty; } /// /// 带填充的 S7 字符串写入,防止残留数据 /// private bool WriteS7StringWithPadding(string address, string value) { try { // 解析地址,例如 DB1.DBB0, DB1.DBD0 var match = Regex.Match(address, @"DB(\d+)\.DB[B|W|D|X]?(\d+)", RegexOptions.IgnoreCase); if (!match.Success) return false; int db = int.Parse(match.Groups[1].Value); int startByte = int.Parse(match.Groups[2].Value); byte maxLen = 254; // 默认最大值 try { var header = plc.ReadBytes(DataType.DataBlock, db, startByte, 1); if (header != null && header.Length > 0 && header[0] > 0) { maxLen = header[0]; } } catch { } byte[] buffer = new byte[maxLen + 2]; buffer[0] = maxLen; int currentLen = Math.Min(value.Length, maxLen); buffer[1] = (byte)currentLen; if (currentLen > 0) { byte[] strBytes = Encoding.ASCII.GetBytes(value); Array.Copy(strBytes, 0, buffer, 2, Math.Min(strBytes.Length, currentLen)); } plc.WriteBytes(DataType.DataBlock, db, startByte, buffer); return true; } catch { return false; } } public object Read(string address) { if (plc == null || !plc.IsConnected) return null; return plc.Read(address); } public void Write(string address, object value) { if (plc != null && plc.IsConnected) plc.Write(address, value); } public override void Dispose() { try { AsyncLogHelper.Info($"Device:[{CommunicatorName}],释放资源(Dispose)"); plc?.Close(); plc = null; GC.SuppressFinalize(this); } catch (Exception ex) { AsyncLogHelper.Error($"Device:[{CommunicatorName}],释放资源(Dispose)错误: " + ex.Message); } } } }