using System.Globalization; using S7.Net.Protocol; namespace ZB.MOM.WW.OtOpcUa.Driver.S7; /// /// ISO-TSAP helpers for the optional LocalTsap / RemoteTsap / ConnectionType /// config: parsing the hex text form and resolving the effective handed to /// S7.Net. Pure — no I/O — so the resolution rules are unit-testable without a PLC. /// internal static class S7Tsap { /// /// Parses a TSAP written as two hex bytes — "01.00", "03:01" (1–2 digits per byte) — or /// as one 16-bit hex value, "0100" / "0x0301". High byte first. /// /// The configured text. /// The 16-bit TSAP (first << 8 | second). /// True when is a valid TSAP. internal static bool TryParse(string? text, out ushort tsap) { tsap = 0; if (string.IsNullOrWhiteSpace(text)) return false; var t = text.Trim(); var sep = t.IndexOfAny(['.', ':', '-']); if (sep >= 0) { if (!TryHexByte(t[..sep], out var hi) || !TryHexByte(t[(sep + 1)..], out var lo)) return false; tsap = (ushort)((hi << 8) | lo); return true; } if (t.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) t = t[2..]; if (t.Length is < 1 or > 4) return false; return ushort.TryParse(t, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out tsap); } private static bool TryHexByte(string s, out byte b) { b = 0; s = s.Trim(); return s.Length is >= 1 and <= 2 && byte.TryParse(s, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out b); } /// Formats a 16-bit TSAP as canonical "XX.YY" (upper-case hex). /// The 16-bit TSAP. /// The canonical text. internal static string Format(ushort tsap) => $"{tsap >> 8:X2}.{tsap & 0xFF:X2}"; /// /// Resolves the TSAP pair to connect with, or null when the options carry no override (the /// caller then keeps the plain Plc(cpu, host, port, rack, slot) construction, unchanged). /// Starts from S7.Net's own per-CPU default for 's rack/slot, then applies, /// in order: LocalTsap replaces the local TSAP; RemoteTsap replaces the remote TSAP; /// ConnectionType replaces only the remote TSAP's first byte. /// /// The driver options. /// The effective pair, or null for "no override". internal static TsapPair? Resolve(S7DriverOptions options) { ArgumentNullException.ThrowIfNull(options); var hasLocal = !string.IsNullOrWhiteSpace(options.LocalTsap); var hasRemote = !string.IsNullOrWhiteSpace(options.RemoteTsap); if (!hasLocal && !hasRemote && options.ConnectionType is null) return null; var def = TsapPair.GetDefaultTsapPair(S7CpuTypeMap.ToS7Net(options.CpuType), options.Rack, options.Slot); var local = def.Local; var remote = def.Remote; if (hasLocal) { if (!TryParse(options.LocalTsap, out var l)) throw new FormatException($"S7 LocalTsap '{options.LocalTsap}' is not a valid TSAP (expected e.g. \"01.00\")."); local = new Tsap((byte)(l >> 8), (byte)(l & 0xFF)); } if (hasRemote) { if (!TryParse(options.RemoteTsap, out var r)) throw new FormatException($"S7 RemoteTsap '{options.RemoteTsap}' is not a valid TSAP (expected e.g. \"03.01\")."); remote = new Tsap((byte)(r >> 8), (byte)(r & 0xFF)); } else if (options.ConnectionType is { } ct) { remote = new Tsap((byte)ct, remote.SecondByte); } return new TsapPair(local, remote); } }