Auto: s7-a1 — 64-bit scalar types

Closes the NotSupportedException cliff for S7 Float64/Int64/UInt64.

- S7Size enum gains LWord (8 bytes); parser accepts DBLD/DBL on data
  blocks and LD on M/I/Q (e.g. DB1.DBLD0, DB1.DBL8, MLD0, ILD8, QLD16).
- S7Driver.ReadOneAsync / WriteOneAsync issue ReadBytesAsync /
  WriteBytesAsync for 64-bit types and convert big-endian via
  System.Buffers.Binary.BinaryPrimitives. S7's wire format is BE.
- Internal MapArea(S7Area) helper translates to S7.Net DataType.
- MapDataType now surfaces native DriverDataType for Int16/UInt16/
  UInt32/Int64/UInt64 instead of collapsing them all to Int32.

Tests: parser theories cover DBLD/DBL/MLD/ILD/QLD; discovery test
asserts the 64-bit DriverDataType mapping. 64/64 passing.

Closes #287
This commit is contained in:
Joseph Doherty
2026-04-25 16:16:23 -04:00
parent c6c694b69e
commit d1699af609
4 changed files with 156 additions and 28 deletions

View File

@@ -14,6 +14,8 @@ public sealed class S7AddressParserTests
[InlineData("DB1.DBB0", 1, S7Size.Byte, 0, 0)]
[InlineData("DB1.DBW0", 1, S7Size.Word, 0, 0)]
[InlineData("DB1.DBD4", 1, S7Size.DWord, 4, 0)]
[InlineData("DB1.DBLD0", 1, S7Size.LWord, 0, 0)] // 64-bit long DWord
[InlineData("DB1.DBL8", 1, S7Size.LWord, 8, 0)] // 64-bit alt suffix (LReal)
[InlineData("DB10.DBW100", 10, S7Size.Word, 100, 0)]
[InlineData("DB1.DBX15.3", 1, S7Size.Bit, 15, 3)]
public void Parse_data_block_addresses(string input, int db, S7Size size, int byteOff, int bitOff)
@@ -53,6 +55,9 @@ public sealed class S7AddressParserTests
[InlineData("QW0", S7Area.Output, S7Size.Word, 0, 0)]
[InlineData("Q0.0", S7Area.Output, S7Size.Bit, 0, 0)]
[InlineData("QD4", S7Area.Output, S7Size.DWord, 4, 0)]
[InlineData("MLD0", S7Area.Memory, S7Size.LWord, 0, 0)] // 64-bit Merker
[InlineData("ILD8", S7Area.Input, S7Size.LWord, 8, 0)]
[InlineData("QLD16", S7Area.Output, S7Size.LWord, 16, 0)]
public void Parse_MIQ_addresses(string input, S7Area area, S7Size size, int byteOff, int bitOff)
{
var r = S7AddressParser.Parse(input);

View File

@@ -65,6 +65,34 @@ public sealed class S7DiscoveryAndSubscribeTests
builder.Variables[2].Attr.DriverDataType.ShouldBe(DriverDataType.Float32);
}
[Fact]
public async Task DiscoverAsync_maps_64bit_types_to_matching_DriverDataType()
{
// PR-S7-A1: 64-bit scalar types must surface with their native DriverDataType
// (not collapse to Int32) so the OPC UA address-space layer publishes the right
// BuiltInType. Address suffixes: DBLD (DB long-DWord), MLD/ILD/QLD (M/I/Q long-DWord).
var opts = new S7DriverOptions
{
Host = "192.0.2.1",
Tags =
[
new("BigInt", "DB1.DBLD0", S7DataType.Int64),
new("BigUInt", "DB1.DBLD8", S7DataType.UInt64),
new("BigDouble", "DB1.DBLD16", S7DataType.Float64),
new("MerkerLong", "MLD0", S7DataType.Int64),
],
};
using var drv = new S7Driver(opts, "s7-64bit");
var builder = new RecordingAddressSpaceBuilder();
await drv.DiscoverAsync(builder, TestContext.Current.CancellationToken);
builder.Variables.Single(v => v.Name == "BigInt").Attr.DriverDataType.ShouldBe(DriverDataType.Int64);
builder.Variables.Single(v => v.Name == "BigUInt").Attr.DriverDataType.ShouldBe(DriverDataType.UInt64);
builder.Variables.Single(v => v.Name == "BigDouble").Attr.DriverDataType.ShouldBe(DriverDataType.Float64);
builder.Variables.Single(v => v.Name == "MerkerLong").Attr.DriverDataType.ShouldBe(DriverDataType.Int64);
}
[Fact]
public async Task DiscoverAsync_propagates_WriteIdempotent_from_tag_to_attribute_info()
{