Merge pull request 'AB Legacy ab_server PCCC Docker fixture scaffold (#224)' (#167) from ablegacy-docker-fixture into v2

This commit was merged in pull request #167.
This commit is contained in:
2026-04-20 13:28:16 -04:00
8 changed files with 549 additions and 21 deletions

View File

@@ -37,6 +37,7 @@
<Project Path="tests/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests.csproj"/>
<Project Path="tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests.csproj"/>
<Project Path="tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests.csproj"/>
<Project Path="tests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests.csproj"/>
<Project Path="tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests.csproj"/>
<Project Path="tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests.csproj"/>
<Project Path="tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests.csproj"/>

View File

@@ -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

View File

@@ -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

View File

@@ -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;
/// <summary>
/// End-to-end smoke tests against the <c>ab_server</c> PCCC Docker container.
/// Promotes the AB Legacy driver from unit-only coverage (<c>FakeAbLegacyTag</c>)
/// 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 <c>[AbLegacyTheory]</c> + <c>[MemberData]</c>.
/// </summary>
[Collection(AbLegacyServerCollection.Name)]
[Trait("Category", "Integration")]
[Trait("Simulator", "ab_server-PCCC")]
public sealed class AbLegacyReadSmokeTests(AbLegacyServerFixture sim)
{
public static IEnumerable<object[]> 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);
}
}

View File

@@ -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;
/// <summary>
/// Reachability probe for the <c>ab_server</c> Docker container running in a PCCC
/// plc mode (<c>SLC500</c> / <c>Micrologix</c> / <c>PLC/5</c>). Same container image
/// the AB CIP integration suite uses — libplctag's <c>ab_server</c> supports both
/// CIP + PCCC families from one binary. Tests skip via
/// <see cref="AbLegacyFactAttribute"/> / <see cref="AbLegacyTheoryAttribute"/> when
/// the port isn't live, so <c>dotnet test</c> stays green on a fresh clone without
/// Docker running.
/// </summary>
/// <remarks>
/// Env-var overrides:
/// <list type="bullet">
/// <item><c>AB_LEGACY_ENDPOINT</c> — <c>host:port</c> of the PCCC-mode simulator.
/// Defaults to <c>localhost:44818</c> (EtherNet/IP port; ab_server's PCCC
/// emulation exposes PCCC-over-CIP on the same port as CIP itself).</item>
/// </list>
/// Distinct from <c>AB_SERVER_ENDPOINT</c> used by the AB CIP fixture so both
/// can point at different containers simultaneously during a combined test run.
/// </remarks>
public sealed class AbLegacyServerFixture : IAsyncLifetime
{
private const string EndpointEnvVar = "AB_LEGACY_ENDPOINT";
/// <summary>Standard EtherNet/IP port. PCCC-over-CIP rides on the same port as
/// native CIP; the differentiator is the <c>--plc</c> flag ab_server was started
/// with, not a different TCP listener.</summary>
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; }
}
}
/// <summary>
/// Per-family marker for the PCCC-mode compose profile a given test targets. The
/// compose file (<c>Docker/docker-compose.yml</c>) is the canonical source of truth
/// for which <c>--plc</c> mode + tags each family seeds; this record just ties a
/// family enum to its compose-profile name + operator-facing notes.
/// </summary>
public sealed record AbLegacyServerProfile(
AbLegacyPlcFamily Family,
string ComposeProfile,
string Notes);
/// <summary>Canonical profiles covering every PCCC family the driver supports.</summary>
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<AbLegacyServerProfile> 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<AbLegacyServerFixture>
{
public const string Name = "AbLegacyServer";
}
/// <summary>
/// <c>[Fact]</c>-equivalent that skips when the PCCC simulator isn't reachable.
/// </summary>
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.";
}
}
/// <summary>
/// <c>[Theory]</c>-equivalent with the same gate as <see cref="AbLegacyFactAttribute"/>.
/// </summary>
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.";
}
}

View File

@@ -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 `<file>[<size>]` 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

View File

@@ -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: `<file>[<size>]` 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]"
]

View File

@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<RootNamespace>ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit.v3" Version="1.1.0"/>
<PackageReference Include="Shouldly" Version="4.3.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0"/>
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ZB.MOM.WW.OtOpcUa.Driver.AbLegacy\ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.csproj"/>
</ItemGroup>
<ItemGroup>
<None Update="Docker\**\*" CopyToOutputDirectory="PreserveNewest"/>
</ItemGroup>
<ItemGroup>
<NuGetAuditSuppress Include="https://github.com/advisories/GHSA-37gx-xxp4-5rgx"/>
<NuGetAuditSuppress Include="https://github.com/advisories/GHSA-w3x6-4m5h-cxqf"/>
</ItemGroup>
</Project>