NAV 2016: How to read from a local client stream

Dear all,

I'm working on an interface to an electronic scale. I'm communicating with the scale via an online stream to a IP address and port no.

I was planning to the tcpClient and tcpListener dotnet functions to read and write to the stream. So far so good.

 

The problem that appears is that NAV is running this on the server, while the IP/port is only available from the client, as the scale is attached via the local USB port. So the communication needs to happen locally on the client.

Do you know if this is possible?

  • Hi Erik,

    Each Dotnet variable has a properties "RunOnClient". If this property is set to yes, navision will read(excecuted) from local not from server.

  • In reply to Xhevat Tafaj:

    Thanks a lot .  Smile

    I believe that you're right, it may be the right direction to get it to work.

     

    It just didn't work. At least not until I can overcome the next error:

    The server "net.tcp://server/instance/Service" is either unavailable or your connection has been lost.
    Do you want to attempt to reconnect?

     

    Wondering if the RunOnClient should be for all the DotNet vars in the function?

  • In reply to Erik P. Ernst:

    Changing to RunOnClient in all the DotNet's is giving a new error:

    A call to System.Net.Sockets.TcpListener.Start failed with this message: Adressen, der blev anmodet om, er ikke gyldig i sin kontekst (the requested address is not valid in its context).

    What I have found out so far, is that it means the address does not exist from where the program runs. RunOnClient should have made it run there and then it would work. But it seems that it still is not really calling it, in the content of the server.

     

  • In reply to Erik P. Ernst:

    Hi Erik,

    I'm not sure if this helps, but in this example of retrieving data from an external source (a Windows Dialog in that case), all DotNet vars are defined as RunOnClient.

    In your case, the NAV client plays the role of the server (TCP listener), is this correct? While the electronic scale is the TCP client. That is, the communication is only initiated by the electronic scale (i.e. when it has to send some measurement to the computer). In this case, IMHO the original error message ("... is either unavailable or your connection has been lost.") has nothing to do with the actual TCP communication you are implementing, but with NAV communication internals between NAV server and NAV Windows Client. Is this correct?

     

    UPDATE (for the newer error message):

    How about wrapping all the communication logic in a single DLL, then use a single DotNet var in C/AL. And this single one var would be "RunOnClient".

    Also, do you think there are some permission issues? Opening a TCP Listener may require more privileges? How about the firewall? Does the computer have more Network Cards? Are they all configured correctly? Though, most probably it wouldn't matter, since perhaps the driver of the electronic scale... And right now I realise: isn't it the driver of the scale which should be the TCP server?  Why do you need a TCP listener?

    What is the lifespan of the TCP listener? When do you run the C/AL code that starts it? On a Page Load of something similar?

     

    This last paragraph only makes sense if there is an actual need of a TCP listener (and perhaps only as a last resort):

    If acceptable from a deployment point of view, a Windows Service can be installed on the computer and be the TCP listener itself. Then, it would update a file on the local file system when a new measurements arrives from the electronic scale. Then, when the NAV user requires the latest measurement data (say, to fill in a field on a page), a piece of C/AL code can read the contents of that file.

     

    [P.S.: Sorry for the many edits]

  • In reply to Andrew:

    Hi Andrew,
    Thank you for you answer.

    The first error was fixed by setting the all to RunOnClient.

    It doesn't have to be tcp listener, if you have a better suggestion.

    The way to communicate with the scale is through the stream.
    The stream is endless, and always transmit the weight (plus a few other numbers) - just as a listing of some simple text columns.
    In NAV I just need to display the current weight and select zero/tear (which records the weight to the record in NAV).
    To zero/tear the scale, then I can also submit simple command (eg. .x105 ) (with put I assume).

    And the reason for doing it in C/AL is just because that's what I know the best.
    Did consider writing it all in a C# - I'm just not as sharp to C#.
  • In reply to Erik P. Ernst:

    If it helps, I sketched a tiny simulator for the electronic scale and also sketched a .NET wrapper that may be called from C/AL. Assuming that the electronic scale plays the role of the server, and the NAV client connects to it whenever it wants to fetch the weight (or push some other data), then you do not need to use a TCP Listener IMHO. The listener is most probably implemented on the side of the scale, perhaps by the software driver that was shipped with the scale (you mentioned that it connects via USB, thus I assume somebody has to start a background process on the machine in order to open the TCP socket).

    Perhaps the scale has a higher level API like HTTP, though?

    I do not know the structure of the data provided by the scale, thus in my simulator example I use something like "WEIGHT;other data; other data" + a newline. If you provide more details, I can adjust the example.

    For testing the code, you can use LINQPad (instead of downloading and installing Visual Studio). Does the communication work? If it doesn't perhaps there are other issues that have to be investigated before actually integrating the code into NAV (C/AL).

    The scale simulator (server side):

    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using System.IO;
    
    namespace ScaleSimulator
    {
    	class MainClass
    	{
    		public static void Main(string[] args)
    		{
    			var server = new TcpListener(IPAddress.Any, 15000);
    
    			server.Start();
    
    			Console.WriteLine("Server is running.");
    
    			using (var client = server.AcceptTcpClient())
    			{
    				Console.WriteLine("We have a client!");
    
    				try
    				{
    					SendDataToClient(client);
    				}
    				catch
    				{
    					Console.WriteLine("Something happened.");
    				}
    			}
    
    			server.Stop();
    		}
    
    		static void SendDataToClient(TcpClient client)
    		{
    			var eternity = DateTime.Now + TimeSpan.FromSeconds(10);
    
    			using (var streamWriter = new StreamWriter(client.GetStream()))
    			{
    				streamWriter.AutoFlush = true;
    
    				while (DateTime.Now < eternity)
    				{
    					streamWriter.WriteLine("1.5;1.0;test");
    					Thread.Sleep(1000);
    				}
    			}
    		}
    	}
    }
    

    The client wrapper (to be called from C/AL):

    using System;
    using System.Net.Sockets;
    using System.IO;
    
    namespace ScaleClient
    {
    	public class ScaleClientWrapper
    	{
    		private string address;
    		private int port;
    
    		public ScaleClientWrapper(string address, int port)
    		{
    			this.address = address;
    			this.port = port;
    		}
    
    		public double GetWeight()
    		{
    			using (var client = new TcpClient(address, port))
    			{
    				using (var streamReader = new StreamReader(client.GetStream()))
    				{
    					var line = streamReader.ReadLine();
    					var weight = double.Parse(line.Split(';')[0]);
    
    					return weight;
    				}
    			}
    		}
    
    		public void Tear()
    		{
    			// TODO
    		}
    	}
    
    	class MainClass
    	{
    		// Example of using the wrapper.
    		// Delete before compiling into a DLL.
    		public static void Main(string[] args)
    		{
    			var wrapper = new ScaleClientWrapper("127.0.0.1", 15000);
    			var weight = wrapper.GetWeight();
    			Console.WriteLine(weight);
    		}
    	}
    }
    

  • In reply to Andrew:

    Hi Andrew,
    Thanks! But let me show you this part from the "streaming interface" document:

    "When the PC User Interface software is running, one can access the direct
    stream of weighing data from the MU1 module.
    The stream is available on port 52253 and the IP address of the MU1. The first
    MU1 which is plugged in the USB, will get the IP Address 169.254.1.2.
    To see the actual stream, one can use programs such as NetCat (nc) from the command line."

    And that's basically what I have to work from! No samples or anything! And no other API's.

    For me uncharted territory...

    Btw. one of the other MVP's (Erik Hougaard) told me that using tcpListener requires that the NAV client runs under the Administrator to work. Wonder if putting it into a wrapper would solve that issue?
  • In reply to Erik P. Ernst:

    Putting the code within a DLL wrapper would not solve the permission issue IMHO since the process itself has to be executed in elevated mode. However, the good thing is that most probably a tcpListener is not needed.

    Do you think it is possible to execute the following in the command prompt?
    telnet 169.254.1.2 52253

    We should see the data :)

    How does it look like?

    If we see the data and it is interpretable, then the C/AL code should do the following:

    • instantiate a TCP client with parameters ("169.254.1.2", 52253)
    • read from the stream (we shall see how, since we don't know yet whether data is binary or textual)
    • parse the weight and display it
  • In reply to Andrew:

    Hi Andrew,

    I think it helped with some sleep!

    All it really took to read from the stream was:

      tcpClient := tcpClient.TcpClient;
      tcpClient.Connect(IPno,Port);
      ClientStream := tcpClient.GetStream;
      IF ClientStream.CanRead THEN BEGIN
        Reader := Reader.StreamReader(ClientStream);
        ResultText := Reader.ReadLine;
      END;
      
      EXIT(ResultText);

     

    Who said "keep it simple"? Smile

    The ResultText is a simple text string that I just need to read from the right positions.

     

    Next phase is then also to send commands to the weight, but I am assuming I can just use StreamWriter in the same way.

  • In reply to Erik P. Ernst:

    Hi Erik,

    Glad to hear this :)
    Sending commands should work indeed the way you mentioned (a Flush() may be necessary though).
  • In reply to Erik P. Ernst:

    Hi Erik,
    Can we handle all type of weighing machine similarly?. Machines which are connected to Network or connected to local machine with USB?.

    If you can give the type of machine you have integrated successfully.

    Please share the objects, which will motivate me in trying something similar in NAV 2018.

    With Regards,
    Prajeesh Nair
  • In reply to Prajeesh:

    Hi Prajeesh,
    Well it depends on the brand and model I guess. So via
    This was an Icelandic company called Marel, the weight caled MU1 unit. But I no longer have access to the objects, so can't help you with that.
    It was mostly the call above, where I had to read and respond to a long text string.
    Although quite "flexible" then I would surely have preferred webservices/json instead.
  • In reply to Erik P. Ernst:

    Thank you Erik,

    I am planning for trying both the TCP automation as well as dll. Will keep you updated .

    With Regards,
Related