diff --git a/Demo/MauiDemo/MainPage.xaml b/Demo/MauiDemo/MainPage.xaml
index 7be44fe..e8dfab5 100644
--- a/Demo/MauiDemo/MainPage.xaml
+++ b/Demo/MauiDemo/MainPage.xaml
@@ -53,6 +53,10 @@
57600
115200
194000
+ 460800
+ 921600
+ 1843200
+ 2000000
@@ -106,7 +110,7 @@
-
+
@@ -147,6 +151,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Demo/MauiDemo/MainPage.xaml.cs b/Demo/MauiDemo/MainPage.xaml.cs
index 89453ff..cbd6a51 100644
--- a/Demo/MauiDemo/MainPage.xaml.cs
+++ b/Demo/MauiDemo/MainPage.xaml.cs
@@ -7,11 +7,23 @@ public partial class MainPage : ContentPage
public MainPage(MainViewModel vm)
{
InitializeComponent();
- this.BindingContext = vm;
- baudRatePicker.SelectedIndex = 6;
+ BindingContext = vm;
+ baudRatePicker.SelectedIndex = 9;
dataBitsPicker.SelectedIndex = 3;
stopBitsPicker.SelectedIndex = 0;
parityPicker.SelectedIndex = 0;
}
+ protected override void OnAppearing()
+ {
+ base.OnAppearing();
+ if (BindingContext is not MainViewModel vm)
+ return;
+ // if device already connected
+ // get all devices to list
+ // and select first
+ vm.GetAllCommand.Execute(null);
+ if (0 < vm.UsbDeviceInfos.Count)
+ vm.SelectedDeviceInfo = vm.UsbDeviceInfos[0];
+ }
}
}
diff --git a/Demo/MauiDemo/Models/IOTestModel.cs b/Demo/MauiDemo/Models/IOTestModel.cs
new file mode 100644
index 0000000..9e37b83
--- /dev/null
+++ b/Demo/MauiDemo/Models/IOTestModel.cs
@@ -0,0 +1,120 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using System.Diagnostics;
+using UsbSerialForAndroid.Net;
+using UsbSerialForAndroid.Net.Drivers;
+
+namespace MauiDemo.Models;
+
+public partial class IOTestModel : ObservableObject
+{
+ //private UsbDriverBase? _usbDriver;
+ [ObservableProperty] public partial string? WriteSpeed { get; set; }
+ [ObservableProperty] public partial string? ReadSpeed { get; set; }
+ public IOTestModel() { }
+
+ public static TimeSpan UpdatePreiod = TimeSpan.FromMilliseconds(1000);
+
+ public async Task StartTestAsync(int deviceId, int baudRate, byte dataBits, byte stopBits, byte parity,
+ CancellationToken ct)
+ {
+ using var usbDriver = UsbDriverFactory.CreateUsbDriver(deviceId);
+ var _stopBits = (UsbSerialForAndroid.Net.Enums.StopBits)stopBits;
+ var _parity = (UsbSerialForAndroid.Net.Enums.Parity)parity;
+ await usbDriver.OpenAsync(baudRate, dataBits, _stopBits, _parity);
+ await Task.WhenAny(ExecReadAsync(usbDriver, ct), ExecWriteAsync(usbDriver, ct));
+ }
+ public async Task ExecReadAsync(UsbDriverBase usbDriver, CancellationToken ct)
+ {
+ try
+ {
+ await ReadAsync(usbDriver, ct);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[err] {ex}");
+ }
+ }
+ public async Task ExecWriteAsync(UsbDriverBase usbDriver, CancellationToken ct)
+ {
+ try
+ {
+ await WriteAsync(usbDriver, ct);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[err] {ex}");
+ }
+ }
+ public const int SampleBufLength = 256;
+ public async Task WriteAsync(UsbDriverBase usbDriver, CancellationToken ct)
+ {
+ byte[] writeBuf = new byte[SampleBufLength];
+ // fill buf
+ for (int i = 0; i < writeBuf.Length; i++)
+ writeBuf[i] = (byte)i;
+
+ double speed = 0;
+ long sentTotal = 0;
+ long sentPrev = 0;
+ var sw = Stopwatch.StartNew();
+ TimeSpan tickPrev = sw.Elapsed;
+ while (!ct.IsCancellationRequested)
+ {
+ TimeSpan now = sw.Elapsed;
+ TimeSpan difTime = now - tickPrev;
+ if (UpdatePreiod < difTime)
+ {
+ double difBytes = sentTotal - sentPrev;
+ speed = (speed + (difBytes / difTime.TotalSeconds)) / 2;
+ WriteSpeed = $"{speed:N0} byte/sec, sent total={sentTotal}";// {difBytes:N0} {difTime}
+ tickPrev = now;
+ sentPrev = sentTotal;
+ }
+ if (SampleBufLength != await usbDriver.WriteAsync(writeBuf, 0, writeBuf.Length, ct))
+ throw new Exception("Something write wrong");
+ sentTotal += SampleBufLength;
+ }
+ }
+ private async Task ReadAsync(UsbDriverBase usbDriver, CancellationToken ct)
+ {
+ byte[] buf = new byte[SampleBufLength];
+ byte[] testDataSample = new byte[SampleBufLength];
+ // fill buf
+ for (int i = 0; i < testDataSample.Length; i++)
+ testDataSample[i] = (byte)i;
+ double speed = 0;
+ long readTotal = 0;
+ long readPrev = 0;
+ var sw = Stopwatch.StartNew();
+ TimeSpan tickPrev = sw.Elapsed;
+ while (!ct.IsCancellationRequested)
+ {
+ TimeSpan now = sw.Elapsed;
+ TimeSpan difTime = now - tickPrev;
+ if (UpdatePreiod < difTime)
+ {
+ double difBytes = readTotal - readPrev;
+ speed = (speed + (difBytes / difTime.TotalSeconds)) / 2;
+ ReadSpeed = $"{speed:N0} byte/sec read total={readTotal}";// {difBytes:N0} {difTime}
+ tickPrev = now;
+ readPrev = readTotal;
+ }
+ int currOffset = 0;
+ int currLen = SampleBufLength;
+ while (0 < currLen)
+ {
+ int currReadLen = await usbDriver.ReadAsync(buf, currOffset, currLen, ct);
+ currOffset += currReadLen;
+ currLen -= currReadLen;
+ readTotal += currReadLen;
+ }
+ if (!testDataSample.SequenceEqual(buf))
+ {
+ Console.WriteLine($"[err] {BitConverter.ToString(buf)}");
+ Console.WriteLine($"[err] Read {readTotal} not equal write sequence");
+ throw new Exception($"Read {readTotal} not equal write sequence");
+ }
+ }
+ }
+
+}
diff --git a/Demo/MauiDemo/ViewModels/MainViewModel.cs b/Demo/MauiDemo/ViewModels/MainViewModel.cs
index 9604c85..e72a2d3 100644
--- a/Demo/MauiDemo/ViewModels/MainViewModel.cs
+++ b/Demo/MauiDemo/ViewModels/MainViewModel.cs
@@ -93,6 +93,44 @@ items[3] is byte stopBits &&
ShowMessage(ex.Message);
}
});
+ [ObservableProperty] public partial string? WriteSpeed { get; set; }
+ [ObservableProperty] public partial string? ReadSpeed { get; set; }
+ [RelayCommand(IncludeCancelCommand = true)]
+ public async Task SendReceive(object[] items, CancellationToken ct)
+ {
+ try
+ {
+ if (items is null
+ || items.Length != 5
+ || items[0] is not UsbDeviceInfo usbDeviceInfo
+ || items[1] is not int baudRate
+ || items[2] is not byte dataBits
+ || items[3] is not byte stopBits
+ || items[4] is not Parity parity)
+ return;
+ var test = new IOTestModel();
+ test.PropertyChanged += (obj, arg) =>
+ {
+ MainThread.BeginInvokeOnMainThread(() =>
+ {
+ switch (arg.PropertyName)
+ {
+ default: break;
+ case nameof(WriteSpeed): WriteSpeed = test.WriteSpeed; break;
+ case nameof(ReadSpeed): ReadSpeed = test.ReadSpeed; break;
+ }
+ });
+ };
+ await Task.Run(() => test.StartTestAsync(usbDeviceInfo.DeviceId,
+ baudRate, dataBits, stopBits, (byte)parity, ct), ct);
+ }
+ catch (Exception ex)
+ {
+ ShowMessage(ex.Message);
+ }
+ WriteSpeed = null;
+ ReadSpeed = null;
+ }
public RelayCommand TestConnectCommand => new(() =>
{
try
diff --git a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs
index 376e874..634fb33 100644
--- a/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs
+++ b/UsbSerialForAndroid.Net/Drivers/Base/UsbDriverBase.cs
@@ -1,19 +1,31 @@
-using Android.App;
+#if DEBUG
+#define TRACE_INFO
+#endif
+using Android.App;
using Android.Content;
using Android.Hardware.Usb;
+using Java.Nio;
using System;
using System.Buffers;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Channels;
using System.Threading.Tasks;
using UsbSerialForAndroid.Net.Enums;
using UsbSerialForAndroid.Net.Exceptions;
+using UsbSerialForAndroid.Net.Helper;
namespace UsbSerialForAndroid.Net.Drivers
{
///
/// USB driver base class
///
- public abstract class UsbDriverBase
+ public abstract class UsbDriverBase : BaseDisposable
{
+ [Conditional("TRACE_INFO")]
+ public static void TraceInfo(string msg) => Console.WriteLine($"[USBDRIVER] {msg}");
+
private static readonly UsbManager usbManager = GetUsbManager();
public const byte XON = 17;
public const byte XOFF = 19;
@@ -107,7 +119,9 @@ private static UsbManager GetUsbManager()
/// dataBits
/// stopBits
/// parity
- public abstract void Open(int baudRate, byte dataBits, StopBits stopBits, Parity parity);
+ public void Open(int baudRate, byte dataBits, StopBits stopBits, Parity parity) =>
+ OpenAsync(baudRate, dataBits, stopBits, parity).AsTask().SynchronousWait();
+ public abstract ValueTask OpenAsync(int baudRate, byte dataBits, StopBits stopBits, Parity parity);
///
/// Set DTR enabled
///
@@ -121,9 +135,28 @@ private static UsbManager GetUsbManager()
///
/// close the usb device
///
- public virtual void Close()
+ public void Close() => CloseAsync().SynchronousWait();
+ public async virtual Task CloseAsync(List? errors = null)
{
- UsbDeviceConnection?.Close();
+ TraceInfo("CloseAsync");
+ try
+ {
+ await DeinitBuffersAsync();
+ }
+ catch (Exception ex)
+ {
+ errors?.Add(ex);
+ }
+ UsbEndpointRead?.Dispose(); UsbEndpointRead = null;
+ UsbEndpointWrite?.Dispose(); UsbEndpointWrite = null;
+ UsbDeviceConnection?.ReleaseInterface(UsbInterface);
+ UsbInterface?.Dispose(); UsbInterface = null;
+ UsbDeviceConnection?.Close(); UsbDeviceConnection = null;
+ TraceInfo("CloseAsync - Ok");
+ }
+ protected async override ValueTask DisposeAsyncCore()
+ {
+ await CloseAsync();
}
///
/// sync write
@@ -163,12 +196,9 @@ public virtual void Write(byte[] buffer)
/// write data
///
/// Write failed exception
- public virtual async Task WriteAsync(byte[] buffer)
+ public virtual Task WriteAsync(byte[] buffer)
{
- ArgumentNullException.ThrowIfNull(UsbDeviceConnection);
- int result = await UsbDeviceConnection.BulkTransferAsync(UsbEndpointWrite, buffer, 0, buffer.Length, WriteTimeout);
- if (result < 0)
- throw new BulkTransferException("Write failed", result, UsbEndpointWrite, buffer, 0, buffer.Length, WriteTimeout);
+ return WriteAsync(buffer, 0, buffer.Length);
}
///
/// async read
@@ -176,18 +206,15 @@ public virtual async Task WriteAsync(byte[] buffer)
/// The read data is returned after the read succeeds. Null data is returned after the read fails
public virtual async Task ReadAsync()
{
- ArgumentNullException.ThrowIfNull(UsbDeviceConnection);
- var buffer = ArrayPool.Shared.Rent(DefaultBufferLength);
+ var dest = ArrayPool.Shared.Rent(DefaultBufferLength);
try
{
- int result = await UsbDeviceConnection.BulkTransferAsync(UsbEndpointRead, buffer, 0, DefaultBufferLength, ReadTimeout);
- return result >= 0
- ? buffer.AsSpan().Slice(0, result).ToArray()
- : default;
+ int len = await ReadAsync(dest, 0, dest.Length);
+ return dest.AsSpan(0, len).ToArray();
}
finally
{
- ArrayPool.Shared.Return(buffer);
+ ArrayPool.Shared.Return(dest);
}
}
///
@@ -223,5 +250,393 @@ public bool TestConnection()
return false;
}
}
+ public const int UsbBufLength = 256;
+ public const int UsbRequestCount = 64;
+ public const int UsbMinRequestCount = 4;
+
+ public int ReadHeaderLength = 0;
+ public FilterDataFn? FilterData;
+ public delegate int FilterDataFn(Span src, Span dst);
+
+ protected UsbRequest? _usbWriteRequest;
+ protected List _readRequests = [];
+
+ protected UsbRequest? _current = null;
+ protected CancellationTokenSource? _processUsbTasksToken;
+
+ protected Task? _receiveTask;
+ protected Task? _sendTask;
+
+ Channel? _writeChannel;
+ Channel? _dataRqChannel;
+ Channel? _sendRqChannel;
+
+ protected async Task InitBuffersAsync()
+ {
+ TraceInfo("InitAsync");
+ ArgumentNullException.ThrowIfNull(UsbDeviceConnection);
+ ArgumentNullException.ThrowIfNull(UsbEndpointWrite);
+ ArgumentNullException.ThrowIfNull(UsbEndpointRead);
+
+ // initializing a queue of free write requests
+ _usbWriteRequest = new();
+ _usbWriteRequest.Initialize(UsbDeviceConnection, UsbEndpointWrite);
+ _writeChannel = Channel.CreateBounded(new BoundedChannelOptions(1)
+ {
+ SingleReader = true,
+ SingleWriter = true,
+ AllowSynchronousContinuations = false,
+ FullMode = BoundedChannelFullMode.Wait
+ });
+ await _writeChannel.Writer.WriteAsync(_usbWriteRequest);// just one
+ // initializing a queue of free read requests
+ _sendRqChannel = Channel.CreateUnbounded(new UnboundedChannelOptions()
+ {
+ SingleReader = true,
+ SingleWriter = false,
+ AllowSynchronousContinuations = false
+ });
+ _dataRqChannel = Channel.CreateUnbounded(new UnboundedChannelOptions()
+ {
+ SingleReader = false,
+ SingleWriter = true,
+ AllowSynchronousContinuations = false
+ });
+ // create and send to OS all read request
+ for (int i = 0; i < UsbRequestCount; i++)
+ {
+ var rq = new UsbRequest();
+ rq.Initialize(UsbDeviceConnection, UsbEndpointRead);
+ _readRequests.Add(rq);
+ rq.ClientData = new NetDirectByteBuffer(UsbBufLength);
+ await _sendRqChannel.Writer.WriteAsync(rq);
+ }
+ StartProcessingTasks();
+ TraceInfo("InitAsync - Ok");
+ }
+ protected async Task DeinitBuffersAsync(List? errors = null)
+ {
+ TraceInfo("DeinitAsync");
+ try
+ {
+ await StopProcessingTasks();
+ }
+ catch (Exception ex)
+ {
+ errors?.Add(ex);
+ }
+ try
+ {
+ Interlocked.Exchange(ref _writeChannel, null)?.Writer.Complete();
+ Interlocked.Exchange(ref _dataRqChannel, null)?.Writer.Complete();
+ Interlocked.Exchange(ref _sendRqChannel, null)?.Writer.Complete();
+
+ // clear requests
+ var oldReadRequests = Interlocked.Exchange(ref _readRequests, []);
+ foreach (var item in oldReadRequests)
+ {
+ if (item.ClientData is NetDirectByteBuffer buf)
+ buf.Dispose();
+ item.Cancel();
+ item.Close();
+ item.Dispose();
+ }
+ Interlocked.Exchange(ref _usbWriteRequest, null)?.Dispose();
+ TraceInfo("DeinitAsync - Ok");
+ }
+ catch (Exception ex)
+ {
+ errors?.Add(ex); // unexpected errors
+ }
+ }
+ private void StartProcessingTasks()
+ {
+ _processUsbTasksToken = new();
+ _sendTask = Task.Run(() => UsbSendAsync(_processUsbTasksToken.Token));
+ _receiveTask = Task.Run(() => UsbReceiveAsync(_processUsbTasksToken.Token));
+ }
+ private void ThrowIfNotStartedOrFaulted()
+ {
+ ArgumentNullException.ThrowIfNull(_receiveTask);
+ ArgumentNullException.ThrowIfNull(_sendTask);
+ if (_receiveTask.IsFaulted)
+ throw _receiveTask.Exception;
+ if (_sendTask.IsFaulted)
+ throw _sendTask.Exception;
+ }
+ private async Task StopProcessingTasks()
+ {
+ _processUsbTasksToken?.Cancel();
+ // await exit all tasks // clear all tasks
+ List exceptions = [];
+ if (null != _receiveTask)
+ {
+ try
+ {
+ await _receiveTask;
+ }
+ catch (OperationCanceledException) { }
+ catch (Exception ex)
+ {
+ exceptions.Add(ex); // mostly when device disconnected, or unexpected errors
+ }
+ _receiveTask = null;
+ }
+ if (null != _sendTask)
+ {
+ try
+ {
+ await _sendTask;
+ }
+ catch (OperationCanceledException) { }
+ catch (Exception ex)
+ {
+ exceptions.Add(ex); // mostly when device disconnected, or unexpected errors
+ }
+ _sendTask = null;
+ }
+ Interlocked.Exchange(ref _processUsbTasksToken, null)?.Dispose();
+ if (exceptions.Count > 0)
+ throw new AggregateException(exceptions);
+ }
+ protected virtual async Task UsbReceiveAsync(CancellationToken ct = default)
+ {
+ ArgumentNullException.ThrowIfNull(UsbDeviceConnection);
+ ArgumentNullException.ThrowIfNull(_writeChannel);
+ ArgumentNullException.ThrowIfNull(_dataRqChannel);
+ ArgumentNullException.ThrowIfNull(_sendRqChannel);
+ var wr = _writeChannel.Writer;
+ var dataRqWriter = _dataRqChannel.Writer;
+ var sendRqWriter = _sendRqChannel.Writer;
+ UsbRequest? dataRq = null;
+ while (!ct.IsCancellationRequested)
+ {
+ try
+ {
+ dataRq = await UsbDeviceConnection.RequestWaitAsync().WaitAsync(ct);
+ }
+ catch (Java.Lang.IllegalArgumentException iEx)
+ {
+ TraceInfo($"IllegalArgumentException {iEx}");
+ continue;
+ }
+ catch (BufferOverflowException boEx)
+ {
+ TraceInfo($"BufferOverflowException {boEx}");
+ continue;
+ }
+ if (null == dataRq)
+ {
+ TraceInfo($"response is null");
+ if (!TestConnection())
+ {
+ TraceInfo($"device disconnected - close port");
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ List errors = [];
+ await CloseAsync(errors);
+ if (0 < errors.Count)
+ TraceInfo($"close errors: {errors}");
+ }
+ catch (Exception ex)
+ {
+ TraceInfo($"unexpected error {ex}");
+ }
+ }, ct);
+ break;
+ }
+ continue;
+ }
+ if (ReferenceEquals(dataRq.Endpoint, UsbEndpointRead))
+ {
+ if (ReadHeaderLength < ((NetDirectByteBuffer?)dataRq.ClientData!).Position)
+ {
+ await dataRqWriter.WriteAsync(dataRq, ct);
+ // if _dataRqChannel is full, deqeue from the beginning of the queue
+ // we'll leave a reserve of UsbMinRequestCount(4) active requests in the OS queue.
+ if (UsbRequestCount - UsbMinRequestCount < _dataRqChannel.Reader.Count)
+ {
+ dataRq = Interlocked.Exchange(ref _current, null);// try return current first dequeued item
+ dataRq ??= await _dataRqChannel.Reader.ReadAsync(ct);
+ await sendRqWriter.WriteAsync(dataRq, ct);
+ }
+ }
+ else
+ await sendRqWriter.WriteAsync(dataRq, ct);
+ continue;
+ }
+ if (ReferenceEquals(dataRq.Endpoint, UsbEndpointWrite))
+ {
+ await wr.WriteAsync(dataRq, ct);
+ }
+ }
+ }
+ ///
+ /// receives requests
+ /// , and send back to OS request queue.
+ /// Does not allow the OS request queue to starve
+ ///
+ ///
+ ///
+ protected async Task UsbSendAsync(CancellationToken ct)
+ {
+ ArgumentNullException.ThrowIfNull(_sendRqChannel);
+ var sendRqReader = _sendRqChannel.Reader;
+ UsbRequest? sendRq;
+ while (!ct.IsCancellationRequested)
+ {
+ sendRq = await sendRqReader.ReadAsync(ct);
+ if (sendRq?.ClientData is NetDirectByteBuffer buf)
+ {
+ if (ReferenceEquals(sendRq.Endpoint, UsbEndpointRead))
+ {
+ buf.Rewind();
+ buf.ClientData = null;
+ }
+ if (!(OperatingSystem.IsAndroidVersionAtLeast(26) ?
+ sendRq.Queue(buf.JavaBuffer) : sendRq.Queue(buf.JavaBuffer, buf.JavaBuffer.Capacity())))
+ throw new Java.IO.IOException("Error queueing request.");
+ }
+ else
+ throw new Exception("broken emptyRq - ClientData is null or not NetDirectByteBuffer");
+ }
+ }
+ public virtual async Task ReadAsync(byte[] dstBuf, int offset, int count, CancellationToken ct = default)
+ {
+ TraceInfo($"Start read count={count}");
+ ThrowIfNotStartedOrFaulted();
+ ArgumentNullException.ThrowIfNull(_dataRqChannel);
+ ArgumentNullException.ThrowIfNull(_sendRqChannel);
+ var sendRqWriter = _sendRqChannel.Writer;
+ var dataRqReader = _dataRqChannel.Reader;
+ int readed = 0;
+ UsbRequest? rq = null;
+ try
+ {
+ rq = Interlocked.Exchange(ref _current, null); // try get previos peeked data,
+ rq ??= await dataRqReader.ReadAsync(ct); // if there is none, do async wait
+ // get all buffered data, not more than the requested size
+ while (!ct.IsCancellationRequested && rq?.ClientData is NetDirectByteBuffer buf)
+ {
+ var data = buf.MemBuffer.Span.Slice(0, buf.Position);
+ TraceInfo($"data length {buf.Position}");
+ if (null != FilterData && buf.Position > (count - readed))
+ {
+ buf.Position = FilterData(data, data);
+ buf.ClientData = true;// filtered
+ data = buf.MemBuffer.Span.Slice(0, buf.Position);
+ TraceInfo($"filter in buf, filtered Length={buf.Position}");
+ }
+ int currLen;
+ if (null == FilterData || buf.ClientData is true)
+ {
+ currLen = int.Min(count - readed, buf.Position);
+ TraceInfo($"copy filtered {currLen}");
+ data.Slice(0, currLen).CopyTo(dstBuf.AsSpan(offset));
+ }
+ else
+ {
+ currLen = FilterData(data, dstBuf.AsSpan(offset));
+ TraceInfo($"filter copy {currLen}");
+ }
+ readed += currLen;
+ offset += currLen;
+ TraceInfo($"readed={readed}");
+ if (readed == count)
+ {
+ var rest = buf.Position - currLen;
+ TraceInfo($"rest={rest}");
+ if (0 < rest)
+ {
+ data.Slice(currLen, rest).CopyTo(data.Slice(0, rest));
+ buf.Position = rest;
+ rq = Interlocked.Exchange(ref _current, rq);
+ }
+ else
+ {
+ await sendRqWriter.WriteAsync(rq, ct);
+ rq = null;
+ }
+ }
+ else
+ {
+ await sendRqWriter.WriteAsync(rq, ct);
+ dataRqReader.TryRead(out rq);
+ }
+ // _emptyReader!.Count does not work on single reader
+ //TraceInfo($"[USBDRIVER]: net buf={buf} data={_dataReader.Count} , free={_emptyReader!.Count}");
+ }
+ return readed;
+ }
+ finally
+ {
+ if (null != rq)
+ {
+ await sendRqWriter.WriteAsync(rq, CancellationToken.None);
+ }
+ }
+ }
+ public virtual async Task WriteAsync(byte[] wbuf, int offset, int count, CancellationToken ct = default)
+ {
+ ThrowIfNotStartedOrFaulted();
+ ArgumentNullException.ThrowIfNull(_writeChannel);
+ ArgumentNullException.ThrowIfNull(_sendRqChannel);
+ UsbRequest? wr = null;
+ try
+ {
+ var receiveQueue = _writeChannel.Reader;
+ var sendQueue = _sendRqChannel.Writer;
+ int rest = count;
+ while (0 < rest)
+ {
+ ct.ThrowIfCancellationRequested();
+ if (!receiveQueue.TryRead(out wr))
+ wr = await receiveQueue.ReadAsync(ct);// get a free write-request
+ using var buf = new NetDirectByteBuffer(wbuf, offset, int.Min(rest, UsbBufLength));
+ wr.ClientData = buf;
+ await sendQueue.WriteAsync(wr, ct);//send request
+ wr = null; // here we no longer own the request
+ wr = await receiveQueue.ReadAsync(ct);//wait response
+ offset += buf.Position;
+ rest -= buf.Position;
+ //TraceInfo($"[USBDRIVER]: sent {buf.Position}");
+ }
+ return count - rest;
+ }
+ catch (OperationCanceledException)
+ {
+ // we need to wait for a request to remove from the queue, even if we cancel the request
+ // the UsbDispatchAsync thread will do wait and return the free request to _writeChannel queue
+ var isCanceled = wr?.Cancel();
+ // isCanceled == true - operation canceled
+ // isCanceled == false - the operation does not require cancellation, because has already been completed
+ TraceInfo($"cancel write is {isCanceled}");
+ throw;
+ }
+ finally
+ {
+ if (null != wr)
+ {
+ // we will get here from "wr.QueueReq"
+ // or upon completion of sending
+ await _writeChannel.Writer.WriteAsync(wr, CancellationToken.None);
+ }
+ }
+ }
+ public async Task FlushAsync(CancellationToken ct = default)
+ {
+ ArgumentNullException.ThrowIfNull(_dataRqChannel);
+ ArgumentNullException.ThrowIfNull(_sendRqChannel);
+ var sendQueue = _sendRqChannel.Writer;
+ await StopProcessingTasks();
+ var curr = Interlocked.Exchange(ref _current, null);
+ if (curr != null)
+ await sendQueue.WriteAsync(curr, ct);
+ await foreach (var rq in _dataRqChannel.Reader.ReadAllAsync(ct))
+ await sendQueue.WriteAsync(rq, ct);
+ StartProcessingTasks();
+ }
}
}
\ No newline at end of file
diff --git a/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs
new file mode 100644
index 0000000..a18d30f
--- /dev/null
+++ b/UsbSerialForAndroid.Net/Drivers/CdcAcmSerialDriver.cs
@@ -0,0 +1,269 @@
+using Android.Hardware.Usb;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using UsbSerialForAndroid.Net.Enums;
+using UsbSerialForAndroid.Net.Exceptions;
+
+namespace UsbSerialForAndroid.Net.Drivers
+{
+ public class CdcAcmSerialDriver : UsbDriverBase
+ {
+ public const int UsbSubclassAcm = 2;
+ public const int UsbRecipInterface = 0x01;
+ public const int UsbRtAcm = UsbConstants.UsbTypeClass | UsbRecipInterface;
+ public const int SetLineCoding = 0x20;
+ public const int SetControlLineState = 0x22;
+ private int controlIndex;
+ private UsbInterface? controlInterface;
+ private UsbEndpoint? controlEndpoint;
+ public CdcAcmSerialDriver(UsbDevice usbDevice) : base(usbDevice) { }
+ public override async ValueTask OpenAsync(int baudRate, byte dataBits, StopBits stopBits, Parity parity)
+ {
+ UsbDeviceConnection = UsbManager.OpenDevice(UsbDevice);
+
+ if (UsbDevice.InterfaceCount == 1)
+ {
+ //device might be castrated ACM device, trying single interface logic
+ OpenSingleInterface();
+ }
+ else
+ {
+ OpenInterface();
+ }
+ SetParameters(baudRate, dataBits, stopBits, parity);
+ await InitBuffersAsync();
+ }
+ private void OpenSingleInterface()
+ {
+ ArgumentNullException.ThrowIfNull(UsbDeviceConnection);
+ controlIndex = 0;
+ controlInterface = UsbInterface = UsbDevice.GetInterface(0);
+ if (!UsbDeviceConnection.ClaimInterface(UsbInterface, true))
+ throw new Exception($"Could not claim interface {UsbInterfaceIndex}");
+
+ for (int i = 0; i < UsbInterface.EndpointCount; ++i)
+ {
+ var ep = UsbInterface.GetEndpoint(i);
+ if ((ep?.Direction == UsbAddressing.In) && (ep.Type == UsbAddressing.XferInterrupt))
+ {
+ controlEndpoint = ep;
+ }
+ else if ((ep?.Direction == UsbAddressing.In) && (ep.Type == UsbAddressing.XferBulk))
+ {
+ UsbEndpointRead = ep;
+ }
+ else if ((ep?.Direction == UsbAddressing.Out) && (ep.Type == UsbAddressing.XferBulk))
+ {
+ UsbEndpointWrite = ep;
+ }
+ }
+ }
+ private int GetFirstInterfaceIdFromDescriptors()
+ {
+ ArgumentNullException.ThrowIfNull(UsbDeviceConnection);
+ /* doesn't work, needs to deal with upstream
+ var descriptors = UsbDeviceConnection.GetDescriptors();
+
+ if (descriptors.Count > 0 &&
+ descriptors[0].Length == 18 &&
+ descriptors[0][1] == 1 && // bDescriptorType
+ descriptors[0][4] == (byte)UsbClass.Misc && //bDeviceClass
+ descriptors[0][5] == 2 && // bDeviceSubClass
+ descriptors[0][6] == 1)
+ {
+ int port = -1;
+ for (int i = 1; i < descriptors.Count; i++)
+ {
+ if (descriptors[i].Length == 8 &&
+ descriptors[i][1] == 0x0b && // bDescriptorType == IAD
+ descriptors[i][4] == (byte)UsbClass.Comm && // bFunctionClass == CDC
+ descriptors[i][5] == UsbSubclassAcm)
+ {
+ port++;
+ if (port == UsbInterfaceIndex && descriptors[i][3] == 2)
+ {
+ return descriptors[i][2];
+ }
+ }
+ }
+ }
+ */
+ return -1;
+ }
+
+ private void OpenInterface()
+ {
+ ArgumentNullException.ThrowIfNull(UsbDeviceConnection);
+
+ controlInterface = UsbDevice.GetInterface(0);
+ if (!UsbDeviceConnection.ClaimInterface(controlInterface, true))
+ throw new Exception("Could not claim control interface");
+
+ controlEndpoint = controlInterface.GetEndpoint(0);
+ UsbInterface = UsbDevice.GetInterface(1);
+
+ if (!UsbDeviceConnection.ClaimInterface(UsbInterface, true))
+ throw new Exception("Could not claim mDataInterface interface");
+
+
+ for (int i = 0; i < UsbInterface.EndpointCount; i++)
+ {
+ var ep = UsbInterface.GetEndpoint(i);
+ if (ep?.Direction == UsbAddressing.In && ep.Type == UsbAddressing.XferBulk)
+ {
+ UsbEndpointRead = ep;
+ }
+ else if (ep?.Direction == UsbAddressing.Out && ep.Type == UsbAddressing.XferBulk)
+ {
+ UsbEndpointWrite = ep;
+ }
+ }
+ }
+
+ private void OpenInterface1()
+ {
+ int id = GetFirstInterfaceIdFromDescriptors();
+ if (id >= 0)
+ {
+ for (int i = 0; i < UsbDevice.InterfaceCount; i++)
+ {
+ var usbInterface = UsbDevice.GetInterface(i);
+ if (usbInterface.Id == id || usbInterface.Id == id + 1)
+ {
+ if (usbInterface.InterfaceClass == UsbClass.Comm && (int)usbInterface.InterfaceSubclass == UsbSubclassAcm)
+ {
+ controlIndex = usbInterface.Id;
+ controlInterface = usbInterface;
+ }
+ if (usbInterface.InterfaceClass == UsbClass.CdcData)
+ {
+ UsbInterface = usbInterface;
+ }
+ }
+ }
+ }
+ if (controlInterface == null || UsbInterface == null)
+ {
+ int controlInterfaceCount = 0;
+ int dataInterfaceCount = 0;
+ for (int i = 0; i < UsbDevice.InterfaceCount; i++)
+ {
+ var usbInterface = UsbDevice.GetInterface(i);
+ if (usbInterface.InterfaceClass == UsbClass.Comm && (int)usbInterface.InterfaceSubclass == UsbSubclassAcm)
+ {
+ if (controlInterfaceCount == UsbInterfaceIndex)
+ {
+ controlIndex = usbInterface.Id;
+ controlInterface = usbInterface;
+ }
+ controlInterfaceCount++;
+ }
+ if (usbInterface.InterfaceClass == UsbClass.CdcData)
+ {
+ if (dataInterfaceCount == UsbInterfaceIndex)
+ {
+ UsbInterface = usbInterface;
+ }
+ dataInterfaceCount++;
+ }
+ }
+ }
+
+ ArgumentNullException.ThrowIfNull(controlInterface);
+ ArgumentNullException.ThrowIfNull(UsbDeviceConnection);
+
+ if (!UsbDeviceConnection.ClaimInterface(controlInterface, true))
+ throw new Exception("Could not claim control interface");
+
+ controlEndpoint = controlInterface.GetEndpoint(0);
+ if (controlEndpoint?.Direction != UsbAddressing.In || controlEndpoint?.Type != UsbAddressing.XferInterrupt)
+ throw new Exception("Invalid control endpoint");
+
+ ArgumentNullException.ThrowIfNull(UsbInterface);
+
+ if (!UsbDeviceConnection.ClaimInterface(UsbInterface, true))
+ throw new Exception("Could not claim data interface");
+
+ for (int i = 0; i < UsbInterface.EndpointCount; i++)
+ {
+ var ep = UsbInterface.GetEndpoint(i);
+ if (ep?.Direction == UsbAddressing.In && ep.Type == UsbAddressing.XferBulk)
+ {
+ UsbEndpointRead = ep;
+ }
+ else if (ep?.Direction == UsbAddressing.Out && ep.Type == UsbAddressing.XferBulk)
+ {
+ UsbEndpointWrite = ep;
+ }
+ }
+ }
+ private void SetParameters(int baudRate, byte dataBits, StopBits stopBits, Parity parity)
+ {
+ if (baudRate <= 0)
+ throw new ArgumentOutOfRangeException(nameof(baudRate), baudRate, "Invalid baud rate");
+
+ if (dataBits < 5 || dataBits > 8)
+ throw new ArgumentOutOfRangeException(nameof(dataBits), dataBits, "Invalid data bits");
+
+ byte stopBitsByte = stopBits switch
+ {
+ StopBits.One => 0,
+ StopBits.OnePointFive => 1,
+ StopBits.Two => 2,
+ _ => throw new ArgumentOutOfRangeException(nameof(stopBits), stopBits, "Invalid stop bits"),
+ };
+
+ byte parityBitesByte = parity switch
+ {
+ Parity.None => 0,
+ Parity.Odd => 1,
+ Parity.Even => 2,
+ Parity.Mark => 3,
+ Parity.Space => 4,
+ _ => throw new ArgumentOutOfRangeException(nameof(parity), parity, "Invalid parity"),
+ };
+
+ var buffer = new byte[]{
+ (byte) ( baudRate & 0xff),
+ (byte) ((baudRate >> 8 ) & 0xff),
+ (byte) ((baudRate >> 16) & 0xff),
+ (byte) ((baudRate >> 24) & 0xff),
+ stopBitsByte,
+ parityBitesByte,
+ dataBits};
+
+ ArgumentNullException.ThrowIfNull(UsbDeviceConnection);
+
+ int result = UsbDeviceConnection.ControlTransfer((UsbAddressing)UsbRtAcm, SetLineCoding, 0, controlIndex, buffer, buffer.Length, ControlTimeout);
+ if (result < 0)
+ throw new ControlTransferException("Set parameters failed", result, UsbRtAcm, SetLineCoding, 0, controlIndex, buffer, buffer.Length, ControlTimeout);
+ }
+ public override Task CloseAsync(List? errors = null)
+ {
+ controlEndpoint?.Dispose(); controlEndpoint = null;
+ UsbDeviceConnection?.ReleaseInterface(controlInterface);
+ controlInterface?.Dispose(); controlInterface = null;
+ return base.CloseAsync(errors);
+ }
+ public override void SetDtrEnabled(bool value)
+ {
+ DtrEnable = value;
+ SetDtrRts();
+ }
+ public override void SetRtsEnabled(bool value)
+ {
+ RtsEnable = value;
+ SetDtrRts();
+ }
+ private void SetDtrRts()
+ {
+ ArgumentNullException.ThrowIfNull(UsbDeviceConnection);
+
+ int value = (RtsEnable ? 0x2 : 0) | (DtrEnable ? 0x1 : 0);
+ int result = UsbDeviceConnection.ControlTransfer((UsbAddressing)UsbRtAcm, SetControlLineState, value, controlIndex, null, 0, ControlTimeout);
+ if (result != 0)
+ throw new ControlTransferException("Set dtr rts failed", result, UsbRtAcm, SetControlLineState, value, controlIndex, null, 0, ControlTimeout);
+ }
+ }
+}
diff --git a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs
index 1ac713f..6a760e6 100644
--- a/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs
+++ b/UsbSerialForAndroid.Net/Drivers/FtdiSerialDriver.cs
@@ -14,8 +14,6 @@ public class FtdiSerialDriver : UsbDriverBase
{
private bool baudRateWithPort = false;
public const int RequestTypeHostToDevice = UsbConstants.UsbTypeVendor | (int)UsbAddressing.Out;
- public const int ReadHeaderLength = 2; // contains MODEM_STATUS
-
public const int ModemControlDtrEnable = 0x0101;
public const int ModemControlDtrDisable = 0x0100;
@@ -33,7 +31,11 @@ public class FtdiSerialDriver : UsbDriverBase
public const int SetLatencyTimerRequest = 9; // SET_LATENCY_TIMER_REQUEST
public const int GetLatencyTimerRequest = 10; // GET_LATENCY_TIMER_REQUEST
- public FtdiSerialDriver(UsbDevice usbDevice) : base(usbDevice) { }
+ public FtdiSerialDriver(UsbDevice usbDevice)
+ : base(usbDevice)
+ {
+ ReadHeaderLength = 2;
+ }
///
/// Open the USB device
///
@@ -42,7 +44,7 @@ public FtdiSerialDriver(UsbDevice usbDevice) : base(usbDevice) { }
///
///
///
- public override void Open(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity)
+ public override async ValueTask OpenAsync(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity)
{
UsbDeviceConnection = UsbManager.OpenDevice(UsbDevice);
ArgumentNullException.ThrowIfNull(UsbDeviceConnection);
@@ -74,6 +76,8 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul
SetParameter(baudRate, dataBits, stopBits, parity);
SetLatency(1);
+ FilterData = FilterBuf;
+ await InitBuffersAsync();
}
///
/// Reset the USB device
@@ -304,14 +308,6 @@ public void SetLatency(byte latency)
ArrayPool.Shared.Return(buffer);
}
}
- ///
- /// Asynchronous read the data
- ///
- ///
- public override Task ReadAsync()
- {
- return Task.FromResult(Read());
- }
static private int FilterBuf(Span src, Span dst)
{
int curr;
diff --git a/UsbSerialForAndroid.Net/Drivers/ProlificSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/ProlificSerialDriver.cs
index 1a0bafd..7c6967b 100644
--- a/UsbSerialForAndroid.Net/Drivers/ProlificSerialDriver.cs
+++ b/UsbSerialForAndroid.Net/Drivers/ProlificSerialDriver.cs
@@ -1,5 +1,7 @@
using Android.Hardware.Usb;
using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
using UsbSerialForAndroid.Net.Enums;
using UsbSerialForAndroid.Net.Exceptions;
@@ -55,7 +57,7 @@ public ProlificSerialDriver(UsbDevice usbDevice) : base(usbDevice) { }
///
///
///
- public override void Open(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity)
+ public override async ValueTask OpenAsync(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity)
{
UsbDeviceConnection = UsbManager.OpenDevice(UsbDevice);
ArgumentNullException.ThrowIfNull(UsbDeviceConnection);
@@ -127,6 +129,12 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul
SetFlowControl(FlowControl);
SetParameter(baudRate, dataBits, stopBits, parity);
+ await InitBuffersAsync();
+ }
+ public override Task CloseAsync(List? errors = null)
+ {
+ UsbEndpointInterupt?.Dispose(); UsbEndpointInterupt = null;
+ return base.CloseAsync(errors);
}
///
/// Set parameter
@@ -139,9 +147,9 @@ private void SetParameter(int baudRate, byte dataBits, StopBits stopBits, Parity
{
var para = new byte[7];
para[0] = (byte)(baudRate & 0xFF);
- para[1] = (byte)(baudRate >> 8 & 0xFF);
- para[2] = (byte)(baudRate >> 16 & 0xFF);
- para[3] = (byte)(baudRate >> 24 & 0xFF);
+ para[1] = (byte)((baudRate >> 8) & 0xFF);
+ para[2] = (byte)((baudRate >> 16) & 0xFF);
+ para[3] = (byte)((baudRate >> 24) & 0xFF);
switch (stopBits)
{
case StopBits.None:
diff --git a/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs
index cca1db0..5d34924 100644
--- a/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs
+++ b/UsbSerialForAndroid.Net/Drivers/QinHengSerialDriver.cs
@@ -1,5 +1,6 @@
using Android.Hardware.Usb;
using System;
+using System.Threading.Tasks;
using UsbSerialForAndroid.Net.Enums;
using UsbSerialForAndroid.Net.Exceptions;
@@ -21,6 +22,22 @@ public class QinHengSerialDriver : UsbDriverBase
public const int SclDtr = 0x20;
public const int SclRts = 0x40;
+ // info from linux
+ // https://github.com/torvalds/linux/tree/master/drivers/usb/serial
+ // https://github.com/nospam2000/ch341-baudrate-calculation/tree/master
+ public byte ChipVersion = 0;
+ public const byte CH341_REQ_READ_VERSION = 0x5F;
+ public const byte CH341_REQ_WRITE_REG = 0x9A;
+ public const byte CH341_REQ_READ_REG = 0x95;
+ public const byte CH341_REQ_SERIAL_INIT = 0xA1;
+ public const byte CH341_REQ_MODEM_CTRL = 0xA4;
+
+ public const byte CH341_REG_DIVISOR = 0x13;
+ public const byte CH341_REG_PRESCALER = 0x12;
+
+ public const byte CH341_REG_LCR2 = 0x25;
+ public const byte CH341_REG_LCR = 0x18;
+
public const int RequestTypeHostToDeviceIn = UsbConstants.UsbTypeVendor | (int)UsbAddressing.In;
public const int RequestTypeHostToDeviceOut = UsbConstants.UsbTypeVendor | (int)UsbAddressing.Out;
public QinHengSerialDriver(UsbDevice usbDevice) : base(usbDevice) { }
@@ -32,7 +49,7 @@ public QinHengSerialDriver(UsbDevice usbDevice) : base(usbDevice) { }
///
///
///
- public override void Open(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity)
+ public override async ValueTask OpenAsync(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity)
{
UsbDeviceConnection = UsbManager.OpenDevice(UsbDevice);
ArgumentNullException.ThrowIfNull(UsbDeviceConnection);
@@ -64,9 +81,9 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul
}
}
}
-
Initialize();
SetParameter(baudRate, dataBits, stopBits, parity);
+ await InitBuffersAsync();
}
///
/// Initialize the device
@@ -74,53 +91,18 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul
///
private void Initialize()
{
- int request = 0x5f;
- int value = 0;
- int index = 0;
- var data = new int[] { -1 /* 0x27, 0x30 */, 0x00 };
- CheckState("init #1", request, value, data);
-
- request = 0xa1;
- value = 0;
- index = 0;
- int ret = ControlOut(request, value, index);
- if (ret < 0)
- throw new ControlTransferException("init failed! #2", ret, RequestTypeHostToDeviceOut, request, value, index, null, 0, ControlTimeout);
-
+ byte[] data = [0xFF /* 0x27, 0x30 */, 0x00];
+ CheckState("init #1", CH341_REQ_READ_VERSION, 0, data);
+ ChipVersion = data[0];
+ ControlOut("init #2", CH341_REQ_SERIAL_INIT, 0, 0);
SetBaudRate(DefaultBaudRate);
-
- request = 0x95;
- value = 0x2518;
- data = new int[] { -1 /* 0x56, c3*/, 0x00 };
- CheckState("init #4", request, value, data);
-
- request = 0x9a;
- value = 0x2518;
- index = 0x0050;
- ret = ControlOut(0x9a, 0x2518, 0x0050);
- if (ret < 0)
- throw new ControlTransferException("init failed! #5", ret, RequestTypeHostToDeviceOut, request, value, index, null, 0, ControlTimeout);
-
- request = 0x95;
- value = 0x0706;
- data = new int[] { -1 /*0xf?*/, -1 /*0xec,0xee*/};
- CheckState("init #6", request, value, data);
-
- request = 0xa1;
- value = 0x501f;
- index = 0xd90a;
- ret = ControlOut(request, value, index);
- if (ret < 0)
- throw new ControlTransferException("init failed! #7", ret, RequestTypeHostToDeviceOut, request, value, index, null, 0, ControlTimeout);
-
+ CheckState("init #4", CH341_REQ_READ_REG, 0x2518, [0xFF /* 0x56, c3*/, 0x00]);
+ ControlOut("init #5", CH341_REQ_WRITE_REG, 0x2518, 0x0050);
+ CheckState("init #6", CH341_REQ_READ_REG, 0x0706, [0xFF /*0xf?*/, 0xFF /*0xec,0xee*/]);
+ ControlOut("init #7", CH341_REQ_SERIAL_INIT, 0x501f, 0xd90a);
SetBaudRate(DefaultBaudRate);
-
SetControlLines();
-
- request = 0x95;
- value = 0x0706;
- data = new int[] { -1 /* 0x9f, 0xff*/, -1/*0xec,0xee*/ };
- CheckState("init #10", request, value, data);
+ CheckState("init #10", CH341_REQ_READ_REG, 0x0706, [0xFF /* 0x9f, 0xff*/, 0xFF/*0xec,0xee*/]);
}
///
/// Check the state of the device
@@ -131,23 +113,20 @@ private void Initialize()
///
///
///
- private void CheckState(string msg, int request, int value, int[] expected)
+ private void CheckState(string msg, int request, int value, byte[] expected)
{
byte[] buffer = new byte[expected.Length];
int ret = ControlIn(request, value, 0, buffer);
if (ret < 0)
throw new ControlTransferException($"Failed send cmd [{msg}]", ret, RequestTypeHostToDeviceIn, request, value, 0, buffer, buffer.Length, ControlTimeout);
-
if (ret != expected.Length)
throw new ControlTransferException($"Expected {expected.Length} bytes, but get {ret} [{msg}]", ret, RequestTypeHostToDeviceIn, request, value, 0, buffer, buffer.Length, ControlTimeout);
for (int i = 0; i < expected.Length; i++)
{
- if (expected[i] == -1) continue;
-
- int current = buffer[i] & 0xff;
- if (expected[i] != current)
- throw new Exception($"Expected 0x{expected[i]:X} bytes, but get 0x{current:X} [ {msg} ]");
+ if (expected[i] != 0xFF && expected[i] != buffer[i])
+ throw new Exception($"Expected 0x{expected[i]:X} bytes, but get 0x{buffer[i]:X} [ {msg} ]");
+ expected[i] = buffer[i];
}
}
///
@@ -170,10 +149,12 @@ private int ControlIn(int request, int value, int index, byte[] buffer)
///
///
///
- private int ControlOut(int request, int value, int index)
+ private void ControlOut(string msg, int request, int value, int index)
{
ArgumentNullException.ThrowIfNull(UsbDeviceConnection);
- return UsbDeviceConnection.ControlTransfer((UsbAddressing)RequestTypeHostToDeviceOut, request, value, index, null, 0, ControlTimeout);
+ int ret = UsbDeviceConnection.ControlTransfer((UsbAddressing)RequestTypeHostToDeviceOut, request, value, index, null, 0, ControlTimeout);
+ if (0 > ret)
+ throw new ControlTransferException(msg, ret, RequestTypeHostToDeviceOut, request, value, index, null, 0, ControlTimeout);
}
///
/// Set the control lines
@@ -181,12 +162,8 @@ private int ControlOut(int request, int value, int index)
///
private void SetControlLines()
{
- const int request = 0xA4;
int value = ~((DtrEnable ? SclDtr : 0) | (RtsEnable ? SclRts : 0));
- const int index = 0;
- int ret = ControlOut(request, value, 0);
- if (ret < 0)
- throw new ControlTransferException("Failed to set control lines", ret, RequestTypeHostToDeviceOut, request, value, index, null, 0, ControlTimeout);
+ ControlOut("Failed to set control lines", CH341_REQ_MODEM_CTRL, value, 0);
}
///
/// Set the baud rate
@@ -200,24 +177,17 @@ private void SetBaudRate(int baudRate)
{
if (baud[i * 3] == baudRate)
{
- const int request = 0x9a;
-
- const int value1 = 0x1312;
- int index1 = baud[i * 3 + 1];
- int ret = ControlOut(request, value1, index1);
- if (ret < 0)
- throw new ControlTransferException("Error setting baud rate. #1", ret, RequestTypeHostToDeviceOut, request, value1, index1, null, 0, ControlTimeout);
-
+ const int value1 = (CH341_REG_DIVISOR << 8) | CH341_REG_PRESCALER;
+ int index1 = baud[(i * 3) + 1];
+ if (ChipVersion > 0x27)
+ index1 |= 0x80; // BIT(7)
+ ControlOut("Error setting baud rate. #1", CH341_REQ_WRITE_REG, value1, index1);
const int value2 = 0x0f2c;
- int index2 = baud[i * 3 + 2];
- ret = ControlOut(request, value2, index2);
- if (ret < 0)
- throw new ControlTransferException("Error setting baud rate. #2", ret, RequestTypeHostToDeviceOut, request, value2, index2, null, 0, ControlTimeout);
-
+ int index2 = baud[(i * 3) + 2];
+ ControlOut("Error setting baud rate. #2", CH341_REQ_WRITE_REG, value2, index2);
return;
}
}
-
throw new Exception($"Baud rate {baudRate} currently not supported");
}
///
@@ -266,11 +236,8 @@ private void SetParameter(int baudRate, byte dataBits, StopBits stopBits, Parity
_ => throw new Exception($"Invalid stop bits: {stopBits}")
};
- const int request = 0x9A;
- const int value = 0x2518;
- int ret = ControlOut(request, value, lcr);
- if (ret < 0)
- throw new ControlTransferException("Error setting control byte", ret, RequestTypeHostToDeviceOut, request, value, lcr, null, 0, ControlTimeout);
+ const int value = (CH341_REG_LCR2 << 8) | CH341_REG_LCR;
+ ControlOut("Error setting control byte", CH341_REQ_WRITE_REG, value, lcr);
}
///
/// Set DTR enabled
diff --git a/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs b/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs
index c9e5fe6..8520d55 100644
--- a/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs
+++ b/UsbSerialForAndroid.Net/Drivers/SiliconLabsSerialDriver.cs
@@ -1,5 +1,7 @@
using Android.Hardware.Usb;
using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
using UsbSerialForAndroid.Net.Enums;
using UsbSerialForAndroid.Net.Exceptions;
@@ -36,7 +38,7 @@ public SiliconLabsSerialDriver(UsbDevice usbDevice) : base(usbDevice) { }
///
///
///
- public override void Open(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity)
+ public override async ValueTask OpenAsync(int baudRate = DefaultBaudRate, byte dataBits = DefaultDataBits, StopBits stopBits = DefaultStopBits, Parity parity = DefaultParity)
{
UsbDeviceConnection = UsbManager.OpenDevice(UsbDevice);
ArgumentNullException.ThrowIfNull(UsbDeviceConnection);
@@ -63,20 +65,20 @@ public override void Open(int baudRate = DefaultBaudRate, byte dataBits = Defaul
}
}
}
-
SetUartEnabled();
SetConfigSingle(SilabserSetMhsRequestCode, McrAll | ControlDtrDisable | ControlRtsDisable);
//SetConfigSingle(SilabserSetBauddivRequestCode, BaudRateGenFreq / DefaultBaudRate);
SetParameter(baudRate, dataBits, stopBits, parity);
+ await InitBuffersAsync();
}
///
/// close port
///
- public override void Close()
+ public override Task CloseAsync(List? errors = null)
{
PurgeHwBuffers(true, true);
SetConfigSingle(SilabserIcfEnableRquestCode, UartDisable);
- base.Close();
+ return base.CloseAsync(errors);
}
///
/// Set the UART enabled
diff --git a/UsbSerialForAndroid.Net/Enums/VendorIds.cs b/UsbSerialForAndroid.Net/Enums/VendorIds.cs
index 28cc9b4..4d8057d 100644
--- a/UsbSerialForAndroid.Net/Enums/VendorIds.cs
+++ b/UsbSerialForAndroid.Net/Enums/VendorIds.cs
@@ -10,5 +10,10 @@ public enum VendorIds
Prolific = 0x067B,
QinHeng = 0x1A86,
SiliconLabs = 0x10C4,
+ Arduino = 0x2341,
+ GigaDevice = 0x28E9,
+ Atmel = 0x03EB,
+ Stm32 = 0x0483,
+ Nrf = 0x1915,
}
}
diff --git a/UsbSerialForAndroid.Net/Helper/BaseDisposable.cs b/UsbSerialForAndroid.Net/Helper/BaseDisposable.cs
new file mode 100644
index 0000000..57c9cc4
--- /dev/null
+++ b/UsbSerialForAndroid.Net/Helper/BaseDisposable.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace UsbSerialForAndroid.Net.Helper;
+
+public class BaseDisposable : IAnyDisposable
+{
+ private int _isDisposed = 0;
+ public bool IsDisposed => 0 != _isDisposed;
+
+ ~BaseDisposable()
+ {
+ if (IsDisposed)
+ return;
+ DisposeInternal(false).SynchronousWait();
+ }
+ public async ValueTask DisposeAsync()
+ {
+ await DisposeInternal(true).ConfigureAwait(false);
+ GC.SuppressFinalize(this);
+ }
+ public async void Dispose()
+ {
+ await DisposeInternal(true).ConfigureAwait(false);
+ GC.SuppressFinalize(this);
+ }
+ private async Task DisposeInternal(bool disposing)
+ {
+ if (0 != Interlocked.Exchange(ref _isDisposed, 1))
+ return;
+ try
+ {
+ if (!disposing)
+ Console.WriteLine($"MEMORY LEAK: {GetType().FullName}");
+ await DisposeAsyncCore().ConfigureAwait(false);
+ Dispose(disposing);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"FAILED Dispose {ex}");
+ }
+ }
+ protected virtual void Dispose(bool disposing) { }
+ protected virtual ValueTask DisposeAsyncCore() => ValueTask.CompletedTask;
+}
diff --git a/UsbSerialForAndroid.Net/Helper/IAnyDisposable.cs b/UsbSerialForAndroid.Net/Helper/IAnyDisposable.cs
new file mode 100644
index 0000000..b07cf73
--- /dev/null
+++ b/UsbSerialForAndroid.Net/Helper/IAnyDisposable.cs
@@ -0,0 +1,7 @@
+using System;
+
+namespace UsbSerialForAndroid.Net.Helper;
+
+public interface IAnyDisposable : IDisposable, IAsyncDisposable
+{
+}
\ No newline at end of file
diff --git a/UsbSerialForAndroid.Net/Helper/NetDirectByteBuffer.cs b/UsbSerialForAndroid.Net/Helper/NetDirectByteBuffer.cs
new file mode 100644
index 0000000..b98a485
--- /dev/null
+++ b/UsbSerialForAndroid.Net/Helper/NetDirectByteBuffer.cs
@@ -0,0 +1,58 @@
+using Android.Runtime;
+using Java.Nio;
+using System;
+using System.Runtime.InteropServices;
+
+namespace UsbSerialForAndroid.Net.Helper;
+
+// Direct buffer does not require additional copying: line 320
+// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/hardware/usb/UsbRequest.java
+
+public class NetDirectByteBuffer : Java.Lang.Object, IDisposable
+{
+ public object? ClientData;
+ ///
+ /// it`s not copy its just wrapper for array
+ ///
+ ///
+ ///
+ ///
+ public NetDirectByteBuffer(byte[] array, int offset, int length)
+ : base()
+ {
+ ArgumentOutOfRangeException.ThrowIfGreaterThan(length, array.Length - offset);
+ _handle = GCHandle.Alloc(array, GCHandleType.Pinned);
+ MemBuffer = MemoryMarshal.CreateFromPinnedArray(array, offset, length);
+ IntPtr ndb = JNIEnv.NewDirectByteBuffer(_handle.AddrOfPinnedObject() + offset, length);
+ ByteBuffer? jdb = Java.Lang.Object.GetObject(ndb, JniHandleOwnership.TransferLocalRef);
+ ArgumentNullException.ThrowIfNull(jdb);
+ JavaBuffer = jdb;
+ }
+ ///
+ /// creates a Net array, pinned it and makes a DirectByteBuffer from it
+ ///
+ ///
+ public NetDirectByteBuffer(int capacity = 512)
+ : this(new byte[capacity], 0, capacity)
+ {
+ }
+
+ public readonly Memory MemBuffer;
+ public readonly ByteBuffer JavaBuffer;
+ GCHandle _handle;
+
+ public Java.Nio.Buffer? Rewind() => JavaBuffer.Rewind();
+ public int Position
+ {
+ get => JavaBuffer.Position();
+ set => JavaBuffer.Position(value);
+ }
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ JavaBuffer.Dispose();
+ _handle.Free();
+ }
+ }
+}
diff --git a/UsbSerialForAndroid.Net/Helper/SynchronousWaitExt.cs b/UsbSerialForAndroid.Net/Helper/SynchronousWaitExt.cs
new file mode 100644
index 0000000..688217d
--- /dev/null
+++ b/UsbSerialForAndroid.Net/Helper/SynchronousWaitExt.cs
@@ -0,0 +1,20 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace UsbSerialForAndroid.Net.Helper;
+public static class SynchronousWaitExt
+{
+ public static void SynchronousWait(this Task t)
+ {
+ if (SynchronizationContext.Current == null && TaskScheduler.Current == TaskScheduler.Default)
+ t.GetAwaiter().GetResult();
+ else
+ Task.Run(() => t).GetAwaiter().GetResult();
+ }
+ public static T SynchronousWait(this Task t)
+ {
+ if (SynchronizationContext.Current == null && TaskScheduler.Current == TaskScheduler.Default)
+ return t.GetAwaiter().GetResult();
+ return Task.Run(() => t).GetAwaiter().GetResult();
+ }
+}
diff --git a/UsbSerialForAndroid.Net/UsbDriverDictionary.cs b/UsbSerialForAndroid.Net/UsbDriverDictionary.cs
index f8e7373..990dd09 100644
--- a/UsbSerialForAndroid.Net/UsbDriverDictionary.cs
+++ b/UsbSerialForAndroid.Net/UsbDriverDictionary.cs
@@ -40,6 +40,10 @@ public UsbDriverDictionary()
Register((int)VendorIds.Prolific, (usbDevice) => new ProlificSerialDriver(usbDevice));
Register((int)VendorIds.QinHeng, (usbDevice) => new QinHengSerialDriver(usbDevice));
Register((int)VendorIds.SiliconLabs, (usbDevice) => new SiliconLabsSerialDriver(usbDevice));
+ Register((int)VendorIds.Atmel, (usbDevice) => new CdcAcmSerialDriver(usbDevice));
+ Register((int)VendorIds.GigaDevice, (usbDevice) => new CdcAcmSerialDriver(usbDevice));
+ Register((int)VendorIds.Arduino, (usbDevice) => new CdcAcmSerialDriver(usbDevice));
+ Register((int)VendorIds.Nrf, (usbDevice) => new CdcAcmSerialDriver(usbDevice));
}
void Register(int VendorId, CreateUsbDriverFn? fn)
{