using System; using System.Text.RegularExpressions; using IoTClient.Clients.PLC; using IoTClient.Common.Enums; namespace StandardScene.Signal.Plc { /// /// 西门子 S7 长连接封装(IoTClient)。一个机构一个实例,由对应 Worker 独占。 /// /// /// 字节地址兼容老 Hsl 写法 DB1.0 与 IoTClient 写法 DB1.DBB0; /// 心跳位地址保持原样(如 DB1.DBX2.0),不做 DBB 转换。 /// 连接失败时读/写返回 false,由 Worker 标断线,不在这里抛给扫车线程。 /// public sealed class SiemensPlcSession : IDisposable { /// 匹配 Hsl 风格「DB块号.字节偏移」,捕获组 1=DB,2=偏移。 private static readonly Regex BareDbByte = new Regex(@"^DB(\d+)\.(\d+)$", RegexOptions.IgnoreCase | RegexOptions.Compiled); private readonly SiemensVersion _version; private readonly string _ip; private readonly int _port; private readonly byte _slot; private SiemensClient _client; private readonly object _sync = new object(); /// 进程字段 PlcVersion,如 S7_1500。 /// 机构 IP。 /// S7 端口,通常 102。 /// 机架槽号,S7-1500 常见 1。 public SiemensPlcSession(string version, string ip, int port, int slot) { _version = ParseVersion(version); _ip = ip ?? ""; _port = port > 0 ? port : 102; _slot = (byte)Math.Max(0, slot); } /// 当前客户端是否已 Open 成功。未创建客户端时为 false。 public bool Connected { get { lock (_sync) return _client?.Connected == true; } } /// 惰性创建客户端并 Open。已连接则直接 true;失败返回 false 不抛。 public bool EnsureOpen() { lock (_sync) { try { if (_client == null) _client = new SiemensClient(_version, _ip, _port, _slot); if (_client.Connected) return true; var r = _client.Open(); return r != null && r.IsSucceed; } catch { return false; } } } /// 读一字节。地址会先规范化;未连接或失败时 value=0 且返回 false。 public bool TryReadByte(string address, out byte value) { value = 0; if (string.IsNullOrWhiteSpace(address) || !EnsureOpen()) return false; try { var r = _client.ReadByte(NormalizeByteAddress(address)); if (r == null || !r.IsSucceed) return false; value = r.Value; return true; } catch { return false; } } /// 写一字节(进入中/到位等)。失败返回 false。 public bool TryWriteByte(string address, byte value) { if (string.IsNullOrWhiteSpace(address) || !EnsureOpen()) return false; try { var r = _client.Write(NormalizeByteAddress(address), value); return r != null && r.IsSucceed; } catch { return false; } } /// 读心跳位。地址不做 DBB 转换。 public bool TryReadBool(string address, out bool value) { value = false; if (string.IsNullOrWhiteSpace(address) || !EnsureOpen()) return false; try { var r = _client.ReadBoolean(address.Trim()); if (r == null || !r.IsSucceed) return false; value = r.Value; return true; } catch { return false; } } /// 写心跳位(通常写当前值的反码,让 PLC 看到翻转)。 public bool TryWriteBool(string address, bool value) { if (string.IsNullOrWhiteSpace(address) || !EnsureOpen()) return false; try { var r = _client.Write(address.Trim(), value); return r != null && r.IsSucceed; } catch { return false; } } /// 关闭并丢弃客户端,下次 EnsureOpen 会新建。 public void Close() { lock (_sync) { try { _client?.Close(); } catch { } _client = null; } } public void Dispose() => Close(); /// /// DB1.0DB1.DBB0;已是 DBB/DBW/DBX 的地址原样返回。 /// internal static string NormalizeByteAddress(string address) { var addr = address.Trim(); var m = BareDbByte.Match(addr); return m.Success ? $"DB{m.Groups[1].Value}.DBB{m.Groups[2].Value}" : addr; } /// 空或无法解析时默认 S7_1500。允许把 S7-1500 写成带连字符。 internal static SiemensVersion ParseVersion(string version) { if (string.IsNullOrWhiteSpace(version)) return SiemensVersion.S7_1500; return Enum.TryParse(version.Replace("-", "_"), true, out SiemensVersion parsed) ? parsed : SiemensVersion.S7_1500; } } }