Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 193 additions & 0 deletions src/Api/Readers/TWN4ReaderDevice/Protocols/TWN4ReaderDevice.Io.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Writes a single byte to the requested host channel.
/// </summary>
/// <param name="channel">Communication endpoint to write to.</param>
/// <param name="value">Byte value to send.</param>
public async Task WriteByteAsync(IoChannel channel, byte value)
{
await CallFunctionAsync(new byte[] { API_IO, 0, (byte)channel, value });
}

/// <summary>
/// Reads a single byte from the requested host channel.
/// </summary>
/// <param name="channel">Communication endpoint to read from.</param>
/// <returns>The byte read from the reader buffer.</returns>
public async Task<byte> ReadByteAsync(IoChannel channel)
{
var parser = await CallFunctionAsync(new byte[] { API_IO, 1, (byte)channel });
return parser.ParseByte();
}

/// <summary>
/// Checks whether the requested channel buffer is empty for the given direction.
/// </summary>
/// <param name="channel">Communication endpoint to inspect.</param>
/// <param name="direction">Direction of the buffer to test.</param>
/// <returns><see langword="true"/> if the buffer is empty; otherwise <see langword="false"/>.</returns>
public async Task<bool> TestEmptyAsync(IoChannel channel, IoDirection direction)
{
var parser = await CallFunctionAsync(new byte[] { API_IO, 2, (byte)channel, (byte)direction });
return parser.ParseBool();
}

/// <summary>
/// Checks whether the requested channel buffer is already full for the given direction.
/// </summary>
/// <param name="channel">Communication endpoint to inspect.</param>
/// <param name="direction">Direction of the buffer to test.</param>
/// <returns><see langword="true"/> if the buffer is full; otherwise <see langword="false"/>.</returns>
public async Task<bool> TestFullAsync(IoChannel channel, IoDirection direction)
{
var parser = await CallFunctionAsync(new byte[] { API_IO, 3, (byte)channel, (byte)direction });
return parser.ParseBool();
}

/// <summary>
/// Retrieves the configured buffer size for the selected channel and direction.
/// </summary>
/// <param name="channel">Communication endpoint to query.</param>
/// <param name="direction">Direction of the buffer to query.</param>
/// <returns>The buffer size in bytes.</returns>
public async Task<ushort> GetBufferSizeAsync(IoChannel channel, IoDirection direction)
{
var parser = await CallFunctionAsync(new byte[] { API_IO, 4, (byte)channel, (byte)direction });
return parser.ParseUInt16();
}

/// <summary>
/// Retrieves the number of bytes currently stored in the requested channel buffer.
/// </summary>
/// <param name="channel">Communication endpoint to query.</param>
/// <param name="direction">Direction of the buffer to query.</param>
/// <returns>The amount of buffered data in bytes.</returns>
public async Task<ushort> GetByteCountAsync(IoChannel channel, IoDirection direction)
{
var parser = await CallFunctionAsync(new byte[] { API_IO, 5, (byte)channel, (byte)direction });
return parser.ParseUInt16();
}

/// <summary>
/// Applies serial framing parameters for the selected COM channel and direction.
/// </summary>
/// <param name="channel">Communication endpoint to configure.</param>
/// <param name="direction">Direction for which the parameters apply.</param>
/// <param name="baudRate">Baud rate to configure.</param>
/// <param name="parity">Parity constant as defined by the TWN4 firmware.</param>
/// <param name="stopBits">Stop bits constant as defined by the TWN4 firmware.</param>
/// <param name="dataBits">Number of data bits.</param>
/// <returns><see langword="true"/> if the configuration was accepted; otherwise <see langword="false"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="dataBits"/> is zero.</exception>
public async Task<bool> 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<byte> { 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();
}

/// <summary>
/// Returns the current USB device state.
/// </summary>
/// <returns>The USB device state reported by the firmware.</returns>
public async Task<UsbDeviceState> GetUsbDeviceStateAsync()
{
var parser = await CallFunctionAsync(new byte[] { API_IO, 7 });
return (UsbDeviceState)parser.ParseByte();
}

/// <summary>
/// Returns the host channel the reader currently uses for upstream communication.
/// </summary>
/// <returns>The configured host channel.</returns>
public async Task<IoChannel> GetHostChannelAsync()
{
var parser = await CallFunctionAsync(new byte[] { API_IO, 8 });
return (IoChannel)parser.ParseByte();
}

/// <summary>
/// Triggers a remote wakeup on the USB host.
/// </summary>
public async Task UsbRemoteWakeupAsync()
{
await CallFunctionAsync(new byte[] { API_IO, 9 });
}

/// <summary>
/// Writes multiple bytes to the requested host channel.
/// </summary>
/// <param name="channel">Communication endpoint to write to.</param>
/// <param name="data">Payload to send.</param>
/// <returns><see langword="true"/> when the reader acknowledged the frame; otherwise <see langword="false"/>.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="data"/> is <see langword="null"/>.</exception>
public async Task<bool> WriteBytesAsync(IoChannel channel, byte[] data)
{
if (data == null)
{
throw new ArgumentNullException(nameof(data));
}

var payload = new List<byte> { API_IO, 10, (byte)channel, (byte)data.Length };
payload.AddRange(data);
var parser = await CallFunctionAsync(payload.ToArray());
return parser.ParseBool();
}

/// <summary>
/// Reads the requested number of bytes from the specified host channel.
/// </summary>
/// <param name="channel">Communication endpoint to read from.</param>
/// <param name="length">Maximum number of bytes to read.</param>
/// <returns>Acknowledgement and payload returned by the reader.</returns>
public async Task<ReadBytesResult> 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
{
/// <summary>
/// Indicates whether the firmware accepted the request.
/// </summary>
public bool Acknowledged { get; set; }

/// <summary>
/// Data returned by the reader.
/// </summary>
public byte[] Data { get; set; }
}

#endregion
}
}
4 changes: 2 additions & 2 deletions src/Api/Readers/TWN4ReaderDevice/TWN4ReaderDevice.System.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public async Task<DeviceType> GetDeviceTypeAsync()
/// <returns>See TWN4 API Reference.</returns>
public async Task<byte> SleepAsync(uint ticks, uint flags)
{
List<byte> bytes = new List<byte> { API_RF, 7 };
List<byte> bytes = new List<byte> { API_SYS, 7 };
bytes.AddUInt32(ticks);
bytes.AddUInt32(flags);
var parser = await CallFunctionAsync(bytes.ToArray());
Expand Down Expand Up @@ -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))

Expand Down
42 changes: 42 additions & 0 deletions src/Internal/DataAccess/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,48 @@ public enum ReaderError : uint
BErr = 305
}

#region Host I/O

/// <summary>
/// Directions supported by the host channel buffers.
/// </summary>
public enum IoDirection : byte
{
Out = 0,
In = 1
}

/// <summary>
/// Communication channels available for host I/O helpers.
/// </summary>
public enum IoChannel : byte
{
Usb = 0,
Com1 = 1,
Com2 = 2,
CcidData = 3,
CcidControl = 4,
I2C = 5,
Rng = 6,
Spi = 7,
Host = 8,
None = 0xFF
}

/// <summary>
/// USB device lifecycle states reported by the reader.
/// </summary>
public enum UsbDeviceState : byte
{
NotInitialized = 0,
Default = 1,
Addressed = 2,
Configured = 3,
Suspended = 4
}

#endregion

#region GPIOs

/// <summary>
Expand Down
53 changes: 53 additions & 0 deletions tests/Elatec.NET.Tests/TWN4ReaderDeviceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
}
Loading