views:

8314

answers:

10

I'm writing a Windows Service for communication with a Serial Mag-stripe reader and a relay board (access control system).

I run into problems where the code stops working (i get IOExceptions) after another program has "interrupted" the process by opening the same serial port as my service.

Part of the code is as follows:

public partial class Service : ServiceBase
{
 Thread threadDoorOpener;
 public Service()
 {
  threadDoorOpener = new Thread(DoorOpener);
 }
 public void DoorOpener()
 {
  while (true)
  {
   SerialPort serialPort = new SerialPort();
   Thread.Sleep(1000);
   string[] ports = SerialPort.GetPortNames();
   serialPort.PortName = "COM1";
   serialPort.BaudRate = 9600;
   serialPort.DataBits = 8;
   serialPort.StopBits = StopBits.One;
   serialPort.Parity = Parity.None;
   if (serialPort.IsOpen) serialPort.Close();
   serialPort.Open();
   serialPort.DtrEnable = true;
   Thread.Sleep(1000);
   serialPort.Close();
  }
 }
 public void DoStart()
 {
  threadDoorOpener.Start();
 }
 public void DoStop()
 {
  threadDoorOpener.Abort();
 }
 protected override void OnStart(string[] args)
 {
  DoStart();
 }
 protected override void OnStop()
 {
  DoStop();
 }
}

My sample program successfully starts the work-thread, and the opening/closing and raising of DTR causes my Mag-stripe reader to power up (wait 1sec), shut down (wait 1 sec) and so on.

If I launch HyperTerminal and connects to the same COM port, HyperTerminal tells me the port is currently in use. If i repeatedly press ENTER in HyperTerminal, to try to reopen the port it will succeed after a few retries.

This has the effect of causing IOExceptions in my work-thread, which is expected. However, even if I close down HyperTerminal, i still get the same IOException in my work-thread. The only cure is actually to restart the computer.

Other programs (which is not using .NET libraries for port-access) seem to work normally at this point.

Any ideas as to what is causing this?

+3  A: 

You can't close someone elses connection to a port, the following code will never work:

if (serialPort.IsOpen) serialPort.Close();

Because your object didn't open the port you can't close it.

Also you should close and dispose the serial port even after exceptions occur

try
{
   //do serial port stuff
}
finally
{
   if(serialPort != null)
   {
      if(serialPort.IsOpen)
      {
         serialPort.Close();
      }
      serialPort.Dispose();
   }
}

If you want the process to be interruptible then you should Check if the port is open and then back off for a period and then try again, something like.

while(serialPort.IsOpen)
{
   Thread.Sleep(200);
}
trampster
Not just close it, dispose it too! SerialPort implements IDisposable, so you might want to use using (SerialPort serialPort = new SerialPort) { /* Daniel's code */ }
DrJokepu
Good point will edit my post
trampster
But still, this doesn't really explain why my application refuses to open the port after it has been interrupted by another application. Or does it?
thomask
Also see the answer by FryGuy, HyperTerminal will not be able to interupt with his code.
trampster
@daniel: Your code will get an exception on trying to open the port when hyperterminal has it open, and then exit out of the thread. If you restart the thread, it should successfully open
FryGuy
I actually want it to be able to be interrupted, but also to resume operation once the interrupting application closes the port again.
thomask
Didn't see your answer before i posted my last comment, how would I go about restarting the thread? And should that be done whenever an IOException occurs?
thomask
see my edit at the bottom of my answer that way you get no exception and don't have to restart your thread.
trampster
+2  A: 

Have you tried leaving the port open in your application, and just turning DtrEnable on/off, and then closing the port when your application closes? i.e:

using (SerialPort serialPort = new SerialPort("COM1", 9600))
{
    serialPort.Open();
    while (true)
    {
        Thread.Sleep(1000);
        serialPort.DtrEnable = true;
        Thread.Sleep(1000);
        serialPort.DtrEnable = false;
    }
    serialPort.Close();
}

I'm not familiar with DTR semantics, so I don't know if this would work.

FryGuy
This device needs DTR=1 to "wake up", so basically the code switches the device on/off every second.
thomask
Does my code do what you need? It seems like it's the same as what the question is asking, but without giving up the resource.
FryGuy
I actually need the port to be available to another application inbetween the cycles.
thomask
A: 

I've tried changing the work-thread like this, with the exact same result. Once HyperTerminal once succeeds in "capturing the port" (while my thread is sleeping), my service won't be able to open the port again.

 public void DoorOpener()
 {
  while (true)
  {
   SerialPort serialPort = new SerialPort();
   Thread.Sleep(1000);
   serialPort.PortName = "COM1";
   serialPort.BaudRate = 9600;
   serialPort.DataBits = 8;
   serialPort.StopBits = StopBits.One;
   serialPort.Parity = Parity.None;
   try
   {
    serialPort.Open();
   }
   catch
   {
   }
   if (serialPort.IsOpen)
   {
    serialPort.DtrEnable = true;
    Thread.Sleep(1000);
    serialPort.Close();
   }
   serialPort.Dispose();
  }
 }
thomask
+1  A: 

This code seems to work properly. I've tested it on my local machine in a console application, using Procomm Plus to open/close the port, and the program keeps on ticking.

        using (SerialPort port = new SerialPort("COM1", 9600))
        {
            while (true)
            {
                Thread.Sleep(1000);
                try
                {
                    Console.Write("Open...");
                    port.Open();
                    port.DtrEnable = true;
                    Thread.Sleep(1000);
                    port.Close();
                    Console.WriteLine("Close");
                }
                catch
                {
                    Console.WriteLine("Error opening serial port");
                }
                finally
                {
                    if (port.IsOpen)
                        port.Close();
                }
            }
        }
FryGuy
Yes, that code works. Although when interrupted by HyperTerminal, it fails miserably, with the repeating error message in the catch block.This leads me to believe there is something wrong with the state HyperTerminal leaves the port in.
thomask
Regarding the code in the finally-block. Wouldn't the port.Close() statement potentially give another exception if the port is not opened by this application at this point?
thomask
No. SerialPort.IsOpen returns true if the *instance* of the serial port has the port open, not if the serial port is open in general. Think about it like a file. If you have a TextReader instance, calling IsOpen on it wouldn't return if the file is open by anyone, just if you have the file open.
FryGuy
A: 

This answer got to long to be a comment...

I believe that when your program is in a Thread.Sleep(1000) and you open your HyperTerminal connection, the HyperTerminal takes control over the serial port. When your program then wakes up and trying to open the serial port, an IOException is thrown.

Redesign your method and try to handle the opening of the port in a different way.

EDIT: About that you have to reboot your computer when your program fails...

That probably because your program isn´t really closed, open your taskmanager and see if you can find your program service. Be sure to stop all your threads before exiting your application.

Anders R
My program *really* is closed. And the work-thread is stopped using the Abort() method once the application exits. Basically once HyperTerminal has opened the port once, .NET will fail to open the same port until restart.
thomask
That's quite interesting. I'm going to see what HT does to my code, but first I have to find a copy of XP (no HT in Vista).
Peter Wone
A: 

Is there a good reason to keep your service from "owning" the port? Look at the built-in UPS service -- once you tell it there's an UPS attached to, say, COM1, you can kiss that port goodbye. I'd suggest you do the same unless there's a strong operational requirement to share the port.

Coderer
I need to share access to a door opener relay device with another application. This application is also kind enough to release the port after it's done with it (i.e. has singled it to open).
thomask
A: 

I think I have come to the conclusion that HyperTerminal does not play well. I've run the following test:

  1. Start my service in "console mode", it starts switching the device on/off (i can tell by it's LED).

  2. Start HyperTerminal and connect to the port. The device stays on (HyperTerminal raises DTR) My service writes to the event log, that it cannot open the port

  3. Stop HyperTerminal, I verify it is properly closed using task manager

  4. The device stays off (HyperTerminal has lowered DTR), my app keeps on writing to the event log, saying it cannot open the port.

  5. I start a third application (the one I need to coexist with), and tell it to connect to the port. I does so. No errors here.

  6. I stop the above mentioned application.

  7. VOILA, my service kicks in again, the port opens successfully, and the LED goes ON/OFF.

thomask
A: 

How to do reliable async comms

Don't use the blocking methods, the internal helper class has some subtle bugs.

Use APM with a session state class, instances of which manage a buffer and buffer cursor shared across calls, and a callback implementation that wraps EndRead in a try...catch. In normal operation, the last thing the try block should do is set up the next overlapped I/O callback with a call to BeginRead().

When things go awry, catch should asynchronously invoke a delegate to a restart method. The callback implementation should exit immediately after the catch block so that the restart logic can destroy the current session (session state is almost certainly corrupt) and create a new session. The restart method must not be implemented on the session state class because this would prevent it from destroying and recreating the session.

When the SerialPort object is closed (which will happen when the application exits) there may well be a pending I/O operation. When this is so, closing the SerialPort will trigger the callback, and under these conditions EndRead will throw an exception that is indistinguishable from a general comms shitfit. You should set a flag in your session state to inhibit the restart behaviour in the catch block. This will stop your restart method from interfering with natural shutdown.

This architecture can be relied upon not to hold onto the SerialPort object unexpectedly.

The restart method manages the closing and re-opening of the serial port object. After you call Close() on the SerialPort object, call Thread.Sleep(5) to give it a chance to let go. It is possible for something else to grab the port, so be ready to deal with this while re-opening it.

Peter Wone
A: 

Hi could y Peter Wone could you perhaps provide an example of your last solution?

Cheers,

Marc

+2  A: 

@thomask

Yes, Hyperterminal does in fact enable fAbortOnError in SetCommState's DCB, which explains for most of the IOExceptions thrown by the SerialPort object. Some PCs / handhelds also have UARTs that have the abort on error flag turned on by default - so it's imperative that a serial port's init routine clears it (which Microsoft neglected to do). I wrote a long article recently to explain this in greater detail (see this if you're interested).

Zach Saw
@Zach Saw: Great article you wrote, could I dare you to whip up a working sample for clearing fAbortOnError? :)
thomask
@thomask: Sure. I'll write up something quickly and post it as a follow-up article later.
Zach Saw
Here's the code.http://zachsaw.blogspot.com/2010/07/serialport-ioexception-workaround-in-c.html
Zach Saw
Thank you very much, this will be a nice cure :)
thomask
@Zach : Interesting stuff. What happens if this fAbortOnError flag on the DCB is off, and there is an error though, does it get swallowed?
Andy
fAbortOnError only aborts operations when an error occurs. It's orthogonal to error events coming through via the EventLoopRunner thread, so no, errors will still fire the appropriate events.
Zach Saw