diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Tests/S7TypeMappingTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Tests/S7TypeMappingTests.cs
new file mode 100644
index 0000000..bcaf395
--- /dev/null
+++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Tests/S7TypeMappingTests.cs
@@ -0,0 +1,173 @@
+using Shouldly;
+using Xunit;
+
+namespace ZB.MOM.WW.OtOpcUa.Driver.S7.Tests;
+
+///
+/// Unit tests for and
+/// — the pure read/write type-reinterpret
+/// helpers factored out of ReadOneAsync / WriteOneAsync so they can be
+/// exercised without a live PLC (Driver.S7-014).
+///
+[Trait("Category", "Unit")]
+public sealed class S7TypeMappingTests
+{
+ // ── Helpers ──────────────────────────────────────────────────────────────────────────────
+
+ private static S7TagDefinition Tag(string name, S7DataType dt) =>
+ new(name, "DB1.DBW0", dt);
+
+ private static S7ParsedAddress Addr(S7Size size) =>
+ new(S7Area.DataBlock, DbNumber: 1, size, ByteOffset: 0, BitOffset: 0);
+
+ // ── ReinterpretRawValue — implemented types ───────────────────────────────────────────
+
+ [Fact]
+ public void ReinterpretRawValue_Bool_returns_bool()
+ {
+ var tag = new S7TagDefinition("B", "DB1.DBX0.0", S7DataType.Bool);
+ var addr = new S7ParsedAddress(S7Area.DataBlock, 1, S7Size.Bit, 0, 0);
+ S7Driver.ReinterpretRawValue(tag, addr, true).ShouldBe(true);
+ S7Driver.ReinterpretRawValue(tag, addr, false).ShouldBe(false);
+ }
+
+ [Fact]
+ public void ReinterpretRawValue_Byte_returns_byte()
+ {
+ var tag = new S7TagDefinition("By", "DB1.DBB0", S7DataType.Byte);
+ var addr = Addr(S7Size.Byte);
+ S7Driver.ReinterpretRawValue(tag, addr, (byte)42).ShouldBe((byte)42);
+ }
+
+ [Fact]
+ public void ReinterpretRawValue_UInt16_returns_ushort()
+ {
+ S7Driver.ReinterpretRawValue(Tag("U16", S7DataType.UInt16), Addr(S7Size.Word), (ushort)1000)
+ .ShouldBe((ushort)1000);
+ }
+
+ [Fact]
+ public void ReinterpretRawValue_Int16_reinterprets_ushort_as_signed()
+ {
+ // 0xFFFF as ushort == -1 as short (two's complement reinterpret, not Convert).
+ var result = S7Driver.ReinterpretRawValue(Tag("I16", S7DataType.Int16), Addr(S7Size.Word), (ushort)0xFFFF);
+ result.ShouldBe((short)-1);
+ }
+
+ [Fact]
+ public void ReinterpretRawValue_UInt32_returns_uint()
+ {
+ S7Driver.ReinterpretRawValue(Tag("U32", S7DataType.UInt32), Addr(S7Size.DWord), (uint)70_000u)
+ .ShouldBe(70_000u);
+ }
+
+ [Fact]
+ public void ReinterpretRawValue_Int32_reinterprets_uint_as_signed()
+ {
+ // 0xFFFFFFFF as uint == -1 as int.
+ var result = S7Driver.ReinterpretRawValue(Tag("I32", S7DataType.Int32), Addr(S7Size.DWord), 0xFFFF_FFFFu);
+ result.ShouldBe(-1);
+ }
+
+ [Fact]
+ public void ReinterpretRawValue_Float32_converts_uint_bits_to_float()
+ {
+ float expected = 3.14f;
+ uint bits = BitConverter.SingleToUInt32Bits(expected);
+ var result = S7Driver.ReinterpretRawValue(Tag("F32", S7DataType.Float32), Addr(S7Size.DWord), bits);
+ ((float)result).ShouldBe(expected, tolerance: 1e-6f);
+ }
+
+ // ── ReinterpretRawValue — unsupported types throw NotSupportedException ───────────────
+
+ [Theory]
+ [InlineData(S7DataType.Int64)]
+ [InlineData(S7DataType.UInt64)]
+ [InlineData(S7DataType.Float64)]
+ [InlineData(S7DataType.String)]
+ [InlineData(S7DataType.DateTime)]
+ public void ReinterpretRawValue_unsupported_type_throws_NotSupportedException(S7DataType dt)
+ {
+ Should.Throw(() =>
+ S7Driver.ReinterpretRawValue(Tag("x", dt), Addr(S7Size.DWord), (uint)0));
+ }
+
+ // ── BoxValueForWrite — implemented types ─────────────────────────────────────────────
+
+ [Fact]
+ public void BoxValueForWrite_Bool_converts_value_to_bool()
+ {
+ S7Driver.BoxValueForWrite(S7DataType.Bool, true).ShouldBe(true);
+ S7Driver.BoxValueForWrite(S7DataType.Bool, false).ShouldBe(false);
+ S7Driver.BoxValueForWrite(S7DataType.Bool, 1).ShouldBe(true);
+ }
+
+ [Fact]
+ public void BoxValueForWrite_Byte_converts_to_byte()
+ {
+ S7Driver.BoxValueForWrite(S7DataType.Byte, (byte)200).ShouldBe((byte)200);
+ }
+
+ [Fact]
+ public void BoxValueForWrite_UInt16_converts_to_ushort()
+ {
+ S7Driver.BoxValueForWrite(S7DataType.UInt16, (ushort)1234).ShouldBe((ushort)1234);
+ }
+
+ [Fact]
+ public void BoxValueForWrite_Int16_reinterprets_as_ushort_two_complement()
+ {
+ // -1 as short → 0xFFFF as ushort; S7.Net writes the ushort to the wire.
+ S7Driver.BoxValueForWrite(S7DataType.Int16, (short)-1).ShouldBe((ushort)0xFFFF);
+ }
+
+ [Fact]
+ public void BoxValueForWrite_UInt32_converts_to_uint()
+ {
+ S7Driver.BoxValueForWrite(S7DataType.UInt32, 70_000u).ShouldBe(70_000u);
+ }
+
+ [Fact]
+ public void BoxValueForWrite_Int32_reinterprets_as_uint_two_complement()
+ {
+ // -1 as int → 0xFFFFFFFF as uint.
+ S7Driver.BoxValueForWrite(S7DataType.Int32, -1).ShouldBe(0xFFFF_FFFFu);
+ }
+
+ [Fact]
+ public void BoxValueForWrite_Float32_encodes_as_ieee754_uint()
+ {
+ float f = 3.14f;
+ var expected = BitConverter.SingleToUInt32Bits(f);
+ S7Driver.BoxValueForWrite(S7DataType.Float32, f).ShouldBe(expected);
+ }
+
+ // ── BoxValueForWrite — unsupported types throw NotSupportedException ─────────────────
+
+ [Theory]
+ [InlineData(S7DataType.Int64)]
+ [InlineData(S7DataType.UInt64)]
+ [InlineData(S7DataType.Float64)]
+ [InlineData(S7DataType.String)]
+ [InlineData(S7DataType.DateTime)]
+ public void BoxValueForWrite_unsupported_type_throws_NotSupportedException(S7DataType dt)
+ {
+ Should.Throw(() =>
+ S7Driver.BoxValueForWrite(dt, 0));
+ }
+
+ // ── BoxValueForWrite — overflow paths ────────────────────────────────────────────────
+
+ [Fact]
+ public void BoxValueForWrite_Byte_overflows_for_out_of_range_value()
+ {
+ // Convert.ToByte(256) throws OverflowException.
+ Should.Throw(() => S7Driver.BoxValueForWrite(S7DataType.Byte, 256));
+ }
+
+ [Fact]
+ public void BoxValueForWrite_UInt16_overflows_for_out_of_range_value()
+ {
+ Should.Throw(() => S7Driver.BoxValueForWrite(S7DataType.UInt16, 65_536));
+ }
+}