14ec145487
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
66 lines
3.5 KiB
C#
66 lines
3.5 KiB
C#
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>
|
|
/// Bit-within-word write (<c>B3:0/5</c>) against <c>ab_server</c> on both transports: only the addressed
|
|
/// bit may change, its neighbours must survive. Writes B3:0 (simulator-only, gated by
|
|
/// <see cref="IntegrationWriteGate"/>). PLC-5 on the native transport refuses writes (#571), which the test pins instead.
|
|
/// </summary>
|
|
[Collection(AbLegacyServerCollection.Name)]
|
|
[Trait("Category", "Integration")]
|
|
[Trait("Simulator", "ab_server-PCCC")]
|
|
public sealed class AbLegacyBitWriteTests(AbLegacyServerFixture sim)
|
|
{
|
|
/// <summary>Seeds B3:0 = 0x0A0A, sets bit 5, clears bit 3, and checks neighbours are untouched.</summary>
|
|
/// <param name="transport">The transport stack the driver runs on.</param>
|
|
[AbLegacyTheory]
|
|
[MemberData(nameof(TransportMatrix.Profiles), MemberType = typeof(TransportMatrix))]
|
|
public async Task Bit_write_changes_only_the_addressed_bit(AbLegacyServerProfile profile, AbLegacyTransport transport)
|
|
{
|
|
if (sim.SkipReasonFor(profile) is { } skip) Assert.Skip(skip);
|
|
IntegrationWriteGate.SkipUnlessWritesAllowed();
|
|
|
|
var deviceUri = $"ab://{sim.Host}:{sim.PortFor(profile)}/{sim.CipPath}";
|
|
await using var drv = new AbLegacyDriver(new AbLegacyDriverOptions
|
|
{
|
|
Devices = [new AbLegacyDeviceOptions(deviceUri, profile.Family)],
|
|
RawTags = AbLegacyRawTags.Entries(
|
|
new AbLegacyTagDefinition("Word", deviceUri, "B3:0", AbLegacyDataType.Int, Writable: true),
|
|
new AbLegacyTagDefinition("Bit5", deviceUri, "B3:0/5", AbLegacyDataType.Bit, Writable: true),
|
|
new AbLegacyTagDefinition("Bit3", deviceUri, "B3:0/3", AbLegacyDataType.Bit, Writable: true)),
|
|
Timeout = TimeSpan.FromSeconds(5),
|
|
Probe = new AbLegacyProbeOptions { Enabled = false },
|
|
Transport = transport,
|
|
}, driverInstanceId: $"ablegacy-bitwrite-{profile.Family}-{transport}");
|
|
var ct = TestContext.Current.CancellationToken;
|
|
await drv.InitializeAsync("{}", ct);
|
|
|
|
var refusedNatively = profile.Family == AbLegacyPlcFamily.Plc5 && transport == AbLegacyTransport.Native;
|
|
|
|
// 0x0A0A: bits 1,3,9,11 set; bit 5 clear.
|
|
if (!refusedNatively)
|
|
(await drv.WriteAsync([new WriteRequest("Word", (short)0x0A0A)], ct)).Single()
|
|
.StatusCode.ShouldBe(AbLegacyStatusMapper.Good, "seeding the whole word must succeed");
|
|
|
|
if (refusedNatively)
|
|
{
|
|
// Native PLC-5 writes are refused before the wire (#571): BadNotSupported (0x803D0000), not an ab_server gap.
|
|
(await drv.WriteAsync([new WriteRequest("Bit5", true)], ct)).Single()
|
|
.StatusCode.ShouldBe(AbLegacyStatusMapper.BadNotSupported);
|
|
return;
|
|
}
|
|
|
|
(await drv.WriteAsync([new WriteRequest("Bit5", true)], ct)).Single()
|
|
.StatusCode.ShouldBe(AbLegacyStatusMapper.Good, "B3:0/5 bit write must succeed");
|
|
Convert.ToInt32((await drv.ReadAsync(["Word"], ct)).Single().Value).ShouldBe(0x0A0A | 0x20);
|
|
|
|
(await drv.WriteAsync([new WriteRequest("Bit3", false)], ct)).Single()
|
|
.StatusCode.ShouldBe(AbLegacyStatusMapper.Good, "clearing B3:0/3 must succeed");
|
|
Convert.ToInt32((await drv.ReadAsync(["Word"], ct)).Single().Value).ShouldBe((0x0A0A | 0x20) & ~0x08);
|
|
}
|
|
}
|