LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

C++ Client to read LabVIEW Server data

LabVIEW has a few examples for using TCP to send data from a server to a client. However, I am trying to find an example where the data is generated by the LabVIEW Server but then it is read by a client that is written in C++ (C and python would be acceptable too, although less preferred). I want to get that confidence and proof that the data I am generating can be read and visualized with a script based language too, not only a LabVIEW client.

The scenario would be the following: I run a program that collects data from a sensor. I want to send this data over internet using the TCP protocol. Ideally, I would like to broadcast the data 'non-stop' over the network so any client that starts/connects at any time can request and receive data. Based on my LabVIEW knowledge to date, my plan is to create a 'server application' as in the attached example (this is just  a mock-up, that posts random numbers). If I use a LabVIEW client similar to the examples provided by NI, the client receives the data and the plan works as intended (although I only tested it with the server and client on the same PC so far). In order to tests the actual situation I am going to face, I would need a client that is not created with LabVIEW, but rather with C++ (C and python would be acceptable too, but it is really C++ my customer would use). Unfortunately, I do not have the necessary experience with C++, therefore I am reaching out to the community for help. What I would like to achieve for my testing purposes is the following: start the LabVIEW - based server application,  then run the C++ client on the same or different PC from the network, and see that it reads the data from the LabVIEW server (as in seeing data scrolling on the screen for example). The server can start and stop at any time and same with the client; they should both run independent of each other (unlike the example "Simple TCP.lvproj" where if either program stops, the other stops as well).

(I am using LabVIEW 2019 SP1)

0 Kudos
Message 1 of 21
(5,436 Views)

There is no real magic in the binary format of LabVIEW. LabVIEW being developed itself in C/C++ uses the same data types and memory representations as is standard in C. And the datatypes map pretty much 1:1 between them.

 

There are a few specialities that come from differences between CPUs, OSes and what else. LabVIEW being a multiplatform software had to standardize on them and so when you Flatten data to a binary stream there are two main factors.

 

The first is endianes. LabVIEW by default uses Big Endian format, but the Flatten to String (really should be rather Flatten to Byte Array) has nowadays a selector that lets you select between Big Endian and Little Endian format.

 

If you want to match a specific remote side you will need to know what Endianes it has. Most software running nowadays on Intel hardware (or ARM which theoretically allows both Endian models but usually uses Little Endian as it is what is used on Intel x86/64 architecture) is likely going to be Little Endian. If you want to be truly platform independent, there is network byte order which is Big Endian.

 

The other is how the data is packed in the memory stream. LabVIEW uses byte packing, meaning it does NOT add filler bytes to the data stream to align elements on their natural byte address. This has mostly historical reasons, as memory was a scarce commodity back in the days and byte packing could make complex data structures quite a bit smaller. There is another aspect to byte packing: You can always simulate specific alignments by adding manually filler elements to the data structures to achieve a specific alignment but you can NOT simulate byte packing on a system that forces on you a specific alignment. So the byte packing is the most flexible and versatile format possible.

 

Now how to match LabVIEW data to C? I seldom have come across this problem, usually it is the other way around where I have some existing C/C++/Python/Whatever software that provides a certain byte stream format and I have to match it in LabVIEW.

 

The principle is however the same. For the scalar types you want to use the explicit C99/C++11 data types such as int32_t  and friends which happen to match exactly with the corresponding LabVIEW integer data types. A LabVIEW boolean is an uint8_t, which is "usually" similar to the C++ bool type, however C++ as so often, does not guarantee a specific size for its basic types so it is safer to use explicitly a uint8_t here.

LabVIEW floating point types match directly to the according C floating point types double and single. Don't use extended floating point, they are not supported on many modern platforms anyhow including x64.

A flattened LabVIEW cluster is similar to a struct with the #pragma pack(1) applied.

 

And last but not least there are arrays (and strings which are simply also an array of unsigned chars). In the flattened form they are a number of int32_t that corresponds with the number of dimensions, followed directly by the array data flattened into a byte array too.

 

That's pretty much it. Abstain from using LabVIEW specific types such as refnums, Variants and their siblings like Waveforms etc. Their format is difficult to match in C and prone to subtle differences from little changes in the LabVIEW diagram that you may not recognize as having any effect on the binary format.

 

Also timestamps, while theoretically possible to interpret in C, are a little cumbersome.

 

 

 

typedef struct
{
    int64_t seconds;
    uint64_t fractional;
} LVTimeStamp;

 

 

 

Unless you are familiar with fixed point arithmetic data types it is a bit of a struggle to deal with them. Also you need to be aware that the reference for them is January 1, 1904 00:00 GMT.

 

It's usually easier to convert them on the LabVIEW side to a double and subtract 2082844800.0 from them (if your C(++) runtime epoch is January 1, 1970 00:00 UTC) and pass them as a double (or as int32_t which is often used in older C runtimes but prone to the Year 2038 problem). Most modern C runtimes use int64 as time_t nowadays but you need to be aware that there are possible differences if you use different C(++) compilers. Irrespective of what the C(++) runtime uses it should do the right conversion when you pass the LabVIEW calculated double to a C runtime function expecting a time_t (with the caveat that you need to check your time_t epoch to be January 1, 1970 00:00 UTC for your C(++) compiler and if it is different adjust for that on your C(++) side.

 

As far as differences between C and C++, this should not matter on this level, you are not using any C++ specific things here. And I would advise to abstain from using bool as boolean datatype when interpreting the data coming from LabVIEW since bool does not guarantee a specific memory size. As far as the C++ standard is concerned a C++ compiler is absolutely in his right to use a 4 bit nibble to implement bool, if the underlying hardware has efficient support for that. Or it could use a 32 bit or any other datatype it prefers to hold a bool in, if the hardware it targets has more efficient support for boolean operators on 32 bit memory addresses for instance than on a byte address. A lot of this may sound academic, and it is for most mainstream compilers who seldom target anything else than x86/64, or ARM nowadays, but it is far from impossible and may be an issue in a few years from now when some new HyperSuper CPU architecture takes the world by storm.

 

This document describes the in memory format of LabVIEW data types quite extensively and the flattened byte stream format follows this model exactly with the following exceptions:

 

- Data in clusters is byte packed to match the most restrictive format for the Windows 32-bit platform.

 

- Extended floating point is always stored as 128 bit format. But this has little usage nowadays, most modern platforms including x64 do not support Extended floating point at all without special software libraries.

 

- The flattened Path format is undocumented, so don't pass that in a flattened byte stream. While it has stayed the same since its inception in LabVIEW 2.x there is no guarantee that it will stay that way, for instance when NI decides to add Unicode support to the Path data type, which is due for over a decade.

 

- Don't try to pass refnums. They are usually 32-bit magic numbers which only have a meaning inside the LabVIEW context they were created in. Even passing them to a different LabVIEW context is totally meaningless.

 

- Don't use Variants and the various Waveform data types if the remote side is ever expected to be anything else than LabVIEW. Their memory and flattened format is undocumented and prone to changes by small changes to the LabVIEW diagram creating them and a continuous uphill battle to keep working if you decide to reverse engineer a specific format.

 

Since you don't have much C(++) experience you are going to be in a bit of a bind here. If I would have to do it I would maybe create a simple C application but in the first place I would simply create a standard C header file for the client to use which describes the actual data structures to use in his own software. If he then wants to use C or C++ for his programming is fully up to him but pretty much irrelevant for the problem at hand. If he insists on fully working C++ code as example, I'm afraid he doesn't really understand the problem himself and I would expect (and calculate in my offer) a serious extra effort to support them implementing their C(++) side of things.

 


@RPJ wrote:

The server can start and stop at any time and same with the client; they should both run independent of each other (unlike the example "Simple TCP.lvproj" where if either program stops, the other stops as well).


That's not how TCP/IP works! The client is the consumer who can connect to a running server (listener). If the listener doesn't exist at the expected IP address and port number there is nothing it can do in terms of TCP/IP protocol to make it start up. It needs some other higher level means to request such a server to start up and that is totally implementation specific. TCP/IP does not provide any mechanism for that and it is generally not implemented that way but the client rather keeps trying to connect until it succeeds or is stopped. 

Rolf Kalbermatter  My Blog
DEMO, Electronic and Mechanical Support department, room 36.LB00.390
Message 2 of 21
(5,387 Views)

With your simple example you have (which is truly to simple and not really representative of anything) you would do something like this (excluding all the socket handling from establishing a connection and initializing specific socket attributes):

 

int __cdecl main()
{
    WSADATA wsaData;
    int iResult;

    SOCKET ConnectSocket = INVALID_SOCKET;
    struct sockaddr_in clientService; 

    char recvbuf[DEFAULT_BUFLEN];
    int recvbuflen = DEFAULT_BUFLEN;
  
    //----------------------
    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != NO_ERROR)
    {
      printf("WSAStartup failed: %d\n", iResult);
      return 1;
    }

    //----------------------
    // Create a SOCKET for connecting to server
    ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (ConnectSocket == INVALID_SOCKET)
    {
        printf("Error at socket(): %ld\n", WSAGetLastError() );
        WSACleanup();
        return 1;
    }

    //----------------------
    // The sockaddr_in structure specifies the address family,
    // IP address, and port of the server to be connected to.
    clientService.sin_family = AF_INET;
    clientService.sin_addr.s_addr = inet_addr("127.0.0.1");
    clientService.sin_port = htons(6341);

    //----------------------
    // Connect to server.
    iResult = connect( ConnectSocket, (SOCKADDR*) &clientService, sizeof(clientService) );
    if ( iResult == SOCKET_ERROR)
    {
        closesocket (ConnectSocket);
        printf("Unable to connect to server: %ld\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }

    // Receive until the peer closes the connection
    do
    {
        int32_t size = 0;
        iResult = recv(ConnectSocket, (char*)&size, sizeof(int32_t), MSG_WAITALL);
        if (iResult > 0)
        {
            // Convert LabVIEW network byte order format to native
            size = ntohl(size);
            printf("Length received: %d, %d\n", iResult, size);
            iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0); 
            if (iResult > 0)
            {
                printf("Data received: %s\n", recvbuf);
            }
            else if ( iResult == 0 )
                printf("Connection closed\n");
            else
                printf("recv failed: %d\n", WSAGetLastError());
        }
        else if ( iResult == 0 )
            printf("Connection closed\n");
        else
            printf("recv failed: %d\n", WSAGetLastError());
    } while (iResult > 0);

    // Send the quite message
    iResult = send( ConnectSocket, "Q", 1, 0 );
    if (iResult == SOCKET_ERROR)
    {
        printf("send failed: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }
    printf("Bytes Sent: %ld\n", iResult);

    // cleanup
    closesocket(ConnectSocket);
    WSACleanup();

    return 0;
}

 

 

If you now think, Jesus this is involved, that's because yes socket programming in C is involved. And 99% of the code above has absolutely nothing to do with LabVIEW being on the other side.

Rolf Kalbermatter  My Blog
DEMO, Electronic and Mechanical Support department, room 36.LB00.390
Message 3 of 21
(5,361 Views)

Jesus, this is involved ! ! !

Thank you for your time, and thorough and quick response! (it is going to take a bit to have all the details sink in...).

 

I tried to run your C code in Visual Studio 2017 but I ended up with a bunch of errors. I concluded I must be doing the wrong thing. I am attaching a screenshot. Perhaps you can provide some guidance on how/where I should run the program to make it work ?

 

 

0 Kudos
Message 4 of 21
(5,344 Views)

This was not meant as a ready made software. I was just typing it up here without ever trying to compile it. For one it misses all the include statements for defining the different data types and function prototypes.

 

You’ll have to add at least an include for wsock32.h, stdlib.h and likely a few more. And I would be surprised if there wasn’t any typo somewhere!

Rolf Kalbermatter  My Blog
DEMO, Electronic and Mechanical Support department, room 36.LB00.390
Message 5 of 21
(5,334 Views)

OK - thank you for clarifying. I am providing kudos for your efforts and support.

That being said, ... looks like I'm going to be stuck...I have no idea how and what to add to make things work. I will check to see if one of my friends with C programming background can help.

In the meantime, if anyone else has suggestions, they are welcome.

 

Happy 4th of July and be safe!

0 Kudos
Message 6 of 21
(5,319 Views)

One option is RabbitMQ, it's a TCP/IP queue system that's easily available on both platforms.

G# - Award winning reference based OOP for LV, for free! - Qestit VIPM GitHub

Qestit Systems
Certified-LabVIEW-Developer
0 Kudos
Message 7 of 21
(5,261 Views)

To clarify (or at least provide more information):

Regarding the LabVIEW program I am creating, the plan is to have it running non-stop (or quasi non-stop). A program, which I call the 'client', is either already started or will start afterwards, whenever the PC the client is on starts, and its role is to read the string (data) my LabVIEW program is outputting/'transmitting' over TCP, then convert that data to a network-ready format and make it available on the network (can be an internal network or internet). I learned that the program I call 'client'  is actually a web server. However, looking at the examples that come with LabVIEW, this web server acts as a 'client' for the LabVIEW program I create - this is why I call it client (see the clients in "Simple TCP.lvproj" and "TCP Multipe Connections.lvproj")

 

When I create my LabVIEW program, besides making sure I am collecting the necessary data etc., I need to make sure I am creating the capability of transmitting that data over TCP to this 'client'/web server. The only good way for me to verify this works as intended would be to have a C++ or C program that has the capability to read the data from my LabVIEW program. This way I will know the data I am outputting (writing to TCP) will be readable by this client/web server.

 

I need a C, C++ or script program able to read through TCP the data provided by the LabVIEW program.

Suggesting RabbitMQ is appreciated but in the same time... frustrating: I do not have the time to learn an entirely new 'thing' without even knowing how it would relate to my goal. (Therefore, I cannot provide kudos...)\

 

I need help with 1) a simple C++ or C based program capable of reading data from a "LabVIEW server program" as the one I attached or the ones in the LabVIEW help examples, and, 2) in the process, I hope to better understand and learn about this communication over Ethernet using TCP.

 

Can anyone help with something more precise?

0 Kudos
Message 8 of 21
(5,216 Views)

To Rolf: I thought it is as "easy" as writing a string to "the network" through TCP, and the other program reading it from "the network". At the end of the day it is "only" a string format data... Shouldn't the C or C++ program basically 'mimic' what the LabVIEW clients do? (I am guessing you are going to say yes but this 'mimicking' is more complicated than it seems)

Is the LabVIEW string data "different"? Isn't this "all standardized"? 

 

0 Kudos
Message 9 of 21
(5,213 Views)

Well, you usually do not just want to have a simple string you want to transfer. Sooner or later things get more complex. Data (arrays), configuration settings, etc. etc. needs to be transfered.

 

Even if you just transfer a string, there are various methods to transfer that over a network connection. It's not about LabVIEW or not LabVIEW, but about what the conditions are. Just writing the string to the wire makes it very hard to recieve it properly. When do you know that you received the entire string and not just a fraction of it? TCP/IP being a stream protocol does not guarantee datagram consistency, meaning that if you send two packets with 20 bytes each, that the other side will not always  see this as two packets of 20 byte each. TCP/IP could split that in a packet of 15 bytes, another of 10 bytes and then again one of 15 bytes (Theoretically, in practice the TCP/IP frame size is usually at least 1000 to 1500 bytes, but as a principle this is still true). Or more likely combine it into a single packet of 40 bytes. So you need to add some extra information. Either you prepend the data with a header that indicates how many bytes of data follow, or you append each string with a specific termination character (sequence) such as carriage return/line feed. Now the receiver can properly verify that it received the full string.

 

This is all totally independent of LabVIEW and simply the basic of every byte stream protocol.

 

Your problem is the lack of familiarity with how network data communication is generally handled. There is nothing inherently tricky about LabVIEW to C (or C to LabVIEW) data transmission since LabVIEW uses in fact a lot of C paradigmas in that respect. In fact their might be a few LabVIEW specific details, but ABSOLUTELY nothing that can not be handled in C since the LabVIEW paradigmas are somewhat stricter than what one can do in C.

 

You should first concentrate on getting your data transfer working at all and if that is from LabVIEW to LabVIEW, so be it. Once that works you can tackle the problem of documenting the memory byte layout of the data on the wire. If your client requires the data to be received in a C(++) software and doesn't have the expertise to do this themselves from a byte protocol description you should not even attempt to do this project with your knowledge about C programming. It simply never will work and you and your client will end up pointing fingers at each other for failing this project.

Rolf Kalbermatter  My Blog
DEMO, Electronic and Mechanical Support department, room 36.LB00.390
0 Kudos
Message 10 of 21
(5,203 Views)