Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Atomic Sending and Receiving of Messages over RS232

I have a peculiar problem.

A custom protocol which I need to use to communicate with a serial device works as follows, I'm using standard ASCII abreviations.

  1. -> My program sends SOH to start transmitting
  2. -> My program sends a packet of STX+data+ETX (I actually send both SOH and this packet in one go)
  3. <- The device sends back an ACK to acknowledge he has received the packet
  4. -> Now my program sends an EOT to signal end of transmission

If the message was a request expecting an answer, the device sends back data. Because the way SerialPort.DataReceived works I have to piece the received message back together manually. It goes a bit like this:

  1. <- Device sends SOH+STX+data
  2. <- Device sends additional bytes, ending with ETX
  3. -> My program sends ACK once the package has been received in its entirety
  4. <- Device sends EOT

This is basically the way these two communicate, barring lost packets and NAKs and those things, but I don't want to make it too complicated.

My problem is: How do I wrap either the sending of a complete message or receiving of a complete message into an atomic operation? What I don't want is my program sending a new message while I'm in the middle of gluing a received message back together.

I have tried using Monitor.Enter() and Monitor.Exit() but the received event is called on a different thread, so no dice.

I have also tried using a Semaphore with only 1 resource, calling semaphore.WaitOne() at the start of either sending or receiving and calling semaphore.Release() after I have sent the EOT and after I have received an EOT from the device. This also doesn't work quite well.

Is there any way to do this better?

like image 556
Davio Avatar asked Oct 22 '22 18:10

Davio


2 Answers

I would use a lock:

http://msdn.microsoft.com/en-gb/library/c5kehkcz(v=vs.71).aspx

You can setup a global object say serialLock and use this to make sure that when you are glueing pieces together any send threads have to wait:

In the received

lock(serialLock)
{
//Glue data
}

In all send:

lock(serialLock)
{
// send data
}

This will sort any problems with different threads etc.

I would also make sure you have received a full message in the glue data section before releasing the lock. Maybe to make this cleared would be you will need to update a thread-safe data structure in the DataReceived event and in the glue data section you will need to stay in this section checking the data structure for a full message before releasing your lock.

like image 146
TheKingDave Avatar answered Oct 29 '22 21:10

TheKingDave


Faced a similar problem when I wrote code to communicate with a GSM Modem over serial port.

I was able to develop a reasonably good solution by making use of AutoResetEvent class in the System.Threading namespace. Basically, I made my Send method to wait for the ACK signal.

Here is a skeleton code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO.Ports;

namespace Sample
{
    public class SocketWrapper
    {
        #region Static Variables

        private static AutoResetEvent _SendWaitHandle = new AutoResetEvent(false);

        #endregion

        #region Member Variables

        private object _SendLockToken = new object();

        #endregion

        #region Public Methods

        public void Write(byte[] data)
        {
            Monitor.Enter(_SendLockToken);
            try
            {
                // Reset Handle
                _SendWaitHandle.Reset();

                // Send Data
                // Your Logic

                // Wait for ACK
                if (_SendWaitHandle.WaitOne(1000)) // Will wait for 1000 miliseconds
                {
                    // ACK Received
                    // Send EOT
                }
                else
                {
                    // Timeout Occurred
                    // Your Logic To Handle Timeout
                }
            }
            catch (Exception)
            {

                throw;
            }
            finally
            {
                Monitor.Exit(_SendLockToken);
            }
        }
        #endregion

        #region Private Methods

        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            // When ACK is received call SET
            _SendWaitHandle.Set();

        }
        #endregion
    }
}

Note: In DataReceived method, make sure that you don't call the Write method directly/indirectly, as this could result in a DeadLock. Always start processing the received data on a different thread using BackgroundWorker or by the using the TPL.

like image 37
Anand Murali Avatar answered Oct 29 '22 21:10

Anand Murali