Skip to content

Conversation

@alex3696
Copy link
Contributor

@alex3696 alex3696 commented Dec 3, 2025

Yes :-) this is a big pull request with breaking changes.
Reasons:

  1. I need a working USART on Android to work with modbus and several custom protocols ( there is something similar here, but it is java, there are no additional tasks, concurrent queues, etc. readqueue
  2. use familiar async read and write calls as in Stream:
    Task ReadAsync(byte[] dstBuf, int offset, int count, CancellationToken ct)
    Task WriteAsync(byte[] srcBuf, int offset, int count, CancellationToken ct)
  3. Should work with most popular: CH340, FT232R, CP2102N, CP2102, CdcAcm (atmel, nrf, GigaDevice)
  4. Do not lose data or cause crashes at any speed up to 2000000
  5. Avoid unnecessary copying and delays
  6. Use FIFO buffer
  7. Show simple test application

what else is new:

  1. CdcAcmSerialDriver.cs - used from your brunch, with some fixes
  2. QinHengSerialDriver - updated, remove magic numbers and simplify
    3. ILogger - added simple custom logger
  3. use disposable pattern
  4. shared byte buffer between NET and Java
  5. Test application was updated, added test with 2 threads: the first thread always writes data (byte values ​​0...255) blocks of 256 bytes, the second thread reads blocks of 256 bytes, compares and stops the test if there is data loss. source

So... i did it, and test all. Seems to work well in tests with my listed devices. Testing at high speeds with closed RX-TX lines on the FT232R and CP2102N. The speed depends on the phone and the converter, tested on samsung s24+, a50 , blackview 5900
Screenshot_20251218_203320
IO rate is close to BaudRate, and depends on the phone's performance and converter model.

…) has been received unless bit 7 is set.

fix - codestyle and constants
…ingle native buffer for NET and Java. No memory copying required
@LUJIAN2020 LUJIAN2020 self-assigned this Dec 4, 2025
catch (Exception ex)
{
Logger.Error($"[USBDRIVER]: crash {ex}");
return;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it better to throw it normally

Copy link
Contributor Author

@alex3696 alex3696 Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we call "throw;" instead "return;" exception will be bubbled in awaiter task , i.e. StopProcessingTasks->DeinitBuffersAsync and break them.
This can be done, but I think there is no point in causing a failure with Exception when closing the port.
... or I didn't understand, please explain

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throw the exception outward and catch it at the final call site, so that the stack trace is not hidden.

Copy link
Contributor Author

@alex3696 alex3696 Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When physically disconnected, both tasks (_receiveTask, _sendTask) may fail with exceptions.
When attempting to close a port, an exception will be thrown in StopProcessingTasks, which will prevent the port from being closed. Port must be "closable" and release the resource in any case, even if some errors occur.

Copy link
Contributor Author

@alex3696 alex3696 Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it: Default closing or Disposing the port will still ignore errors and free the port.
Closing a port with parameters (List? errors) allows you to get the entire stack trace and close the port. 30f2281

@LUJIAN2020
Copy link
Owner

Thank you for your contribution. This is a significant submission. As work has taken up a lot of my time, I will address these submissions as soon as possible. Thank you again.

@alex3696
Copy link
Contributor Author

alex3696 commented Dec 4, 2025

Thank you for your contribution. This is a significant submission. As work has taken up a lot of my time, I will address these submissions as soon as possible. Thank you again.

I understand you. I also have a lot of work. There's no need to rush.
The code requires discussion. There are also a few more considerations for improving the code.

@@ -0,0 +1,12 @@
using System;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not recommended to integrate logging internally. Instead, exceptions can be thrown outward to preserve the stack trace, and handled by the user at the final call site.

Copy link
Contributor Author

@alex3696 alex3696 Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In most cases, I needed logging when debugging, but Debug.WriteLine is very slow(Console.WriteLine much faster), which led to delays, GC and the buffer quickly overflowed.
Which option do you prefer?

  1. We can remove logging and replace it with Debug.WriteLine where possible now
  2. replace to DI Microsoft.Extensions.Logging.

Copy link
Contributor Author

@alex3696 alex3696 Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok :-)
I had time to think and I did it - logging removed 5f1cb60

catch (Exception ex)
{
Debug.WriteLine(ex);
Logger.Error($"[USBDRIVER]: crash {ex}");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lot of stack trace information is being hidden in the code, which is quite problematic.

Copy link
Contributor Author

@alex3696 alex3696 Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolved : 30f2281

protected Task? _receiveTask;
protected Task? _sendTask;

Channel<UsbRequest>? _writeChannel;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For most hardware, simultaneous write and read operations are not supported. Communication is typically request–response based, where the next request can only be sent after a reply is received. As a result, using a queue does not bring much benefit and instead makes the code more complex.

Copy link
Contributor Author

@alex3696 alex3696 Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. "Most equipment", but not all. All UART converters are asynchronous.
  2. I tested, without dedicated tasks and queues, the processing speed drops significantly.
  3. In Windows/Linux there is buffering, the driver does this there, in our case no one did this.
    for example, when working with ch340 or FTDI without such an implementation, the user will have to launch and maintain the reading thread themselves so as not to lose data.
    Not everyone has enough experience to understand how to implement this correctly, perhaps for this reason there is still no public working version and many are faced with data loss. UPDATE: readqueue already in master branch.
    My implementation ReadAsync WriteAsync - hi-perfomance, easy to use, do not require knowledge of threads, features of Android etc.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neither Socket nor SerialPort has built-in queue processing; they only provide read and write functionality. Data queuing should be implemented as an extension of the communication library rather than being built in.

Copy link
Contributor Author

@alex3696 alex3696 Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not the case, linux and windows simplify access to the UART by hiding the hardware implementation, queues and buffering, and it works conveniently.
Do you want to reject PR or do you want to move the buffering to a separate extension wrapper class?
Extension will require a lot of my time to develop, testing and coordinate the requirements with you.

{
if (ReadHeaderLength < ((NetDirectByteBuffer?)dataRq.ClientData!).Position)
{
await dataRqWriter.WriteAsync(dataRq, ct);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether processing the received data directly is better than first enqueueing the data and then dequeuing it again for processing.

Copy link
Contributor Author

@alex3696 alex3696 Dec 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested: the task with UsbDeviceConnection.RequestWaitAsync should work as quickly as possible and mainly deal with waiting. If we start processing data directly in the same task, the data will begin to be delayed, the OS will quickly fill the buffers and the data will “begin to be lost”.
Second important note: a single android lock is used to enqueue and dequeue the linux buffer (in android line 115 ). This means that if we try to receive and send data from multiple threads, there will be a lot of blocking operations on that mutex.

@alex3696 alex3696 requested a review from LUJIAN2020 December 18, 2025 07:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants