Mark G
Mark G

Reputation: 3126

Sharing a COM port over TCP

What would be a simple design pattern for sharing a COM port over TCP to multiple clients?

For example, a local GPS device that could transmit co-ordinates to remote hosts in realtime.

So I need a program that would open the serial port and accept multiple TCP connections like:

class Program
{
    public static void Main(string[] args)
    {
        SerialPort sp = new SerialPort("COM4", 19200, Parity.None, 8, StopBits.One); 

        Socket srv = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        srv.Bind(new IPEndPoint(IPAddress.Any, 8000));
        srv.Listen(20);

        while (true)
        {
            Socket soc = srv.Accept();
            new Connection(soc);
        }
    }
}

I would then need a class to handle the communication between connected clients, allowing them all to see the data and keeping it synchronized so client commands are received in sequence:

class Connection
{
    static object lck = new object();
    static List<Connection> cons = new List<Connection>();

    public Socket socket;
    public StreamReader reader;
    public StreamWriter writer;

    public Connection(Socket soc)
    {
        this.socket = soc;
        this.reader = new StreamReader(new NetworkStream(soc, false));
        this.writer = new StreamWriter(new NetworkStream(soc, true));
        new Thread(ClientLoop).Start();
    }

    void ClientLoop()
    {
        lock (lck)
        {
            connections.Add(this);
        }
        while (true)
        {
            lock (lck)
            {
                string line = reader.ReadLine();
                if (String.IsNullOrEmpty(line))
                    break;

                foreach (Connection con in cons)
                    con.writer.WriteLine(line);
            }
        }
        lock (lck)
        {
            cons.Remove(this);
            socket.Close();
        }
    }
}

The problem I'm struggling to resolve is how to facilitate communication between the SerialPort instance and the threads.

I'm not certain that the above code is the best way forward, so does anybody have another solution (the simpler the better)?

Upvotes: 3

Views: 4832

Answers (2)

user5583316
user5583316

Reputation:

you have socat and ser2net and other programs but my experience is very bad... not working properly. I've done this small python program, can be useful. Update port, baudrate... then use any tcp client. Remove first line if don't want to use is as auto executable script

#!/usr/bin/python

import socket
import sys
import serial

#open serial port
ser = serial.Serial('/dev/ttyAMA0', 115200, timeout=0)
#create socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)

#bond to the port. Don't use localhost to accept external connections
server_address = ('', 2105)
print('starting up on {} port {}'.format(*server_address))
sock.bind(server_address)

#listen
sock.listen(1)

#loop
while True:
    #waits for a new connection
    print('waiting for a connection')
    connection, client_address = sock.accept()
    try:
        print('connection from', client_address)
        #continously send from serial port to tcp and viceversa
        connection.settimeout(0.1)
        while True:
            try:
                data = connection.recv(16)
                if data == '': break
                ser.write(data)
            except KeyboardInterrupt:
                connection.close()
                sys.exit()
            except Exception as e:
                pass
            received_data = ser.read(ser.inWaiting())
            connection.sendall(received_data)
    except Exception as e:
        print e

    finally:
        #clean up connection
        connection.close()

Upvotes: 0

Ian Mercer
Ian Mercer

Reputation: 39307

Why write at such a low-level (sockets)? Why not use WCF as the communication between the clients and the server and present a cleaner, strongly-typed interface instead of raw access to the GPS device?

Devices like this are often best managed independently from the clients calling in - i.e. you have your own separate thread that talks to the GPS device, polling it at the appropriate interval and populating shared data structures with the current location - while the clients make service calls and are supplied with data from the shared data structures. All error handling and recovery for the sometimes unreliable device connection is handled by the GPS thread and the clients don't need to each get involved with such nastiness. They can make non-blocking calls to get status updates and those updates might include a status 'position unavailable' while the GPS thread is frantically trying to re-establish communication.

So I would create a service that abstracts the particulars of dealing with this specific device and provides a clean interface to the clients. It might for example offer a services like GetPosition() which returns some class like "GeoCoordinate". That way if you ever need to support other location sensing devices you can add them without making any changes to the client code.

                   GPS <--Serial--> Server <--WCF--> Clients

I have a system that communicates with hundreds of different devices, many over serial ports and other semi-reliable connections and this is the approach I use. See http://blog.abodit.com.

----- per your additional requirement to use TELNET: maybe something like:

Create a thread that handles all communication with the device itself.

Create a class that encapsulates a single WorkItem - what to send, the response, and a WaitHandle.

Use a Queue to queue up requests from clients. Each client waits on the WaitHandle for its response to be ready.

Let the single communication thread pull work items off that queue, send them to the GPS device, get the response, store the response in the WorkItem (or set a flag for failures), and then set the wait handle to say that the WorkItem is done.

If the requests come in faster than the GPS can handle, add code so it can return cached values for requests coming within a small time window from the last successful request to the device.

In effect you are now presenting a virtual GPS device to all the clients but internally you are serializing all their requests (on a Queue) and managing communication with the GPS device on a single thread so you can do the Request-Response cycle easily without interference.

This also allows you to time-out nicely (on the wait handle) to inform a client that no response is currently available.

Upvotes: 2

Related Questions