Auto: abcip-1.2 — STRINGnn variant decoding
Closes #226 Adds nullable StringLength to AbCipTagDefinition + AbCipStructureMember so STRING_20 / STRING_40 / STRING_80 UDT variants decode against the right DATA-array capacity. The configured length threads through a new StringMaxCapacity field on AbCipTagCreateParams and lands on the libplctag Tag.StringMaxCapacity attribute (verified property on libplctag 1.5.2). Null leaves libplctag's default 82-byte STRING in place for back-compat. Driver gates on DataType == String so a stray StringLength on a DINT tag doesn't reshape that buffer. UDT member fan-out copies StringLength from the AbCipStructureMember onto the synthesised member tag definition. Tests: 4 new in AbCipDriverReadTests covering threaded StringMaxCapacity, the null back-compat path, the non-String gate, and the UDT-member fan-out.
This commit is contained in:
@@ -211,4 +211,79 @@ public sealed class AbCipDriverReadTests
|
||||
snapshots.Single().StatusCode.ShouldBe(AbCipStatusMapper.BadCommunicationError);
|
||||
factory.Tags["Nope"].Disposed.ShouldBeTrue();
|
||||
}
|
||||
|
||||
// PR abcip-1.2 — STRINGnn variant decoding. Threading <see cref="AbCipTagDefinition.StringLength"/>
|
||||
// through libplctag's StringMaxCapacity attribute lets STRING_20 / STRING_40 / STRING_80 UDTs
|
||||
// decode against the right DATA-array size; null preserves the default 82-byte STRING.
|
||||
|
||||
[Fact]
|
||||
public async Task StringLength_threads_into_TagCreateParams_StringMaxCapacity()
|
||||
{
|
||||
var (drv, factory) = NewDriver(
|
||||
new AbCipTagDefinition("Banner", "ab://10.0.0.5/1,0", "Banner", AbCipDataType.String,
|
||||
StringLength: 40));
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
factory.Customise = p => new FakeAbCipTag(p) { Value = "hello" };
|
||||
|
||||
await drv.ReadAsync(["Banner"], CancellationToken.None);
|
||||
|
||||
factory.Tags["Banner"].CreationParams.StringMaxCapacity.ShouldBe(40);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StringLength_null_leaves_StringMaxCapacity_null_for_back_compat()
|
||||
{
|
||||
var (drv, factory) = NewDriver(
|
||||
new AbCipTagDefinition("LegacyStr", "ab://10.0.0.5/1,0", "LegacyStr", AbCipDataType.String));
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
factory.Customise = p => new FakeAbCipTag(p) { Value = "world" };
|
||||
|
||||
await drv.ReadAsync(["LegacyStr"], CancellationToken.None);
|
||||
|
||||
factory.Tags["LegacyStr"].CreationParams.StringMaxCapacity.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StringLength_ignored_for_non_String_data_types()
|
||||
{
|
||||
// StringLength on a DINT-typed tag must not flow into StringMaxCapacity — libplctag would
|
||||
// otherwise re-shape the buffer and corrupt the read. EnsureTagRuntimeAsync gates on the
|
||||
// declared DataType.
|
||||
var (drv, factory) = NewDriver(
|
||||
new AbCipTagDefinition("Speed", "ab://10.0.0.5/1,0", "Speed", AbCipDataType.DInt,
|
||||
StringLength: 80));
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
factory.Customise = p => new FakeAbCipTag(p) { Value = 7 };
|
||||
|
||||
await drv.ReadAsync(["Speed"], CancellationToken.None);
|
||||
|
||||
factory.Tags["Speed"].CreationParams.StringMaxCapacity.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UDT_member_StringLength_threads_through_to_member_runtime()
|
||||
{
|
||||
// STRINGnn members of a UDT — declaration-driven fan-out copies StringLength from
|
||||
// AbCipStructureMember onto the synthesised member AbCipTagDefinition; the per-member
|
||||
// runtime then receives the right StringMaxCapacity.
|
||||
var udt = new AbCipTagDefinition(
|
||||
Name: "Recipe",
|
||||
DeviceHostAddress: "ab://10.0.0.5/1,0",
|
||||
TagPath: "Recipe",
|
||||
DataType: AbCipDataType.Structure,
|
||||
Members: [
|
||||
new AbCipStructureMember("Name", AbCipDataType.String, StringLength: 20),
|
||||
new AbCipStructureMember("Description", AbCipDataType.String, StringLength: 80),
|
||||
new AbCipStructureMember("Code", AbCipDataType.DInt),
|
||||
]);
|
||||
var (drv, factory) = NewDriver(udt);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
factory.Customise = p => new FakeAbCipTag(p) { Value = "x" };
|
||||
|
||||
await drv.ReadAsync(["Recipe.Name", "Recipe.Description", "Recipe.Code"], CancellationToken.None);
|
||||
|
||||
factory.Tags["Recipe.Name"].CreationParams.StringMaxCapacity.ShouldBe(20);
|
||||
factory.Tags["Recipe.Description"].CreationParams.StringMaxCapacity.ShouldBe(80);
|
||||
factory.Tags["Recipe.Code"].CreationParams.StringMaxCapacity.ShouldBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user