jcurl / RJCP.DLL.SerialPortStream

SerialPortStream is an independent implementation of System.IO.Ports.SerialPort and SerialStream for better reliability and maintainability. Default branch is 2.x and now has support for Mono with help of a C library.
Microsoft Public License
639 stars 199 forks source link

Device Error #25

Closed AndreiGorlov closed 7 years ago

AndreiGorlov commented 7 years ago

Trying to read output from Arduino Uno clone (with CH340 on board):

var arduino = new SerialPortStream("COM6", 9600, 8, Parity.None, StopBits.One);
arduino.DtrEnable = true;

arduino.Open();

while (true)
{
    Console.Write(arduino.ReadExisting());
    System.Threading.Thread.Sleep(1000);
}

ReadExisting() throws:

Unhandled Exception: System.IO.IOException: Device Error
   at RJCP.IO.Ports.SerialPortStream.ReadCheckDeviceError(Boolean immediate) in c:\Users\jcurl\Documents\Programming\HELIOS\serialportstream\code\SerialPortStream.cs:line 629
   at RJCP.IO.Ports.SerialPortStream.ReadExisting() in c:\Users\jcurl\Documents\Programming\HELIOS\serialportstream\code\SerialPortStream.cs:line 931
   at ArduinoReader.Program.Main(String[] args) in C:\Users\Andrei\documents\visual studio 2017\Projects\ArduinoReader\ArduinoReader\Program.cs:line 17

Same code with System.IO.Ports.SerialPort works fine.

Windows 10 x64 1703, .NET 4.6.1

splitice commented 7 years ago

I've had a few Device Errors on our hardware too, usually on the first read if at all. Although I found they could be ignored. Have you tried catching it and repeating?

AndreiGorlov commented 7 years ago

Catching and repeating doesn't work. ReadExisting() constantly throws exception after third call. First call returns empty string, second call returns null.

jcurl commented 7 years ago

Sounds like the reading thread died. Can you please provide .net logs?

AndreiGorlov commented 7 years ago

Debug output with enabled trace:

IO.Ports.SerialPortStream Error: 0 : COM6: SerialThread: DoWaitCommEvent: Result: 87
IO.Ports.SerialPortStream Error: 0 : COM6: SerialThread: Died from Value does not fall within the expected range.
The thread 0x2628 has exited with code 0 (0x0).
Exception thrown: 'System.IO.IOException' in RJCP.SerialPortStream.dll
jcurl commented 7 years ago

The driver doesn't support the OS Call WaitCommEvent, which is pretty fundamental to the SerialPortStream implementation on Windows (it tells me when flush has occurred, when bytes are available, etc.)

As a fix can only be made with the hardware to test (I have no idea what this driver is doing), would you be willing to make some changes and debug? It will probably take some time before we get to a final solution.

The first would be to comment out the exception in the method DoWaitCommEvent from CommOverlappedIo.cs and see what happens:

        private bool DoWaitCommEvent(out NativeMethods.SerialEventMask mask, ref NativeOverlapped overlap)
        {
            bool result = UnsafeNativeMethods.WaitCommEvent(m_ComPortHandle, out mask, ref overlap);
            if (!result) {
                int w32err = Marshal.GetLastWin32Error();
                int hr = Marshal.GetHRForLastWin32Error();
                if (w32err != WinError.ERROR_IO_PENDING) {
                    SerialTrace.TraceSer.TraceEvent(System.Diagnostics.TraceEventType.Error, 0,
                        "{0}: SerialThread: DoWaitCommEvent: Result: {1}", m_Name, w32err);
                    // Marshal.ThrowExceptionForHR(hr);
                }
            } else {
                ProcessWaitCommEvent(mask);
            }
            return !result;
        }

That will make the main thread think that the result is pending (it will never arrive) effectively disabling it. Getting the EOF, flushing and errors from the driver won't work as expected. But I'm hoping at least the OS call ClearCommError will work. I'll need full logs for your test program to see what the effects are.

AndreiGorlov commented 7 years ago

After commenting out Marshal.ThrowExceptionForHR(hr) my program began to receive data from the device. Trace:

IO.Ports.SerialPortStream Error: 0 : COM6: SerialThread: DoWaitCommEvent: Result: 87
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: DoReadEvent: ReadFile(628, 3007597484464, 1048576) == False
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: 7 bytes
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: End=0; Bytes=7
IO.Ports.SerialPortStream Verbose: 0 : COM6: CommEvent: EV_RXCHAR
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: DoReadEvent: ReadFile(628, 3007597484471, 1048569) == False
IO.Ports.SerialPortStream Verbose: 0 : COM6: HandleEvent: Chars; NoError; NoChange;
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: 0 bytes
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: DoReadEvent: ReadFile(628, 3007597484471, 1048569) == False
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: 7 bytes
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: End=7; Bytes=7
IO.Ports.SerialPortStream Verbose: 0 : COM6: CommEvent: EV_RXCHAR
IO.Ports.SerialPortStream Verbose: 0 : COM6: HandleEvent: Chars; NoError; NoChange;
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: DoReadEvent: ReadFile(628, 3007597484478, 1048562) == False
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: 0 bytes
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: DoReadEvent: ReadFile(628, 3007597484478, 1048562) == False
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: 7 bytes
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: End=14; Bytes=7
...
jcurl commented 7 years ago

This is going to take a little time to get a full solution (I can only work on this in my free time). I would want to build in a detection mechanism for this. Then I need to deactivate or change some behaviour, such as flushing.

I need to analyse the logs. I might have some more tests that add extra logging. I want to check how the GetReceiveStat function works on your hardware.

AndreiGorlov commented 7 years ago

Ready to help.

jcurl commented 7 years ago

@splitice you indicated that you sometimes had the Device Error also but it only happened a few times. Do you have any logs for that situation? I've spent a few hours trying to reproduce and analysing code, but I can't see how this can occur. If you can, could you open a new issue?

safrazik commented 7 years ago

I have the same hardware - Arduino Uno clone (with CH340 on board) and I face the same problem with DataReceived event (the Read thread exited with code 0). When commenting the line Marshal.ThrowExceptionForHR(hr) the code worked

jcurl commented 7 years ago

I've made a commit to the branch "feature/dotnet-98" if you could please test. It's slightly different. If it's OK, I'll merge and put in the next release.

AndreiGorlov commented 7 years ago

feature/dotnet-98 works fine with CH340. Trace, just in case:

IO.Ports.SerialPortStream Error: 0 : COM6: SerialThread: DoWaitCommEvent: Result: 87
IO.Ports.SerialPortStream Warning: 0 : COM6: SerialThread: Not processing WaitCommEvent events
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: DoReadEvent: ReadFile(648, 2154877655472, 1048576) == False
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: 7 bytes
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: End=0; Bytes=7
IO.Ports.SerialPortStream Verbose: 0 : COM6: CommEvent: EV_RXCHAR
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: DoReadEvent: ReadFile(648, 2154877655479, 1048569) == False
IO.Ports.SerialPortStream Verbose: 0 : COM6: HandleEvent: Chars; NoError; NoChange;
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: 0 bytes
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: DoReadEvent: ReadFile(648, 2154877655479, 1048569) == False
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: 7 bytes
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: End=7; Bytes=7
IO.Ports.SerialPortStream Verbose: 0 : COM6: CommEvent: EV_RXCHAR
IO.Ports.SerialPortStream Verbose: 0 : COM6: HandleEvent: Chars; NoError; NoChange;
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: DoReadEvent: ReadFile(648, 2154877655486, 1048562) == False
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: 0 bytes
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: DoReadEvent: ReadFile(648, 2154877655486, 1048562) == False
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: 7 bytes
IO.Ports.SerialPortStream Verbose: 0 : COM6: SerialThread: ProcessReadEvent: End=14; Bytes=7
jcurl commented 7 years ago

Merged on master