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
612 stars 196 forks source link

Unable to set the serial port state IOException in Open #148

Open IvanGit opened 4 days ago

IvanGit commented 4 days ago

The error occurs when calling Open() in RJCP.IO.Ports.Native.Windows.CommState.SetCommState() в C:\Users\jcurl\Documents\Programming\rjcp.base\framework\serialportstream\code\Native\Windows\CommState.cs:line66 in RJCP.IO.Ports.Native.WinNativeSerial.SetPortSettings() в C:\Users\jcurl\Documents\Programming\rjcp.base\framework\serialportstream\code\Native\WinNativeSerial.cs:line 803 in RJCP.IO.Ports.SerialPortStream.Open(Boolean setCommState) в C:\Users\jcurl\Documents\Programming\rjcp.base\framework\serialportstream\code\SerialPortStream.cs:line 313 The error disappears when calling OpenDirect().

OS: Windows 10 64bit Port driver: Prolific PL2303GT USB Serial COM Port

Should I use OpenDirect instead of Open and what is the difference?

jcurl commented 4 days ago

The OpenDirect method is a workaround for drivers that don’t have physical layer, and usually ignore or bork on settings for parity, baud, data bits, etc.

if your PL2303GT is a TTL device with real signals, then it means the default baud rates, etc. as configured by the driver (or last used settings by another program) will be used.

Windows would also return an error code to these APIs. You can enable logging within the SerialPortStream and determine perhaps why it is failing this way. I can say all my tests with my USB TTL (Prolific, FTDI, 16550A, and others) work.

It might be setting a particular property that breaks. For this, you need to tell me the NuGet version, and best also to provide logs on which P/Inoke call that Win32API failed at.

IvanGit commented 4 days ago

I have installed RJCP.Diagnostics.Trace, added to app.config as described in 6.1.2, but file logfile.txt is not created. I use .NET 4.7.2 and package <package id="SerialPortStream" version="2.4.2" targetFramework="net472" />

jcurl commented 3 days ago

When you use .NET Framework, it's simpler. Just create/modify your App.config to include tracing. See https://github.com/jcurl/RJCP.DLL.SerialPortStream/blob/v2.x/test/SerialPortStreamTest/App.config as an example:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.diagnostics>
    <trace autoflush="false" indentsize="4"/>

    <sources>
      <source name="IO.Ports.SerialPortStream" switchValue="Verbose">
        <listeners>
          <add name="logListener"/>
          <remove name="Default"/>
        </listeners>
      </source>

      <source name="IO.Ports.SerialPortStream_ReadTo" switchValue="Verbose">
        <listeners>
          <add name="consoleListener"/>
          <remove name="Default"/>
        </listeners>
      </source>
    </sources>

    <sharedListeners>
      <add name="logListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="trace.log" />
      <add name="consoleListener" type="System.Diagnostics.ConsoleTraceListener" />
      <add name="xmlListener" type="System.Diagnostics.XmlWriterTraceListener" initializeData= "c:\logs\Traces.svclog" />
    </sharedListeners>
  </system.diagnostics>
</configuration>
IvanGit commented 3 days ago

I have modified file as described, but nothing. No logs, only IOException in runtime while Open().

IvanGit commented 3 days ago

The exception occurs in SerialPortStream::Open() => SerialPortStream::Open(true) => WinNativeSerial::SetPortSettings() => m_CommState.SetCommState() after Kernel32.SetCommState returned false.

jcurl commented 3 days ago

In your case, the Kernel driver (PL2303GT) is throwing the error (SetCommState).

        public void SetCommState()
        {
            if (!Kernel32.SetCommState(m_ComPortHandle, ref m_Dcb)) {
                throw new IOException("Unable to set the serial port state", Marshal.GetLastWin32Error());
            }
        }

Could you try something like:

var sps = new SerialPortStream("COM1");
sps.GetPortSetttings();
sps.Open();  // Does this work?
sps.Close()

If that works, then one can try changing the properties and seeing which one the driver is complaining about.

var sps = new SerialPortStream("COM1");
sps.GetPortSettings();
sps.Baud=115200;
sps.Open();
sps.Close();

It could be that the driver is complaining about a particular field in the DCB.

IvanGit commented 3 days ago

It looks like 2.4.2 does not contain GetPortSettings() method.

IvanGit commented 3 days ago

But I have implemented via reflection.

            var m_NativeSerial = sps.GetType().GetField("m_NativeSerial", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(sps);
            var type = m_NativeSerial.GetType();
            type.InvokeMember("Open", BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, m_NativeSerial, null);
            type.InvokeMember("GetPortSettings", BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, m_NativeSerial, null);
            type.InvokeMember("Close", BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, m_NativeSerial, null);
            sps.Open();  // Does this work? 
            sps.Close();

Yes, it works. Default baud rate is 115200.

            var m_NativeSerial = sps.GetType().GetField("m_NativeSerial", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(sps);
            var type = m_NativeSerial.GetType();
            type.InvokeMember("Open", BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, m_NativeSerial, null);
            type.InvokeMember("GetPortSettings", BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, m_NativeSerial, null);
            type.InvokeMember("Close", BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, m_NativeSerial, null);
            sps.BaudRate = 115200;
            sps.Open();
            sps.Close();

It works too.

IvanGit commented 3 days ago

It works without stopbits:

            (int BaudRate, int DataBits, Parity Parity, StopBits StopBits, int ReadTimeout, int WriteTimeout) = 
                (sps.BaudRate, sps.DataBits, sps.Parity, sps.StopBits, sps.ReadTimeout, sps.WriteTimeout);

            var m_NativeSerial = sps.GetType().GetField("m_NativeSerial", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(sps);
            var type = m_NativeSerial.GetType();
            type.InvokeMember("Open", BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, m_NativeSerial, null);
            type.InvokeMember("GetPortSettings", BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, m_NativeSerial, null);
            type.InvokeMember("Close", BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, m_NativeSerial, null);
            (sps.BaudRate, sps.DataBits, sps.Parity, /*sps.StopBits,*/ sps.ReadTimeout, sps.WriteTimeout) = 
                (BaudRate, DataBits, Parity, /*StopBits,*/ ReadTimeout, WriteTimeout);
            sps.Open();
            Debug.Assert(sps.IsOpen);
IvanGit commented 3 days ago

The reason was in StopBits. RJCP.IO.Ports.Parity is compatible with System.IO.Ports.Parity, but RJCP.IO.Ports.StopBits is not compatible with System.IO.Ports.StopBits. Code was ported from SerialPort and stream setup before like sps.StopBits = (RJCP.IO.Ports.StopBits)db_Int32Value;

I have created extension

    public static class SerialPortStreamStopBitsConverter
    {
        private static readonly Dictionary<System.IO.Ports.StopBits, RJCP.IO.Ports.StopBits> _map = new Dictionary<System.IO.Ports.StopBits, RJCP.IO.Ports.StopBits>()
        {
            { System.IO.Ports.StopBits.One, RJCP.IO.Ports.StopBits.One },
            { System.IO.Ports.StopBits.OnePointFive, RJCP.IO.Ports.StopBits.One5 },
            { System.IO.Ports.StopBits.Two, RJCP.IO.Ports.StopBits.Two }
        };

        public static RJCP.IO.Ports.StopBits ToStopBits(this System.IO.Ports.StopBits stopBits)
        {
            return _map.TryGetValue(stopBits, out var value) ? value : default;
        }
    }

and use it like

sps.StopBits = ((System.IO.Ports.StopBits)db_Int32Value).ToStopBits();

and now it works. Because One bit has been cast to One5 previously.

jcurl commented 3 days ago

Great to see you got to the root cause. Are you sure you needed reflection? I looked at the 2.x branch and SerialPortStream has public void GetPortSettings().

IvanGit commented 3 days ago

Yes, you are right. I have rechecked, reflection is not needed here. Now GetPortSettings() is appeared. No idea why it wasn't compiling before.