diff --git a/ZB.MOM.WW.OtOpcUa.slnx b/ZB.MOM.WW.OtOpcUa.slnx index 6207782..cc7bc01 100644 --- a/ZB.MOM.WW.OtOpcUa.slnx +++ b/ZB.MOM.WW.OtOpcUa.slnx @@ -37,6 +37,7 @@ + diff --git a/docs/drivers/AbLegacy-Test-Fixture.md b/docs/drivers/AbLegacy-Test-Fixture.md index ee24f14..4dc2ac2 100644 --- a/docs/drivers/AbLegacy-Test-Fixture.md +++ b/docs/drivers/AbLegacy-Test-Fixture.md @@ -3,18 +3,34 @@ Coverage map + gap inventory for the AB Legacy (PCCC) driver — SLC 500 / MicroLogix / PLC-5 / LogixPccc-mode. -**TL;DR: there is no integration fixture.** Everything runs through a -`FakeAbLegacyTag` injected via `IAbLegacyTagFactory`. libplctag powers the -real wire path but ships no in-process fake, and `ab_server` has no PCCC -emulation either — so PCCC behavior against real hardware is trusted from -field deployments, not from CI. +**TL;DR:** Docker integration-test scaffolding lives at +`tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/` (task #224), +reusing the AB CIP `ab_server` image in PCCC mode with per-family +compose profiles (`slc500` / `micrologix` / `plc5`). Scaffold passes +the skip-when-absent contract cleanly. **Wire-level round-trip against +`ab_server` PCCC mode currently fails** with `BadCommunicationError` +on read/write (verified 2026-04-20) — ab_server's PCCC server-side +coverage is narrower than libplctag's PCCC client expects. The smoke +tests target the correct shape for real hardware + should pass when +`AB_LEGACY_ENDPOINT` points at a real SLC 5/05 / MicroLogix. Unit tests +via `FakeAbLegacyTag` still carry the contract coverage. ## What the fixture is -Nothing at the integration layer. -`tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/` is unit-only, all tests -tagged `[Trait("Category", "Unit")]`. The driver accepts -`IAbLegacyTagFactory` via ctor DI; every test supplies a `FakeAbLegacyTag`. +**Integration layer** (task #224, scaffolded with a known ab_server +gap): +`tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/` with +`AbLegacyServerFixture` (TCP-probes `localhost:44818`) + three smoke +tests (parametric read across families, SLC500 write-then-read). Reuses +the AB CIP `otopcua-ab-server:libplctag-release` image via a relative +`build:` context in `Docker/docker-compose.yml` — one image, different +`--plc` flags. See `Docker/README.md` §Known limitations for the +ab_server PCCC round-trip gap + resolution paths. + +**Unit layer**: `tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/` is +still the primary coverage. All tests tagged `[Trait("Category", "Unit")]`. +The driver accepts `IAbLegacyTagFactory` via ctor DI; every test +supplies a `FakeAbLegacyTag`. ## What it actually covers (unit only) @@ -77,20 +93,32 @@ cover the common ones but uncommon ones (`R` counters, `S` status files, ## Follow-up candidates -1. **Nothing open-source** — libplctag's test suite runs against real - hardware; there is no public PCCC simulator comparable to `pymodbus` or - `ab_server`. -2. **Lab rig** — cheapest path is a used SLC 5/05 or MicroLogix 1100 on a - dedicated network; the parts are end-of-life but still available. PLC-5 - and LogixPccc-mode behavior require those specific controllers. -3. **libplctag upstream test harness** — the project's own `tests/` folder - has PCCC cases we could try to adapt, but they assume specific hardware. - -AB Legacy is inherently a trust-the-library driver until someone stands up -a rig. +1. **Fix ab_server PCCC coverage upstream** — the scaffold lands the + Docker infrastructure; the wire-level round-trip gap is in ab_server + itself. Filing a patch to `libplctag/libplctag` to expand PCCC + server-side opcode coverage would make the scaffolded smoke tests + pass without a golden-box tier. +2. **Rockwell RSEmulate 500 golden-box tier** — Rockwell's real emulator + for SLC/MicroLogix/PLC-5. Would close UDT-equivalent (integer-file + indirection), timer/counter decomposition, and real ladder execution + gaps. Costs: RSLinx OEM license, Windows-only, Hyper-V conflict + matching TwinCAT XAR + Logix Emulate, no clean PR-diffable project + format (SLC/ML save as binary `.RSS`). Scaffold like the Logix + Emulate tier when operationally worth it. +3. **Lab rig** — used SLC 5/05 or MicroLogix 1100 on a dedicated + network; parts are end-of-life but still available. PLC-5 + + LogixPccc-mode behaviour + DF1 serial need specific controllers. ## Key fixture / config files +- `tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/AbLegacyServerFixture.cs` + — TCP probe + skip attributes + env-var parsing +- `tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/AbLegacyReadSmokeTests.cs` + — three wire-level smoke tests (currently blocked by ab_server PCCC gap) +- `tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/Docker/docker-compose.yml` + — compose profiles reusing AB CIP Dockerfile +- `tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/Docker/README.md` + — known-limitations write-up + resolution paths - `tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/FakeAbLegacyTag.cs` — in-process fake + factory - `src/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriver.cs` — scope remarks diff --git a/docs/drivers/README.md b/docs/drivers/README.md index 4b48c19..7e3c358 100644 --- a/docs/drivers/README.md +++ b/docs/drivers/README.md @@ -44,7 +44,7 @@ Each driver has a dedicated fixture doc that lays out what the integration / uni - [AB CIP](AbServer-Test-Fixture.md) — Dockerized `ab_server` (multi-stage build from libplctag source); atomic-read smoke across 4 families; UDT / ALMD / family quirks unit-only - [Modbus](Modbus-Test-Fixture.md) — Dockerized `pymodbus` + per-family JSON profiles (4 compose profiles); best-covered driver, gaps are error-path-shaped - [Siemens S7](S7-Test-Fixture.md) — Dockerized `python-snap7` server; DB/MB read + write round-trip verified end-to-end on `:1102` -- [AB Legacy](AbLegacy-Test-Fixture.md) — no integration fixture, unit-only via `FakeAbLegacyTag` (libplctag PCCC) +- [AB Legacy](AbLegacy-Test-Fixture.md) — Docker scaffold via `ab_server` PCCC mode (task #224); wire-level round-trip currently blocked by ab_server's PCCC coverage gap, docs call out RSEmulate 500 + lab-rig resolution paths - [TwinCAT](TwinCAT-Test-Fixture.md) — XAR-VM integration scaffolding (task #221); three smoke tests skip when VM unreachable. Unit via `FakeTwinCATClient` with native-notification harness - [FOCAS](FOCAS-Test-Fixture.md) — no integration fixture, unit-only via `FakeFocasClient`; Tier C out-of-process isolation scoped but not shipped - [OPC UA Client](OpcUaClient-Test-Fixture.md) — no integration fixture, unit-only via mocked `Session`; loopback against this repo's own server is the obvious next step diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/AbLegacyReadSmokeTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/AbLegacyReadSmokeTests.cs new file mode 100644 index 0000000..7412902 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/AbLegacyReadSmokeTests.cs @@ -0,0 +1,93 @@ +using Shouldly; +using Xunit; +using ZB.MOM.WW.OtOpcUa.Core.Abstractions; +using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.PlcFamilies; + +namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests; + +/// +/// End-to-end smoke tests against the ab_server PCCC Docker container. +/// Promotes the AB Legacy driver from unit-only coverage (FakeAbLegacyTag) +/// to wire-level: real libplctag PCCC stack over real TCP against the ab_server +/// simulator. Parametrised over all three families (SLC 500 / MicroLogix / PLC-5) +/// via [AbLegacyTheory] + [MemberData]. +/// +[Collection(AbLegacyServerCollection.Name)] +[Trait("Category", "Integration")] +[Trait("Simulator", "ab_server-PCCC")] +public sealed class AbLegacyReadSmokeTests(AbLegacyServerFixture sim) +{ + public static IEnumerable Profiles => + KnownProfiles.All.Select(p => new object[] { p }); + + [AbLegacyTheory] + [MemberData(nameof(Profiles))] + public async Task Driver_reads_seeded_N_file_from_ab_server_PCCC(AbLegacyServerProfile profile) + { + if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason); + + // PCCC PLCs use empty cip-path, but AbLegacyHostAddress still requires the + // /cip-path suffix to parse. + var deviceUri = $"ab://{sim.Host}:{sim.Port}/"; + await using var drv = new AbLegacyDriver(new AbLegacyDriverOptions + { + Devices = [new AbLegacyDeviceOptions(deviceUri, profile.Family)], + Tags = [ + new AbLegacyTagDefinition( + Name: "IntCounter", + DeviceHostAddress: deviceUri, + Address: "N7:0", + DataType: AbLegacyDataType.Int), + ], + Timeout = TimeSpan.FromSeconds(5), + Probe = new AbLegacyProbeOptions { Enabled = false }, + }, driverInstanceId: $"ablegacy-smoke-{profile.Family}"); + + await drv.InitializeAsync("{}", TestContext.Current.CancellationToken); + + var snapshots = await drv.ReadAsync( + ["IntCounter"], TestContext.Current.CancellationToken); + + snapshots.Single().StatusCode.ShouldBe(AbLegacyStatusMapper.Good, + $"N7:0 read must succeed against the {profile.Family} compose profile"); + drv.GetHealth().State.ShouldBe(DriverState.Healthy); + } + + [AbLegacyFact] + public async Task Slc500_write_then_read_round_trip_on_N7_scratch_register() + { + if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason); + + // PCCC PLCs use empty cip-path, but AbLegacyHostAddress still requires the + // /cip-path suffix to parse. + var deviceUri = $"ab://{sim.Host}:{sim.Port}/"; + await using var drv = new AbLegacyDriver(new AbLegacyDriverOptions + { + Devices = [new AbLegacyDeviceOptions(deviceUri, AbLegacyPlcFamily.Slc500)], + Tags = [ + new AbLegacyTagDefinition( + Name: "Scratch", + DeviceHostAddress: deviceUri, + Address: "N7:5", + DataType: AbLegacyDataType.Int, + Writable: true), + ], + Timeout = TimeSpan.FromSeconds(5), + Probe = new AbLegacyProbeOptions { Enabled = false }, + }, driverInstanceId: "ablegacy-smoke-rw"); + + await drv.InitializeAsync("{}", TestContext.Current.CancellationToken); + + const short probe = 0x1234; + var writeResults = await drv.WriteAsync( + [new WriteRequest("Scratch", probe)], + TestContext.Current.CancellationToken); + writeResults.Single().StatusCode.ShouldBe(AbLegacyStatusMapper.Good, + "PCCC N7:5 write must succeed end-to-end"); + + var readResults = await drv.ReadAsync( + ["Scratch"], TestContext.Current.CancellationToken); + readResults.Single().StatusCode.ShouldBe(AbLegacyStatusMapper.Good); + Convert.ToInt32(readResults.Single().Value).ShouldBe(probe); + } +} diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/AbLegacyServerFixture.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/AbLegacyServerFixture.cs new file mode 100644 index 0000000..b997b2f --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/AbLegacyServerFixture.cs @@ -0,0 +1,157 @@ +using System.Net.Sockets; +using Xunit; +using Xunit.Sdk; +using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.PlcFamilies; + +namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests; + +/// +/// Reachability probe for the ab_server Docker container running in a PCCC +/// plc mode (SLC500 / Micrologix / PLC/5). Same container image +/// the AB CIP integration suite uses — libplctag's ab_server supports both +/// CIP + PCCC families from one binary. Tests skip via +/// / when +/// the port isn't live, so dotnet test stays green on a fresh clone without +/// Docker running. +/// +/// +/// Env-var overrides: +/// +/// AB_LEGACY_ENDPOINThost:port of the PCCC-mode simulator. +/// Defaults to localhost:44818 (EtherNet/IP port; ab_server's PCCC +/// emulation exposes PCCC-over-CIP on the same port as CIP itself). +/// +/// Distinct from AB_SERVER_ENDPOINT used by the AB CIP fixture so both +/// can point at different containers simultaneously during a combined test run. +/// +public sealed class AbLegacyServerFixture : IAsyncLifetime +{ + private const string EndpointEnvVar = "AB_LEGACY_ENDPOINT"; + + /// Standard EtherNet/IP port. PCCC-over-CIP rides on the same port as + /// native CIP; the differentiator is the --plc flag ab_server was started + /// with, not a different TCP listener. + public const int DefaultPort = 44818; + + public string Host { get; } = "127.0.0.1"; + public int Port { get; } = DefaultPort; + public string? SkipReason { get; } + + public AbLegacyServerFixture() + { + if (Environment.GetEnvironmentVariable(EndpointEnvVar) is { Length: > 0 } raw) + { + var parts = raw.Split(':', 2); + Host = parts[0]; + if (parts.Length == 2 && int.TryParse(parts[1], out var p)) Port = p; + } + + if (!TcpProbe(Host, Port)) + { + SkipReason = + $"AB Legacy PCCC simulator at {Host}:{Port} not reachable within 2 s. " + + $"Start the Docker container (docker compose -f Docker/docker-compose.yml " + + $"--profile slc500 up -d) or override {EndpointEnvVar}."; + } + } + + public ValueTask InitializeAsync() => ValueTask.CompletedTask; + public ValueTask DisposeAsync() => ValueTask.CompletedTask; + + public static bool IsServerAvailable() + { + var (host, port) = ResolveEndpoint(); + return TcpProbe(host, port); + } + + private static (string Host, int Port) ResolveEndpoint() + { + var raw = Environment.GetEnvironmentVariable(EndpointEnvVar); + if (raw is null) return ("127.0.0.1", DefaultPort); + var parts = raw.Split(':', 2); + var port = parts.Length == 2 && int.TryParse(parts[1], out var p) ? p : DefaultPort; + return (parts[0], port); + } + + private static bool TcpProbe(string host, int port) + { + try + { + using var client = new TcpClient(); + var task = client.ConnectAsync(host, port); + return task.Wait(TimeSpan.FromSeconds(2)) && client.Connected; + } + catch { return false; } + } +} + +/// +/// Per-family marker for the PCCC-mode compose profile a given test targets. The +/// compose file (Docker/docker-compose.yml) is the canonical source of truth +/// for which --plc mode + tags each family seeds; this record just ties a +/// family enum to its compose-profile name + operator-facing notes. +/// +public sealed record AbLegacyServerProfile( + AbLegacyPlcFamily Family, + string ComposeProfile, + string Notes); + +/// Canonical profiles covering every PCCC family the driver supports. +public static class KnownProfiles +{ + public static readonly AbLegacyServerProfile Slc500 = new( + Family: AbLegacyPlcFamily.Slc500, + ComposeProfile: "slc500", + Notes: "SLC 500 / 5/05 family. ab_server SLC500 mode covers N/F/B/L files."); + + public static readonly AbLegacyServerProfile MicroLogix = new( + Family: AbLegacyPlcFamily.MicroLogix, + ComposeProfile: "micrologix", + Notes: "MicroLogix 1000 / 1100 / 1400. Shares N/F/B file-type coverage with SLC500; ST (ASCII strings) included."); + + public static readonly AbLegacyServerProfile Plc5 = new( + Family: AbLegacyPlcFamily.Plc5, + ComposeProfile: "plc5", + Notes: "PLC-5 family. ab_server PLC/5 mode covers N/F/B; per-family quirks on ST / timer file layouts unit-tested only."); + + public static IReadOnlyList All { get; } = + [Slc500, MicroLogix, Plc5]; + + public static AbLegacyServerProfile ForFamily(AbLegacyPlcFamily family) => + All.FirstOrDefault(p => p.Family == family) + ?? throw new ArgumentOutOfRangeException(nameof(family), family, "No integration profile for this family."); +} + +[Xunit.CollectionDefinition(Name)] +public sealed class AbLegacyServerCollection : Xunit.ICollectionFixture +{ + public const string Name = "AbLegacyServer"; +} + +/// +/// [Fact]-equivalent that skips when the PCCC simulator isn't reachable. +/// +public sealed class AbLegacyFactAttribute : FactAttribute +{ + public AbLegacyFactAttribute() + { + if (!AbLegacyServerFixture.IsServerAvailable()) + Skip = "AB Legacy PCCC simulator not reachable. Start the Docker container " + + "(docker compose -f Docker/docker-compose.yml --profile slc500 up -d) " + + "or set AB_LEGACY_ENDPOINT."; + } +} + +/// +/// [Theory]-equivalent with the same gate as . +/// +public sealed class AbLegacyTheoryAttribute : TheoryAttribute +{ + public AbLegacyTheoryAttribute() + { + if (!AbLegacyServerFixture.IsServerAvailable()) + Skip = "AB Legacy PCCC simulator not reachable. Start the Docker container " + + "(docker compose -f Docker/docker-compose.yml --profile slc500 up -d) " + + "or set AB_LEGACY_ENDPOINT."; + } +} diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/Docker/README.md b/tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/Docker/README.md new file mode 100644 index 0000000..23cea31 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/Docker/README.md @@ -0,0 +1,140 @@ +# AB Legacy PCCC integration-test fixture — `ab_server` (Docker) + +[libplctag](https://github.com/libplctag/libplctag)'s `ab_server` supports +both CIP (ControlLogix / CompactLogix / Micro800) and PCCC (SLC 500 / +MicroLogix / PLC-5) families from one binary. This fixture reuses the AB +CIP Docker image (`otopcua-ab-server:libplctag-release`) with different +`--plc` flags. No new Dockerfile needed — the compose file's `build:` +block points at the AB CIP `Docker/` folder so `docker compose build` +from here reuses the same multi-stage build. + +**Docker is the only supported launch path**; a fresh clone needs Docker +Desktop and nothing else. + +| File | Purpose | +|---|---| +| [`docker-compose.yml`](docker-compose.yml) | Three per-family services (`slc500` / `micrologix` / `plc5`); all bind `:44818` | + +## Run + +From the repo root: + +```powershell +# SLC 500 family — widest PCCC coverage +docker compose -f tests\ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests\Docker\docker-compose.yml --profile slc500 up + +# Per-family +docker compose -f tests\...\Docker\docker-compose.yml --profile micrologix up +docker compose -f tests\...\Docker\docker-compose.yml --profile plc5 up +``` + +Detached + stop: + +```powershell +docker compose -f tests\...\Docker\docker-compose.yml --profile slc500 up -d +docker compose -f tests\...\Docker\docker-compose.yml --profile slc500 down +``` + +First run builds the `otopcua-ab-server:libplctag-release` image (~3-5 +min — clones libplctag + compiles `ab_server`). If the AB CIP fixture +already built the image locally, docker reuses the cached layers + this +runs in seconds. Only one family binds `:44818` at a time; to switch +families stop the current service + start another. + +## Endpoint + +- Default: `localhost:44818` (EtherNet/IP standard) +- Override with `AB_LEGACY_ENDPOINT=host:port` to point at a real SLC / + MicroLogix / PLC-5 PLC on its native port. + +## Run the integration tests + +In a separate shell with a container up: + +```powershell +cd C:\Users\dohertj2\Desktop\lmxopcua +dotnet test tests\ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests +``` + +`AbLegacyServerFixture` TCP-probes `localhost:44818` at collection init + +records a skip reason when unreachable. Tests use `[AbLegacyFact]` / +`[AbLegacyTheory]` which check the same probe. + +## What each family seeds + +PCCC tag format is `[]` without a type suffix — file letter +implies type: + +- `N` = 16-bit signed integer +- `F` = 32-bit IEEE 754 float +- `B` = 1-bit boolean (stored as uint16, bit-addressable via `/n`) +- `L` = 32-bit signed integer (SLC 5/05 V15+ only) +- `ST` = 82-byte ASCII string (MicroLogix-specific extension) + +| Family | Seeded tags | Notes | +|---|---|---| +| SLC 500 | `N7[10]`, `F8[10]`, `B3[10]`, `L19[10]` | Baseline; covers the four numeric file types a typical SLC project uses | +| MicroLogix | `B3[10]`, `N7[10]`, `L19[10]` | No `F8` — MicroLogix 1000 has no float file; use L19 when scaled integers aren't enough | +| PLC-5 | `N7[10]`, `F8[10]`, `B3[10]` | No `L` — PLC-5 predates the L file type; DINT equivalents went in integer files | + +## Known limitations + +### ab_server PCCC read/write round-trip (verified 2026-04-20) + +**Scaffold is in place; wire-level round-trip does NOT currently pass +against `ab_server --plc=SLC500`.** With the SLC500 compose profile up, +TCP 44818 accepts connections and libplctag negotiates the session, +but the three smoke tests in `AbLegacyReadSmokeTests.cs` all fail at +read/write with `BadCommunicationError` (libplctag status `0x80050000`). +Possible root causes: + +- ab_server's PCCC server-side opcode coverage may be narrower than + libplctag's PCCC client expects — the tool is primarily a CIP + server; PCCC was added later + is noted in libplctag docs as less + mature. +- libplctag's PCCC-over-CIP encapsulation may assume a real SLC 5/05 + EtherNet/IP NIC's framing that ab_server doesn't emit. + +The scaffold ships **as-is** because: + +1. The Docker infrastructure + fixture pattern works cleanly (probe + passes, container lifecycle is clean, tests skip when absent). +2. The test classes target the correct shape for what the AB Legacy + driver would do against real hardware. +3. Pointing `AB_LEGACY_ENDPOINT` at a real SLC 5/05 / MicroLogix + 1100 / 1400 should make the tests pass outright — the failure + mode is ab_server-specific, not driver-specific. + +Resolution paths (pick one): + +1. **File an ab_server bug** in `libplctag/libplctag` to expand PCCC + server-side coverage. +2. **Golden-box tier** via Rockwell RSEmulate 500 — closer to real + firmware, but license-gated + RSLinx-dependent. +3. **Lab rig** — used SLC 5/05 / MicroLogix 1100 on a dedicated + network; the authoritative path. + +### Other known gaps (unchanged from ab_server) + +- **Timer / Counter file decomposition** — PCCC T4 / C5 files contain + three-field structs (`.ACC` / `.PRE` / `.DN`). Not in ab_server's + scope; tests targeting `T4:0.ACC` stay unit-only. +- **ST (ASCII string) files** — real MicroLogix ST files have a length + field plus CRLF-sensitive semantics that don't round-trip cleanly. +- **Indirect addressing** (`N7:[N10:5]`) — not in ab_server's scope. +- **DF1 serial wire behaviour** — the whole ab_server path is TCP; + DF1 radio / serial fidelity needs real hardware. + +See [`docs/drivers/AbLegacy-Test-Fixture.md`](../../../docs/drivers/AbLegacy-Test-Fixture.md) +for the full coverage map. + +## References + +- [libplctag on GitHub](https://github.com/libplctag/libplctag) — `ab_server` + lives under `src/tools/ab_server/` +- [`docs/drivers/AbLegacy-Test-Fixture.md`](../../../docs/drivers/AbLegacy-Test-Fixture.md) + — coverage map + gap inventory +- [`docs/v2/dev-environment.md`](../../../docs/v2/dev-environment.md) + §Docker fixtures — full fixture inventory +- [`../../ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/Docker/`](../../ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/Docker/) + — the shared Dockerfile this compose file's `build:` block references diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/Docker/docker-compose.yml b/tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/Docker/docker-compose.yml new file mode 100644 index 0000000..f0d3432 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/Docker/docker-compose.yml @@ -0,0 +1,74 @@ +# AB Legacy PCCC integration-test fixture — ab_server in PCCC mode. +# +# Same image as the AB CIP fixture (otopcua-ab-server:libplctag-release). +# The build context points at the AB CIP Docker folder one directory over +# so `docker compose build` from here produces the same image if it +# doesn't already exist; if it does, docker's cache reuses the layer. +# +# One service per PCCC family. All bind :44818 on the host; run one at a +# time. PCCC tag format differs from CIP: `[]` without a +# type suffix since the type is implicit in the file letter (N = INT, +# F = REAL, B = bit-packed, L = DINT). +# +# Usage: +# docker compose --profile slc500 up +# docker compose --profile micrologix up +# docker compose --profile plc5 up +services: + slc500: + profiles: ["slc500"] + build: + context: ../../ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/Docker + dockerfile: Dockerfile + image: otopcua-ab-server:libplctag-release + container_name: otopcua-ab-server-slc500 + restart: "no" + ports: + - "44818:44818" + command: [ + "ab_server", + "--plc=SLC500", + "--port=44818", + "--tag=N7[10]", + "--tag=F8[10]", + "--tag=B3[10]", + "--tag=L19[10]" + ] + + micrologix: + profiles: ["micrologix"] + image: otopcua-ab-server:libplctag-release + build: + context: ../../ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/Docker + dockerfile: Dockerfile + container_name: otopcua-ab-server-micrologix + restart: "no" + ports: + - "44818:44818" + command: [ + "ab_server", + "--plc=Micrologix", + "--port=44818", + "--tag=B3[10]", + "--tag=N7[10]", + "--tag=L19[10]" + ] + + plc5: + profiles: ["plc5"] + image: otopcua-ab-server:libplctag-release + build: + context: ../../ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/Docker + dockerfile: Dockerfile + container_name: otopcua-ab-server-plc5 + restart: "no" + ports: + - "44818:44818" + command: [ + "ab_server", + "--plc=PLC/5", + "--port=44818", + "--tag=N7[10]", + "--tag=F8[10]", + "--tag=B3[10]" + ] diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests.csproj b/tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests.csproj new file mode 100644 index 0000000..37709e1 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests.csproj @@ -0,0 +1,35 @@ + + + + net10.0 + enable + enable + false + true + ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + +