Doc + code-comment sweep � finish the native-fallback removal #164
@@ -113,5 +113,5 @@ Not a Modbus concept. Driver doesn't implement `IAlarmSource` or
|
|||||||
- `tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/DL205/DL205Profile.cs`
|
- `tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/DL205/DL205Profile.cs`
|
||||||
- `tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Mitsubishi/MitsubishiProfile.cs`
|
- `tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Mitsubishi/MitsubishiProfile.cs`
|
||||||
- `tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/S7/S7_1500Profile.cs`
|
- `tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/S7/S7_1500Profile.cs`
|
||||||
- `tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Pymodbus/` — simulator
|
- `tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Docker/` —
|
||||||
driver script + per-family JSON profiles
|
Dockerfile + compose + per-family JSON profiles
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ libplctag + compiles C). Stop with `docker compose -f <compose> --profile <…>
|
|||||||
real PLC instead of the simulator:
|
real PLC instead of the simulator:
|
||||||
|
|
||||||
- `MODBUS_SIM_ENDPOINT` (default `localhost:5020`)
|
- `MODBUS_SIM_ENDPOINT` (default `localhost:5020`)
|
||||||
- `AB_SERVER_ENDPOINT` (no default; when set, skips both Docker + native-binary paths)
|
- `AB_SERVER_ENDPOINT` (no default; overrides the local container endpoint)
|
||||||
- `S7_SIM_ENDPOINT` (default `localhost:1102`)
|
- `S7_SIM_ENDPOINT` (default `localhost:1102`)
|
||||||
- `OPCUA_SIM_ENDPOINT` (default `opc.tcp://localhost:50000`)
|
- `OPCUA_SIM_ENDPOINT` (default `opc.tcp://localhost:50000`)
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ confirmed DL205 quirk lands in a follow-up PR as a named test in that project.
|
|||||||
|
|
||||||
## Harness
|
## Harness
|
||||||
|
|
||||||
**Chosen simulator: pymodbus 3.13.0** (`pip install 'pymodbus[simulator]==3.13.0'`).
|
**Chosen simulator: pymodbus 3.13.0** packaged as a pinned Docker image
|
||||||
Replaced ModbusPal in PR 43 — see `tests/.../Pymodbus/README.md` for the
|
under `tests/.../Modbus.IntegrationTests/Docker/`. See that folder's
|
||||||
trade-off rationale. Headline reasons:
|
`README.md` for image-build notes + compose profiles. Headline reasons:
|
||||||
|
|
||||||
- **Headless** pure-Python CLI; no Java GUI, runs cleanly on a CI runner.
|
- **Headless** pure-Python CLI; no Java GUI, runs cleanly on a CI runner.
|
||||||
- **Maintained** — current stable 3.13.0; ModbusPal 1.6b is abandoned.
|
- **Maintained** — current stable 3.13.0; ModbusPal 1.6b is abandoned.
|
||||||
@@ -26,17 +26,18 @@ trade-off rationale. Headline reasons:
|
|||||||
- **Per-register raw uint16 seeding** — encoding the DL205 string-byte-order
|
- **Per-register raw uint16 seeding** — encoding the DL205 string-byte-order
|
||||||
/ BCD / CDAB-float quirks stays explicit (the quirk math lives in the
|
/ BCD / CDAB-float quirks stays explicit (the quirk math lives in the
|
||||||
`_quirk` JSON-comment fields next to each register).
|
`_quirk` JSON-comment fields next to each register).
|
||||||
- Pip-installable on Windows; sidesteps the privileged-port admin
|
- **Dockerized** — pinned image means the CI simulator surface is
|
||||||
requirement by defaulting to TCP **5020** instead of 502.
|
reproducible + no `pip install` step on the dev box.
|
||||||
|
- Defaults to TCP **5020** (matches the compose port-map + the fixture
|
||||||
|
default endpoint; sidesteps the Windows Firewall prompt on 502).
|
||||||
|
|
||||||
**Setup pattern**:
|
**Setup pattern**:
|
||||||
1. `pip install "pymodbus[simulator]==3.13.0"`.
|
1. `docker compose -f tests\...\Modbus.IntegrationTests\Docker\docker-compose.yml --profile <standard|dl205|mitsubishi|s7_1500> up -d`.
|
||||||
2. Start the simulator with one of the in-repo profiles:
|
2. `dotnet test tests\ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests` —
|
||||||
`tests\.../Pymodbus\serve.ps1 -Profile standard` (or `-Profile dl205`).
|
|
||||||
3. `dotnet test tests\ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests` —
|
|
||||||
tests auto-skip when the endpoint is unreachable. Default endpoint is
|
tests auto-skip when the endpoint is unreachable. Default endpoint is
|
||||||
`localhost:5020`; override via `MODBUS_SIM_ENDPOINT` for a real PLC on its
|
`localhost:5020`; override via `MODBUS_SIM_ENDPOINT` for a real PLC on its
|
||||||
native port 502.
|
native port 502.
|
||||||
|
3. `docker compose -f ... --profile <…> down` when finished.
|
||||||
|
|
||||||
## Per-device quirk catalog
|
## Per-device quirk catalog
|
||||||
|
|
||||||
@@ -113,9 +114,11 @@ vendors get promoted into driver defaults or opt-in options:
|
|||||||
- **PR 42 — ModbusPal `.xmpp` profiles** — **SUPERSEDED by PR 43**. Replaced
|
- **PR 42 — ModbusPal `.xmpp` profiles** — **SUPERSEDED by PR 43**. Replaced
|
||||||
with pymodbus JSON because ModbusPal 1.6b is abandoned, GUI-only, and only
|
with pymodbus JSON because ModbusPal 1.6b is abandoned, GUI-only, and only
|
||||||
exposes 2 of the 4 standard tables.
|
exposes 2 of the 4 standard tables.
|
||||||
- **PR 43 — pymodbus JSON profiles** — **DONE**. `Pymodbus/standard.json` +
|
- **PR 43 — pymodbus JSON profiles** — **DONE**. Dockerized under
|
||||||
`Pymodbus/dl205.json` + `Pymodbus/serve.ps1` runner. Both bind TCP 5020.
|
`Docker/profiles/` (standard.json, dl205.json, mitsubishi.json,
|
||||||
|
s7_1500.json); compose file launches each via a named profile.
|
||||||
|
All bind TCP 5020.
|
||||||
- **PR 44+**: one PR per confirmed DL205 quirk, landing the named test + any
|
- **PR 44+**: one PR per confirmed DL205 quirk, landing the named test + any
|
||||||
driver-side adjustment (string byte order, BCD decoder, V-memory address
|
driver-side adjustment (string byte order, BCD decoder, V-memory address
|
||||||
helper, FC16 cap-per-device-family) needed to pass it. Each quirk's value
|
helper, FC16 cap-per-device-family) needed to pass it. Each quirk's value
|
||||||
is already pre-encoded in `Pymodbus/dl205.json`.
|
is already pre-encoded in `Docker/profiles/dl205.json`.
|
||||||
|
|||||||
@@ -191,40 +191,30 @@ Modbus has no native String, DateTime, or Int64 — those rows are skipped on th
|
|||||||
|
|
||||||
### CI fixture (task #180)
|
### CI fixture (task #180)
|
||||||
|
|
||||||
The integration harness at `tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/` exposes two test-time contracts:
|
The integration harness at `tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/` is Docker-only — `ab_server` is a source-only tool under libplctag's `src/tools/ab_server/`, and the fixture's multi-stage `Docker/Dockerfile` is the only supported reproducible build path.
|
||||||
|
|
||||||
- **`AbServerFixture(AbServerProfile)`** — starts the simulator with the CLI args composed from the profile's `--plc` family + seed-tag set. One fixture instance per family, one simulator process per test case (smoke tier). For larger suites that can share a simulator across several reads/writes, use a `IClassFixture<AbServerFixture>` wrapper per family.
|
- **`AbServerFixture(AbServerProfile)`** — thin TCP probe against `127.0.0.1:44818` (or `AB_SERVER_ENDPOINT` override). Does not spawn the simulator; the operator brings up the compose service for whichever family the test class targets (`controllogix` / `compactlogix` / `micro800` / `guardlogix`).
|
||||||
- **`KnownProfiles.{ControlLogix, CompactLogix, Micro800, GuardLogix}`** — the four per-family profiles. Drives the simulator's `--plc` mode + the preseed `--tag name:type[:size]` set. Micro800 + GuardLogix fall back to `controllogix` under the hood because ab_server has no dedicated mode for them — the driver-side family profile still enforces the narrower connection shape / safety classification separately.
|
- **`KnownProfiles.{ControlLogix, CompactLogix, Micro800, GuardLogix}`** — thin `(Family, ComposeProfile, Notes)` records. The compose file (`Docker/docker-compose.yml`) is the canonical source of truth for which tags each family seeds + which `--plc` mode the simulator boots in. `Micro800` uses the dedicated `--plc=Micro800` mode; `GuardLogix` uses `ControlLogix` emulation because ab_server has no safety subsystem (the `_S`-suffixed seed tag triggers driver-side ViewOnly classification only).
|
||||||
|
|
||||||
**Pinned version** (recorded in `ci/ab-server.lock.json` so drift is one-file visible):
|
**Pinned version**: the `Docker/Dockerfile` clones libplctag at a pinned tag (currently the `release` branch) via its `LIBPLCTAG_TAG` build-arg and compiles `ab_server` from source. Bump deliberately alongside a driver-side change that needs the newer simulator.
|
||||||
|
|
||||||
- `libplctag` **v2.6.16** (published 2026-03-29) — `ab_server.exe` ships inside the `_tools.zip` asset alongside `plctag.dll` + two `list_tags_*` helpers.
|
|
||||||
- Windows x64: `libplctag_2.6.16_windows_x64_tools.zip` — SHA256 `9b78a3dee73d9cd28ca348c090f453dbe3ad9d07ad6bf42865a9dc3a79bc2232`
|
|
||||||
- Windows x86: `libplctag_2.6.16_windows_x86_tools.zip` — SHA256 `fdfefd58b266c5da9a1ded1a430985e609289c9e67be2544da7513b668761edf`
|
|
||||||
- Windows ARM64: `libplctag_2.6.16_windows_arm64_tools.zip` — SHA256 `d747728e4c4958bb63b4ac23e1c820c4452e4778dfd7d58f8a0aecd5402d4944`
|
|
||||||
|
|
||||||
**CI step:**
|
**CI step:**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# GitHub Actions step placed before `dotnet test`:
|
# GitHub Actions step placed before `dotnet test`:
|
||||||
- name: Fetch ab_server (libplctag v2.6.16)
|
- name: Start ab_server Docker container
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: |
|
run: |
|
||||||
$pin = Get-Content ci/ab-server.lock.json | ConvertFrom-Json
|
docker compose -f tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/Docker/docker-compose.yml `
|
||||||
$asset = $pin.assets.'windows-x64' # swap to windows-x86 / windows-arm64 on non-x64 runners
|
--profile controllogix up -d --build
|
||||||
$url = "https://github.com/libplctag/libplctag/releases/download/$($pin.tag)/$($asset.file)"
|
# Wait for :44818 to accept connections (compose healthcheck-equivalent)
|
||||||
$zip = Join-Path $env:RUNNER_TEMP 'libplctag-tools.zip'
|
for ($i = 0; $i -lt 30; $i++) {
|
||||||
Invoke-WebRequest $url -OutFile $zip
|
if ((Test-NetConnection -ComputerName localhost -Port 44818 -WarningAction SilentlyContinue).TcpTestSucceeded) { break }
|
||||||
$actual = (Get-FileHash -Algorithm SHA256 $zip).Hash.ToLower()
|
Start-Sleep -Seconds 1
|
||||||
if ($actual -ne $asset.sha256) { throw "libplctag tools SHA256 mismatch: expected $($asset.sha256), got $actual" }
|
}
|
||||||
$dest = Join-Path $env:RUNNER_TEMP 'libplctag-tools'
|
|
||||||
Expand-Archive $zip -DestinationPath $dest
|
|
||||||
Add-Content $env:GITHUB_PATH $dest
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The fixture's `LocateBinary()` picks the binary up off PATH so the C# harness doesn't own the download — CI YAML is the right place for version pinning + hash verification. Developer workstations install the binary once from source (`cmake + make ab_server` under a libplctag clone) and the same fixture works identically.
|
Tests skip via `AbServerFactAttribute` / `AbServerTheoryAttribute` when the probe fails, so fresh-clone runs without Docker still pass all unit suites in this project.
|
||||||
|
|
||||||
Tests without ab_server on PATH are marked `Skip` via `AbServerFactAttribute` / `AbServerTheoryAttribute`, so fresh-clone runs without the simulator still pass all unit suites in this project.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests.DL205;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tag map for the AutomationDirect DL205 device class. Mirrors what the pymodbus
|
/// Tag map for the AutomationDirect DL205 device class. Mirrors what the pymodbus
|
||||||
/// <c>dl205.json</c> profile in <c>Pymodbus/dl205.json</c> exposes (or the real PLC, when
|
/// <c>dl205.json</c> profile in <c>Docker/profiles/dl205.json</c> exposes (or the real PLC, when
|
||||||
/// <see cref="ModbusSimulatorFixture"/> is pointed at one).
|
/// <see cref="ModbusSimulatorFixture"/> is pointed at one).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
@@ -16,8 +16,8 @@ public static class DL205Profile
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holding register the smoke test writes + reads. Address 200 is the first cell of the
|
/// Holding register the smoke test writes + reads. Address 200 is the first cell of the
|
||||||
/// scratch HR range in both <c>Pymodbus/standard.json</c> (HR[200..209] = 0) and
|
/// scratch HR range in both <c>Docker/profiles/standard.json</c> (HR[200..209] = 0) and
|
||||||
/// <c>Pymodbus/dl205.json</c> (HR[4096..4103] added in PR 43 for the same purpose), so
|
/// <c>Docker/profiles/dl205.json</c> (HR[4096..4103] added in PR 43 for the same purpose), so
|
||||||
/// the smoke test runs identically against either simulator profile. Originally
|
/// the smoke test runs identically against either simulator profile. Originally
|
||||||
/// targeted HR[100] — moved to HR[200] when the standard profile claimed HR[100] as
|
/// targeted HR[100] — moved to HR[200] when the standard profile claimed HR[100] as
|
||||||
/// the auto-incrementing register that drives subscribe-and-receive tests.
|
/// the auto-incrementing register that drives subscribe-and-receive tests.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests.DL205;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Requires the dl205 profile (<c>Pymodbus\serve.ps1 -Profile dl205</c>). The standard
|
/// Requires the dl205 profile (<c>docker compose -f Docker/docker-compose.yml --profile dl205 up</c>). The standard
|
||||||
/// profile does not seed HR[1040..1042] with string bytes, so running this against the
|
/// profile does not seed HR[1040..1042] with string bytes, so running this against the
|
||||||
/// standard profile returns <c>"\0\0\0\0\0"</c> and the test fails. Skip when the env
|
/// standard profile returns <c>"\0\0\0\0\0"</c> and the test fails. Skip when the env
|
||||||
/// var <c>MODBUS_SIM_PROFILE</c> is not set to <c>dl205</c>.
|
/// var <c>MODBUS_SIM_PROFILE</c> is not set to <c>dl205</c>.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# pymodbus simulator container for the Modbus integration suite.
|
# pymodbus simulator container for the Modbus integration suite.
|
||||||
#
|
#
|
||||||
# Pinned base + package version so the fixture surface is reproducible —
|
# Pinned base + package version so the fixture surface is reproducible —
|
||||||
# matches the version referenced in tests/.../Pymodbus/README.md.
|
# matches the version referenced in docs/drivers/Modbus-Test-Fixture.md.
|
||||||
FROM python:3.12-slim-bookworm
|
FROM python:3.12-slim-bookworm
|
||||||
|
|
||||||
LABEL org.opencontainers.image.source="https://github.com/dohertj2/lmxopcua" \
|
LABEL org.opencontainers.image.source="https://github.com/dohertj2/lmxopcua" \
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ using System.Net.Sockets;
|
|||||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests;
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reachability probe for a Modbus TCP simulator (pymodbus-driven, see
|
/// Reachability probe for a Modbus TCP simulator (pymodbus in Docker, see
|
||||||
/// <c>Pymodbus/serve.ps1</c>) or a real PLC. Parses
|
/// <c>Docker/docker-compose.yml</c>) or a real PLC. Parses
|
||||||
/// <c>MODBUS_SIM_ENDPOINT</c> (default <c>localhost:5020</c> per PR 43) and TCP-connects once at
|
/// <c>MODBUS_SIM_ENDPOINT</c> (default <c>localhost:5020</c> per PR 43) and TCP-connects once at
|
||||||
/// fixture construction. Each test checks <see cref="SkipReason"/> and calls
|
/// fixture construction. Each test checks <see cref="SkipReason"/> and calls
|
||||||
/// <c>Assert.Skip</c> when the endpoint was unreachable, so a dev box without a running
|
/// <c>Assert.Skip</c> when the endpoint was unreachable, so a dev box without a running
|
||||||
@@ -28,7 +28,7 @@ public sealed class ModbusSimulatorFixture : IAsyncDisposable
|
|||||||
{
|
{
|
||||||
// PR 43: default port is 5020 (pymodbus convention) instead of 502 (Modbus standard).
|
// PR 43: default port is 5020 (pymodbus convention) instead of 502 (Modbus standard).
|
||||||
// Picking 5020 sidesteps the privileged-port admin requirement on Windows + matches the
|
// Picking 5020 sidesteps the privileged-port admin requirement on Windows + matches the
|
||||||
// port baked into the pymodbus simulator JSON profiles in Pymodbus/. Override with
|
// port baked into the pymodbus simulator JSON profiles in Docker/profiles/. Override with
|
||||||
// MODBUS_SIM_ENDPOINT to point at a real PLC on its native port 502.
|
// MODBUS_SIM_ENDPOINT to point at a real PLC on its native port 502.
|
||||||
private const string DefaultEndpoint = "localhost:5020";
|
private const string DefaultEndpoint = "localhost:5020";
|
||||||
private const string EndpointEnvVar = "MODBUS_SIM_ENDPOINT";
|
private const string EndpointEnvVar = "MODBUS_SIM_ENDPOINT";
|
||||||
@@ -61,14 +61,14 @@ public sealed class ModbusSimulatorFixture : IAsyncDisposable
|
|||||||
if (!task.Wait(TimeSpan.FromSeconds(2)) || !client.Connected)
|
if (!task.Wait(TimeSpan.FromSeconds(2)) || !client.Connected)
|
||||||
{
|
{
|
||||||
SkipReason = $"Modbus simulator at {Host}:{Port} did not accept a TCP connection within 2s. " +
|
SkipReason = $"Modbus simulator at {Host}:{Port} did not accept a TCP connection within 2s. " +
|
||||||
$"Start the pymodbus simulator (Pymodbus\\serve.ps1 -Profile standard) " +
|
$"Start the pymodbus Docker container (docker compose -f Docker/docker-compose.yml --profile standard up -d) " +
|
||||||
$"or override {EndpointEnvVar}, then re-run.";
|
$"or override {EndpointEnvVar}, then re-run.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
SkipReason = $"Modbus simulator at {Host}:{Port} unreachable: {ex.GetType().Name}: {ex.Message}. " +
|
SkipReason = $"Modbus simulator at {Host}:{Port} unreachable: {ex.GetType().Name}: {ex.Message}. " +
|
||||||
$"Start the pymodbus simulator (Pymodbus\\serve.ps1 -Profile standard) " +
|
$"Start the pymodbus Docker container (docker compose -f Docker/docker-compose.yml --profile standard up -d) " +
|
||||||
$"or override {EndpointEnvVar}, then re-run.";
|
$"or override {EndpointEnvVar}, then re-run.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests.S7;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tag map for the Siemens SIMATIC S7-1500 device class with the <c>MB_SERVER</c> library
|
/// Tag map for the Siemens SIMATIC S7-1500 device class with the <c>MB_SERVER</c> library
|
||||||
/// block mapping HR[0..] to DB1.DBW0+. Mirrors <c>s7_1500.json</c> in <c>Pymodbus/</c>.
|
/// block mapping HR[0..] to DB1.DBW0+. Mirrors <c>s7_1500.json</c> in <c>Docker/profiles/</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Unlike DL205, S7 has no fixed Modbus memory map — every site wires MB_SERVER to a
|
/// Unlike DL205, S7 has no fixed Modbus memory map — every site wires MB_SERVER to a
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using S7NetCpuType = global::S7.Net.CpuType;
|
|||||||
namespace ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests.S7_1500;
|
namespace ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests.S7_1500;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Driver-side configuration matching what <c>PythonSnap7/s7_1500.json</c> seeds
|
/// Driver-side configuration matching what <c>Docker/profiles/s7_1500.json</c> seeds
|
||||||
/// into the simulator's DB1 + MB areas. Tag names here become the full references
|
/// into the simulator's DB1 + MB areas. Tag names here become the full references
|
||||||
/// the smoke tests read/write against; addresses map 1:1 to the JSON profile's
|
/// the smoke tests read/write against; addresses map 1:1 to the JSON profile's
|
||||||
/// seed offsets so a seed drift in the JSON surfaces as a driver-side read
|
/// seed offsets so a seed drift in the JSON surfaces as a driver-side read
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ using System.Net.Sockets;
|
|||||||
namespace ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests;
|
namespace ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reachability probe for a python-snap7 simulator (see
|
/// Reachability probe for the python-snap7 simulator Docker container (see
|
||||||
/// <c>PythonSnap7/serve.ps1</c>) or a real S7 PLC. Parses <c>S7_SIM_ENDPOINT</c>
|
/// <c>Docker/docker-compose.yml</c>) or a real S7 PLC. Parses <c>S7_SIM_ENDPOINT</c>
|
||||||
/// (default <c>localhost:1102</c>) + TCP-connects once at fixture construction.
|
/// (default <c>localhost:1102</c>) + TCP-connects once at fixture construction.
|
||||||
/// Tests check <see cref="SkipReason"/> + call <c>Assert.Skip</c> when unreachable, so
|
/// Tests check <see cref="SkipReason"/> + call <c>Assert.Skip</c> when unreachable, so
|
||||||
/// `dotnet test` stays green on a fresh box without the simulator installed —
|
/// `dotnet test` stays green on a fresh box without the simulator installed —
|
||||||
@@ -33,7 +33,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests;
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public sealed class Snap7ServerFixture : IAsyncDisposable
|
public sealed class Snap7ServerFixture : IAsyncDisposable
|
||||||
{
|
{
|
||||||
// Default 1102 (non-privileged) matches PythonSnap7/server.py. Override with
|
// Default 1102 (non-privileged) matches Docker/server.py. Override with
|
||||||
// S7_SIM_ENDPOINT to point at a real PLC on its native 102.
|
// S7_SIM_ENDPOINT to point at a real PLC on its native 102.
|
||||||
private const string DefaultEndpoint = "localhost:1102";
|
private const string DefaultEndpoint = "localhost:1102";
|
||||||
private const string EndpointEnvVar = "S7_SIM_ENDPOINT";
|
private const string EndpointEnvVar = "S7_SIM_ENDPOINT";
|
||||||
@@ -63,13 +63,13 @@ public sealed class Snap7ServerFixture : IAsyncDisposable
|
|||||||
if (!task.Wait(TimeSpan.FromSeconds(2)) || !client.Connected)
|
if (!task.Wait(TimeSpan.FromSeconds(2)) || !client.Connected)
|
||||||
{
|
{
|
||||||
SkipReason = $"python-snap7 simulator at {Host}:{Port} did not accept a TCP connection within 2s. " +
|
SkipReason = $"python-snap7 simulator at {Host}:{Port} did not accept a TCP connection within 2s. " +
|
||||||
$"Start it (PythonSnap7\\serve.ps1 -Profile s7_1500) or override {EndpointEnvVar}.";
|
$"Start it (docker compose -f Docker/docker-compose.yml --profile s7_1500 up -d) or override {EndpointEnvVar}.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
SkipReason = $"python-snap7 simulator at {Host}:{Port} unreachable: {ex.GetType().Name}: {ex.Message}. " +
|
SkipReason = $"python-snap7 simulator at {Host}:{Port} unreachable: {ex.GetType().Name}: {ex.Message}. " +
|
||||||
$"Start it (PythonSnap7\\serve.ps1 -Profile s7_1500) or override {EndpointEnvVar}.";
|
$"Start it (docker compose -f Docker/docker-compose.yml --profile s7_1500 up -d) or override {EndpointEnvVar}.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user