RJCP.DLL.SerialPortStream icon indicating copy to clipboard operation
RJCP.DLL.SerialPortStream copied to clipboard

Unable to set the serial port state IOException in Open

Open IvanGit opened this issue 1 year ago • 2 comments

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?

IvanGit avatar Jul 03 '24 09:07 IvanGit

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.

jcurl avatar Jul 03 '24 11:07 jcurl

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" />

IvanGit avatar Jul 03 '24 12:07 IvanGit

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>

jcurl avatar Jul 04 '24 12:07 jcurl

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

IvanGit avatar Jul 04 '24 13:07 IvanGit

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

IvanGit avatar Jul 04 '24 13:07 IvanGit

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.

jcurl avatar Jul 04 '24 16:07 jcurl

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

IvanGit avatar Jul 04 '24 17:07 IvanGit

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 avatar Jul 04 '24 17:07 IvanGit

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 avatar Jul 04 '24 17:07 IvanGit

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.

IvanGit avatar Jul 04 '24 18:07 IvanGit

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().

jcurl avatar Jul 04 '24 19:07 jcurl

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

IvanGit avatar Jul 04 '24 19:07 IvanGit