using ZB.MOM.WW.OtOpcUa.Driver.AbCip; namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests; /// /// Per-family provisioning profile for the ab_server simulator. Instead of hard-coding /// one fixture shape + one set of CLI args, each integration test picks a profile matching the /// family it wants to exercise — ControlLogix / CompactLogix / Micro800 / GuardLogix. The /// profile composes the CLI arg list passed to ab_server + the tag-definition set the /// driver uses to address the simulator's pre-provisioned tags. /// /// OtOpcUa driver family this profile targets. Drives /// + driver-side connection-parameter profile /// (ConnectionSize, unconnected-only, etc.) per decision #9. /// The value passed to ab_server --plc <arg>. Some families /// map 1:1 (ControlLogix → "controllogix"); Micro800/GuardLogix fall back to the family whose /// CIP behavior ab_server emulates most faithfully (see per-profile Notes). /// Tags to preseed on the simulator via --tag <name>:<type>[:<size>] /// flags. Each entry becomes one CLI arg; the driver-side /// list references the same names so tests can read/write without walking the @tags surface /// first. /// Operator-facing description of what the profile covers + any quirks. public sealed record AbServerProfile( AbCipPlcFamily Family, string AbServerPlcArg, IReadOnlyList SeedTags, string Notes) { /// Default port — every profile uses the same so parallel-runs-of-different-families /// would conflict (deliberately — one simulator per test collection is the model). public const int DefaultPort = 44818; /// Compose the full ab_server CLI arg string for /// . public string BuildCliArgs(int port) { var parts = new List { "--port", port.ToString(), "--plc", AbServerPlcArg, }; foreach (var tag in SeedTags) { parts.Add("--tag"); parts.Add(tag.ToCliSpec()); } return string.Join(' ', parts); } } /// One tag the simulator pre-creates. ab_server spec format: /// <name>:<type>[:<array_size>]. public sealed record AbServerSeedTag(string Name, string AbServerType, int? ArraySize = null) { public string ToCliSpec() => ArraySize is { } n ? $"{Name}:{AbServerType}:{n}" : $"{Name}:{AbServerType}"; } /// Canonical profiles covering every AB CIP family shipped in PRs 9–12. public static class KnownProfiles { /// /// ControlLogix — the widest-coverage family: full CIP capabilities, generous connection /// size, @tags controller-walk supported. Tag shape covers atomic types + a Program-scoped /// tag so the Symbol-Object decoder's scope-split path is exercised. /// public static readonly AbServerProfile ControlLogix = new( Family: AbCipPlcFamily.ControlLogix, AbServerPlcArg: "controllogix", SeedTags: new AbServerSeedTag[] { new("TestDINT", "DINT"), new("TestREAL", "REAL"), new("TestBOOL", "BOOL"), new("TestSINT", "SINT"), new("TestString","STRING"), new("TestArray", "DINT", ArraySize: 16), }, Notes: "Widest-coverage profile — PR 9 baseline. UDTs live in PR 6-shipped Template Object tests; ab_server lacks full UDT emulation."); /// /// CompactLogix — narrower ConnectionSize quirk exercised here. ab_server doesn't /// enforce the narrower limit itself; the driver-side profile caps it + this simulator /// honors whatever the client asks for. Tag set is a subset of ControlLogix. /// public static readonly AbServerProfile CompactLogix = new( Family: AbCipPlcFamily.CompactLogix, AbServerPlcArg: "compactlogix", SeedTags: new AbServerSeedTag[] { new("TestDINT", "DINT"), new("TestREAL", "REAL"), new("TestBOOL", "BOOL"), }, Notes: "Narrower ConnectionSize than ControlLogix — driver-side profile caps it per PR 10. Tag set mirrors the CompactLogix atomic subset."); /// /// Micro800 — unconnected-only family. ab_server has no explicit micro800 plc mode so /// we fall back to the nearest CIP-compatible emulation (controllogix) + document the /// discrepancy. Driver-side path enforcement (empty routing path, unconnected-only /// sessions) is exercised in the unit suite; this integration profile smoke-tests that /// reads work end-to-end against the unconnected path. /// public static readonly AbServerProfile Micro800 = new( Family: AbCipPlcFamily.Micro800, AbServerPlcArg: "controllogix", // ab_server lacks dedicated micro800 mode — see Notes SeedTags: new AbServerSeedTag[] { new("TestDINT", "DINT"), new("TestREAL", "REAL"), }, Notes: "ab_server has no --plc micro800 — falls back to controllogix emulation. Driver side still enforces empty path + unconnected-only per PR 11. Real Micro800 coverage requires a 2080 on a lab rig."); /// /// GuardLogix — safety-capable ControlLogix variant with ViewOnly safety tags. ab_server /// doesn't emulate the safety subsystem; we preseed a safety-suffixed name (_S) so /// the driver's read-only classification path is exercised against a real tag. /// public static readonly AbServerProfile GuardLogix = new( Family: AbCipPlcFamily.GuardLogix, AbServerPlcArg: "controllogix", SeedTags: new AbServerSeedTag[] { new("TestDINT", "DINT"), new("SafetyDINT_S", "DINT"), // _S-suffixed → driver classifies as safety-ViewOnly per PR 12 }, Notes: "ab_server has no safety subsystem — this profile emulates the tag-naming contract. Real safety-lock behavior requires a physical GuardLogix 1756-L8xS rig."); public static IReadOnlyList All { get; } = new[] { ControlLogix, CompactLogix, Micro800, GuardLogix }; public static AbServerProfile ForFamily(AbCipPlcFamily family) => All.FirstOrDefault(p => p.Family == family) ?? throw new ArgumentOutOfRangeException(nameof(family), family, "No integration profile for this family."); }