Industrial Communications

cancel
Showing results for 
Search instead for 
Did you mean: 

Communicating with a .NET Windows desktop app over TCP/IP SOCKET by LabView/VISA

Hello LabView/VISA experts,

 

I'm writing a .NET Windows desktop app that needs to communicate with LabView/VISA over TCP/IP SOCKET. Because I don't own a copy of LabView, I'm using NI MAX's VISA Test Panel to test the app. The app works just fine when NI MAX is running on a different computer - i.e., communicating over a real Ethernet link. However, if both are placed on the same machine, NI MAX sometimes fails to open the socket resource, especially for the first time. Retrying it seems to always work, though. Once the resource is connected, everything works just fine.

 

Does anyone know why this happens? Is there a way to completely avoid the failure opening it?

 

Below shows the socket server code:

namespace Remote
{
    using System;
    using System.Diagnostics;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;

    public class TcpSocketServer
    {
        private readonly IScpiProcessor scpiProcessor;

        private Socket serverSocket;

        public TcpSocketServer(IScpiProcessor scpiProcessor)
        {
            this.scpiProcessor = scpiProcessor;
        }

        public string Ipv4Address { get; set; } = null;

        public ushort Port { get; set; } = 7777;

        public string Terminator { get; set; } = "\n";

        public short BufferSize { get; set; } = 1024;

        public static IPAddress GetIpv4Address()
        {
            var ipHost = Dns.GetHostEntry(Dns.GetHostName());
            foreach (var result in ipHost.AddressList)
            {
                // VISA supports IPv4 only.
                if (result.AddressFamily == AddressFamily.InterNetwork)
                {
                    return result;
                }
            }

            throw new NotSupportedException("No IPv4 address is available on this machine.");
        }

        public void Start()
        {
            if (this.serverSocket != null)
            {
                return;
            }

            if (!TryParseIpv4Address(this.Ipv4Address, out var ipAddress))
            {
                // Use this machine's Ipv4 address when no valid Ipv4 address is set.
                ipAddress = GetIpv4Address();
            }

            var ipEndPoint = new IPEndPoint(ipAddress, this.Port);
            this.serverSocket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            this.serverSocket.Bind(ipEndPoint);
            this.serverSocket.Listen(100);
            this.serverSocket.BeginAccept(this.AcceptCallback, this.serverSocket);
        }

        public void Stop()
        {
            this.serverSocket?.Dispose();
            this.serverSocket = null;
        }

        private static bool TryParseIpv4Address(string input, out IPAddress ipAddress)
        {
            // Make sure it contains three dots that represent Ipv4, rather than Ipv6.
            if (input == null || input.Count(c => c == '.') != 3)
            {
                ipAddress = null;
                return false;
            }

            var result = IPAddress.TryParse(input, out ipAddress);
            return result;
        }

        private void AcceptCallback(IAsyncResult ar)
        {
            Debug.WriteLine("Entering AcceptCallback().");
            StateObject state = null;
            try
            {
                var listener = (Socket)ar.AsyncState;
                var socket = listener.EndAccept(ar);
                state = new StateObject(socket, this.BufferSize);
                state.Socket.BeginReceive(
                    state.Buffer,
                    0,
                    state.Buffer.Length,
                    SocketFlags.None,
                    this.ReceiveCallback,
                    state);
                this.serverSocket?.BeginAccept(this.AcceptCallback, this.serverSocket);
            }
            catch (ObjectDisposedException)
            {
                Debug.WriteLine("TCPIP server socket is disposed properly.");
            }
            catch (SocketException ex)
            {
                Debug.WriteLine($"{ex.GetType()} in {nameof(this.AcceptCallback)}");
                state?.Socket?.Close();
                this.serverSocket?.BeginAccept(this.AcceptCallback, this.serverSocket);
            }
        }

        private void ReceiveCallback(IAsyncResult ar)
        {
            Debug.WriteLine("Entering ReceiveCallback().");
            var state = (StateObject)ar.AsyncState;

            try
            {
                this.DoReceiveCallback(ar, state);
            }
            catch (SocketException ex)
            {
                Debug.WriteLine($"{ex.GetType()} in {nameof(this.ReceiveCallback)}");
                state.Socket?.Close();
            }
        }

        private void DoReceiveCallback(IAsyncResult ar, StateObject state)
        {
            var bytesRead = state.Socket.EndReceive(ar);
            Debug.WriteLine($"bytesRead = {bytesRead}");
            if (bytesRead > 0)
            {
                this.ProcessReceivedMessage(state, bytesRead);

                state.Socket.BeginReceive(
                    state.Buffer,
                    0,
                    state.Buffer.Length,
                    SocketFlags.None,
                    this.ReceiveCallback,
                    state);
            }
            else
            {
                state.Socket.Close();
            }
        }

        private void ProcessReceivedMessage(StateObject state, int bytesRead)
        {
            var s = Encoding.ASCII.GetString(state.Buffer, 0, bytesRead);
            Debug.WriteLine($"Received: {s}");
            state.StringBuilder.Append(s);
            var accumulatedString = state.StringBuilder.ToString();
            if (accumulatedString.Contains(this.Terminator))
            {
                this.ProcessMessage(state);
            }
        }

        private void ProcessMessage(StateObject state)
        {
            // Note that accumulated string may contain multiple terminators depending on the timing.
            var response = this.scpiProcessor.Process(state.StringBuilder.ToString());
            state.StringBuilder.Clear();
            if (!string.IsNullOrEmpty(response))
            {
                this.Send(response, state.Socket);
            }
        }

        private void Send(string message, Socket socket)
        {
            var byteData = Encoding.ASCII.GetBytes(message + Environment.NewLine);
            if (socket.Connected)
            {
                Debug.WriteLine($"Sending {message}");
                lock (socket)
                {
                    socket.Send(byteData, byteData.Length, SocketFlags.None);
                    Debug.WriteLine($"Sent {message}");
                }
            }
            else
            {
                Debug.WriteLine($"Cannot send {message}");
            }
        }

        private class StateObject
        {
            public StateObject(Socket socket, short bufferSize)
            {
                this.Socket = socket;
                this.Buffer = new byte[bufferSize];
                this.StringBuilder = new StringBuilder();
            }

            public Socket Socket { get; }

            public byte[] Buffer { get; }

            public StringBuilder StringBuilder { get; }
        }
    }
}

It requires a dependency called IScpiProcessor that processes SCPI commands and queries. Below shows the interface and its fake implementation:

namespace Remote
{
    public interface IScpiProcessor
    {
        string Process(string content);
    }

public class FakeScpiProcessor : IScpiProcessor
{
public string Process(string content)
{
return content.ToUpperInvariant().Contains("*IDN?") ? "Hello world!" : string.Empty;
}
} }

Below shows my unit test code that starts the server and access it via IMessageBasedSession:

namespace Remote.Test
{
    using System;
    using Ivi.Visa;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Remote;

    [TestClass]
    public class SocketServerTest
    {
        private const string StarIdnQuery = "*IDN?";
        private const string ExpectedIdnResponse = "Hello world!";
        private static TcpSocketServer server;

        [ClassInitialize]
        public static void StartServer(TestContext testContext)
        {
            var scpiProcessor = new FakeScpiProcessor();
            server = new TcpSocketServer(scpiProcessor);
            server.Start();
        }

        [ClassCleanup]
        public static void StopServer()
        {
            server.Stop();
        }

        [TestMethod]
        public void ShouldOpenSession()
        {
            Exception exception = null;
            try
            {
                var session = this.OpenSession();
                session.Dispose();
            }
            catch (Exception ex)
            {
                exception = ex;
            }

            Assert.IsNull(exception);
        }

        [TestMethod]
        public void ShouldIdentify()
        {
            var session = this.OpenSession();
            session.FormattedIO.WriteLine("*Idn?");
            var response = session.FormattedIO.ReadLine();
            response = response.TrimEnd('\r', '\n');
            session.Dispose();

            // Assert
            Assert.AreEqual(ExpectedIdnResponse, response);
        }

        [TestMethod]
        public void ShouldIdentify2()
        {
            var session = this.OpenSession();

            session.FormattedIO.WriteLine("*Idn?");
            var response = session.FormattedIO.ReadLine();
            response = response.TrimEnd('\r', '\n');
            Assert.AreEqual(ExpectedIdnResponse, response);

            session.FormattedIO.WriteLine("*IDN?");
            response = session.FormattedIO.ReadLine();
            response = response.TrimEnd('\r', '\n');
            Assert.AreEqual(ExpectedIdnResponse, response);

            session.Dispose();
        }

        private IMessageBasedSession OpenSession()
        {
            var ipAddress = TcpSocketServer.GetIpv4Address().ToString();
            var resourceName = $"TCPIP0::{ipAddress}::7777::SOCKET";
            var session = (ITcpipSocketSession)GlobalResourceManager.Open(resourceName, AccessModes.None, 0);
            session.TimeoutMilliseconds = 2000;
            session.TerminationCharacter = 0x0a;
            session.TerminationCharacterEnabled = true;

            return session;
        }
    }
}

Interestingly, the unit tests never fail to open the session and always pass, even though the client and server are running on the same machine.

 

When testing the code with NI MAX, make sure you check the "Enable Termination Character" setting in the VISA Test Panel.

 

I'm using .NET V4.7.1, NI VISA V17.0, and IVI.Visa V5.8.0.

 

I'd appreciate any suggestions.

 

Thanks,

 

Tetsu Shimizu

0 Kudos
Message 1 of 1
(2,469 Views)