diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/S7TagConfigModel.cs b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/S7TagConfigModel.cs index 1692f39a..3e944d4a 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/S7TagConfigModel.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/S7TagConfigModel.cs @@ -112,6 +112,26 @@ public sealed class S7TagConfigModel + string.Join(", ", Enum.GetNames()) + "."; } + // S7-016: array shape is judged against the driver's INIT guards, which THROW (failing the whole + // driver's init, not just this tag) for a wide-type array and for a writable Bool array. The + // isArray / arrayLength keys themselves belong to the TagModal's generic array controls + // (TagArrayConfig) and are intentionally not fields of this model, so read them off the key bag. + if (TagConfigJson.GetBool(_bag, "isArray")) + { + var dt = DataType; + if (dt is S7DataType.Int64 or S7DataType.UInt64 or S7DataType.Float64 + or S7DataType.String or S7DataType.DateTime) + { + return $"S7 does not support {dt} arrays — use Bool (read-only), Byte, Int16, UInt16, Int32, " + + "UInt32 or Float32 for an array tag, or a scalar tag."; + } + if (dt == S7DataType.Bool && (Writable ?? true)) + { + return "A Bool array cannot be writable (a byte-granular write would clobber neighbouring bits) — " + + "uncheck Writable, or use scalar Bool tags."; + } + } + // Gitea #554: the reader's remaining, non-enum refusal — a String tag whose 'stringLength' is // negative or above 254 (S7 REJECTS it rather than clamping) — is not restated here. The verdict and // the wording both come from the driver's own reader, via TagConfigParseGate. diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/S7TagConfigModelTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/S7TagConfigModelTests.cs index dbbccfa4..5152626c 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/S7TagConfigModelTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/S7TagConfigModelTests.cs @@ -103,4 +103,44 @@ public sealed class S7TagConfigModelTests [InlineData("""{"address":"DB1.DBW0","dataType":7}""")] public void Validate_accepts_every_dataType_the_driver_accepts(string json) => S7TagConfigModel.FromJson(json).Validate().ShouldBeNull(); + + // ---- S7-016: array shape vs the driver's init guards (which fail the WHOLE driver init) ---- + + [Theory] + [InlineData("Int64")] + [InlineData("UInt64")] + [InlineData("Float64")] + [InlineData("String")] + [InlineData("DateTime")] + public void Validate_rejects_wide_type_arrays(string dataType) + { + var m = S7TagConfigModel.FromJson( + $$"""{"address":"DB1.DBB0","dataType":"{{dataType}}","isArray":true,"arrayLength":4,"writable":false}"""); + + m.Validate().ShouldNotBeNull().ShouldContain("arrays"); + } + + [Fact] + public void Validate_rejects_a_writable_bool_array_but_accepts_it_read_only() + { + var writable = S7TagConfigModel.FromJson( + """{"address":"DB1.DBX0.0","dataType":"Bool","isArray":true,"arrayLength":4}"""); + writable.Validate().ShouldNotBeNull().ShouldContain("Bool array"); + + var readOnly = S7TagConfigModel.FromJson( + """{"address":"DB1.DBX0.0","dataType":"Bool","isArray":true,"arrayLength":4,"writable":false}"""); + readOnly.Validate().ShouldBeNull(); + } + + [Theory] + [InlineData("Byte", "DB1.DBB0")] + [InlineData("Int16", "DB1.DBW0")] + [InlineData("Float32", "DB1.DBD0")] + public void Validate_accepts_writable_arrays_of_supported_element_types(string dataType, string address) + { + var m = S7TagConfigModel.FromJson( + $$"""{"address":"{{address}}","dataType":"{{dataType}}","isArray":true,"arrayLength":4}"""); + + m.Validate().ShouldBeNull(); + } }