diff --git a/src/Api/Readers/TWN4ReaderDevice/Protocols/TWN4ReaderDevice.Io.cs b/src/Api/Readers/TWN4ReaderDevice/Protocols/TWN4ReaderDevice.Io.cs
new file mode 100644
index 0000000..3a1d7ed
--- /dev/null
+++ b/src/Api/Readers/TWN4ReaderDevice/Protocols/TWN4ReaderDevice.Io.cs
@@ -0,0 +1,193 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Elatec.NET.Helpers.ByteArrayHelper.Extensions;
+
+namespace Elatec.NET
+{
+ public partial class TWN4ReaderDevice
+ {
+ #region API_IO / Host I/O Functions
+
+ public static readonly byte API_IO = 1;
+
+ ///
+ /// Writes a single byte to the requested host channel.
+ ///
+ /// Communication endpoint to write to.
+ /// Byte value to send.
+ public async Task WriteByteAsync(IoChannel channel, byte value)
+ {
+ await CallFunctionAsync(new byte[] { API_IO, 0, (byte)channel, value });
+ }
+
+ ///
+ /// Reads a single byte from the requested host channel.
+ ///
+ /// Communication endpoint to read from.
+ /// The byte read from the reader buffer.
+ public async Task ReadByteAsync(IoChannel channel)
+ {
+ var parser = await CallFunctionAsync(new byte[] { API_IO, 1, (byte)channel });
+ return parser.ParseByte();
+ }
+
+ ///
+ /// Checks whether the requested channel buffer is empty for the given direction.
+ ///
+ /// Communication endpoint to inspect.
+ /// Direction of the buffer to test.
+ /// if the buffer is empty; otherwise .
+ public async Task TestEmptyAsync(IoChannel channel, IoDirection direction)
+ {
+ var parser = await CallFunctionAsync(new byte[] { API_IO, 2, (byte)channel, (byte)direction });
+ return parser.ParseBool();
+ }
+
+ ///
+ /// Checks whether the requested channel buffer is already full for the given direction.
+ ///
+ /// Communication endpoint to inspect.
+ /// Direction of the buffer to test.
+ /// if the buffer is full; otherwise .
+ public async Task TestFullAsync(IoChannel channel, IoDirection direction)
+ {
+ var parser = await CallFunctionAsync(new byte[] { API_IO, 3, (byte)channel, (byte)direction });
+ return parser.ParseBool();
+ }
+
+ ///
+ /// Retrieves the configured buffer size for the selected channel and direction.
+ ///
+ /// Communication endpoint to query.
+ /// Direction of the buffer to query.
+ /// The buffer size in bytes.
+ public async Task GetBufferSizeAsync(IoChannel channel, IoDirection direction)
+ {
+ var parser = await CallFunctionAsync(new byte[] { API_IO, 4, (byte)channel, (byte)direction });
+ return parser.ParseUInt16();
+ }
+
+ ///
+ /// Retrieves the number of bytes currently stored in the requested channel buffer.
+ ///
+ /// Communication endpoint to query.
+ /// Direction of the buffer to query.
+ /// The amount of buffered data in bytes.
+ public async Task GetByteCountAsync(IoChannel channel, IoDirection direction)
+ {
+ var parser = await CallFunctionAsync(new byte[] { API_IO, 5, (byte)channel, (byte)direction });
+ return parser.ParseUInt16();
+ }
+
+ ///
+ /// Applies serial framing parameters for the selected COM channel and direction.
+ ///
+ /// Communication endpoint to configure.
+ /// Direction for which the parameters apply.
+ /// Baud rate to configure.
+ /// Parity constant as defined by the TWN4 firmware.
+ /// Stop bits constant as defined by the TWN4 firmware.
+ /// Number of data bits.
+ /// if the configuration was accepted; otherwise .
+ /// Thrown when is zero.
+ public async Task SetComParametersAsync(IoChannel channel, IoDirection direction, uint baudRate, byte parity, byte stopBits, byte dataBits)
+ {
+ if (dataBits == 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(dataBits));
+ }
+
+ var payload = new List { API_IO, 6, (byte)channel, (byte)direction };
+ payload.AddUInt32(baudRate);
+ payload.Add(parity);
+ payload.Add(stopBits);
+ payload.Add(dataBits);
+
+ var parser = await CallFunctionAsync(payload.ToArray());
+ return parser.ParseBool();
+ }
+
+ ///
+ /// Returns the current USB device state.
+ ///
+ /// The USB device state reported by the firmware.
+ public async Task GetUsbDeviceStateAsync()
+ {
+ var parser = await CallFunctionAsync(new byte[] { API_IO, 7 });
+ return (UsbDeviceState)parser.ParseByte();
+ }
+
+ ///
+ /// Returns the host channel the reader currently uses for upstream communication.
+ ///
+ /// The configured host channel.
+ public async Task GetHostChannelAsync()
+ {
+ var parser = await CallFunctionAsync(new byte[] { API_IO, 8 });
+ return (IoChannel)parser.ParseByte();
+ }
+
+ ///
+ /// Triggers a remote wakeup on the USB host.
+ ///
+ public async Task UsbRemoteWakeupAsync()
+ {
+ await CallFunctionAsync(new byte[] { API_IO, 9 });
+ }
+
+ ///
+ /// Writes multiple bytes to the requested host channel.
+ ///
+ /// Communication endpoint to write to.
+ /// Payload to send.
+ /// when the reader acknowledged the frame; otherwise .
+ /// Thrown when is .
+ public async Task WriteBytesAsync(IoChannel channel, byte[] data)
+ {
+ if (data == null)
+ {
+ throw new ArgumentNullException(nameof(data));
+ }
+
+ var payload = new List { API_IO, 10, (byte)channel, (byte)data.Length };
+ payload.AddRange(data);
+ var parser = await CallFunctionAsync(payload.ToArray());
+ return parser.ParseBool();
+ }
+
+ ///
+ /// Reads the requested number of bytes from the specified host channel.
+ ///
+ /// Communication endpoint to read from.
+ /// Maximum number of bytes to read.
+ /// Acknowledgement and payload returned by the reader.
+ public async Task ReadBytesAsync(IoChannel channel, byte length)
+ {
+ var parser = await CallFunctionAsync(new byte[] { API_IO, 11, (byte)channel, length });
+ var acknowledged = parser.ParseBool();
+ var data = parser.ParseFlexByteArray();
+
+ return new ReadBytesResult
+ {
+ Acknowledged = acknowledged,
+ Data = data
+ };
+ }
+
+ public class ReadBytesResult
+ {
+ ///
+ /// Indicates whether the firmware accepted the request.
+ ///
+ public bool Acknowledged { get; set; }
+
+ ///
+ /// Data returned by the reader.
+ ///
+ public byte[] Data { get; set; }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Api/Readers/TWN4ReaderDevice/TWN4ReaderDevice.System.cs b/src/Api/Readers/TWN4ReaderDevice/TWN4ReaderDevice.System.cs
index 247db26..38171eb 100644
--- a/src/Api/Readers/TWN4ReaderDevice/TWN4ReaderDevice.System.cs
+++ b/src/Api/Readers/TWN4ReaderDevice/TWN4ReaderDevice.System.cs
@@ -87,7 +87,7 @@ public async Task GetDeviceTypeAsync()
/// See TWN4 API Reference.
public async Task SleepAsync(uint ticks, uint flags)
{
- List bytes = new List { API_RF, 7 };
+ List bytes = new List { API_SYS, 7 };
bytes.AddUInt32(ticks);
bytes.AddUInt32(flags);
var parser = await CallFunctionAsync(bytes.ToArray());
@@ -210,7 +210,7 @@ public class VersionInfo
#region API_PERIPH / Periphery Functions
- public static readonly byte API_PERIPH = 1;
+ public static readonly byte API_PERIPH = 2;
// Not supported: SYSFUNC(API_PERIPH, 0, bool SysSetGpioConfig(byte bits, byte pull_up_down, byte output_type))
diff --git a/src/Internal/DataAccess/Constants.cs b/src/Internal/DataAccess/Constants.cs
index 6c8c19c..856c668 100644
--- a/src/Internal/DataAccess/Constants.cs
+++ b/src/Internal/DataAccess/Constants.cs
@@ -434,6 +434,48 @@ public enum ReaderError : uint
BErr = 305
}
+ #region Host I/O
+
+ ///
+ /// Directions supported by the host channel buffers.
+ ///
+ public enum IoDirection : byte
+ {
+ Out = 0,
+ In = 1
+ }
+
+ ///
+ /// Communication channels available for host I/O helpers.
+ ///
+ public enum IoChannel : byte
+ {
+ Usb = 0,
+ Com1 = 1,
+ Com2 = 2,
+ CcidData = 3,
+ CcidControl = 4,
+ I2C = 5,
+ Rng = 6,
+ Spi = 7,
+ Host = 8,
+ None = 0xFF
+ }
+
+ ///
+ /// USB device lifecycle states reported by the reader.
+ ///
+ public enum UsbDeviceState : byte
+ {
+ NotInitialized = 0,
+ Default = 1,
+ Addressed = 2,
+ Configured = 3,
+ Suspended = 4
+ }
+
+ #endregion
+
#region GPIOs
///
diff --git a/tests/Elatec.NET.Tests/TWN4ReaderDeviceTests.cs b/tests/Elatec.NET.Tests/TWN4ReaderDeviceTests.cs
index ee18de7..06d060d 100644
--- a/tests/Elatec.NET.Tests/TWN4ReaderDeviceTests.cs
+++ b/tests/Elatec.NET.Tests/TWN4ReaderDeviceTests.cs
@@ -49,5 +49,58 @@ public async Task GetVersionStringAsync_ParsesResponseAndSetsLegicFlag()
Assert.Equal(versionString, result);
Assert.True(device.IsTWN4LegicReader);
}
+
+ [Fact]
+ public async Task GetBufferSizeAsync_UsesIoApiAndParsesUInt16()
+ {
+ var transport = new FakeReaderTransport("COM4");
+ transport.QueueResponseBytes(0x00, 0x10, 0x27);
+ var device = new TWN4ReaderDevice("COM4", _ => transport);
+
+ var result = await device.GetBufferSizeAsync(IoChannel.Com1, IoDirection.In);
+
+ Assert.Equal((ushort)10000, result);
+ Assert.Single(transport.WrittenLines, "01040101");
+ }
+
+ [Fact]
+ public async Task WriteBytesAsync_SendsPayloadLength()
+ {
+ var transport = new FakeReaderTransport("COM5");
+ transport.QueueResponseBytes(0x00, 0x01);
+ var device = new TWN4ReaderDevice("COM5", _ => transport);
+
+ var acked = await device.WriteBytesAsync(IoChannel.Usb, new byte[] { 0xAA, 0xBB, 0xCC });
+
+ Assert.True(acked);
+ Assert.Single(transport.WrittenLines, "010A0003AABBCC");
+ }
+
+ [Fact]
+ public async Task ReadBytesAsync_ParsesAcknowledgementAndPayload()
+ {
+ var transport = new FakeReaderTransport("COM6");
+ transport.QueueResponseBytes(0x00, 0x01, 0x02, 0xAA, 0xBB);
+ var device = new TWN4ReaderDevice("COM6", _ => transport);
+
+ var result = await device.ReadBytesAsync(IoChannel.Usb, 0x02);
+
+ Assert.True(result.Acknowledged);
+ Assert.Equal(new byte[] { 0xAA, 0xBB }, result.Data);
+ Assert.Single(transport.WrittenLines, "010B0002");
+ }
+
+ [Fact]
+ public async Task SleepAsync_UsesSysApi()
+ {
+ var transport = new FakeReaderTransport("COM7");
+ transport.QueueResponseBytes(0x00, 0x01);
+ var device = new TWN4ReaderDevice("COM7", _ => transport);
+
+ var result = await device.SleepAsync(5, 1);
+
+ Assert.Equal(0x01, result);
+ Assert.Single(transport.WrittenLines, "00070500000001000000");
+ }
}
}