Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.IntegrationTests/AbLegacyBitWriteTests.cs
T

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);
}
}