Sending data with server.write() and recieving with a socket written in c

Hello Arduino community :slight_smile:

I'm trying to send values of type int32_t, measured by an ADC on a microcontroller, with server.write() through an ethernet connection. The connection works totally fine and transmitting char arrays, for example "Hello", works perfectly. I have trouble sending and/or recieving or maybe converting numbers. The values of type int32_t contain 4 numbers.

Until now my attempt to send looks like this (the variable num is normally set by the ADC):

int32_t num = 1328;
server.write((char*) &num, 4);

On the recieving computer the code looks like:

char buffer[64];
r = recv(socket, buffer, 64, 0);
buffer[r] = '\0'; //terminating the string with a null
//printing out the array of course gives me symbols and letters

But how can I correctly convert the garbage I get back to a useful value, like 1328, which i can save in a int variable? Can I do some kind of cast?

You have to know how those 4 bytes are arranged on the sender side and receiver side. Depending on what that is, those values could be big endian or little endian so you will have to manipulate the ordering.

https://www.tutorialspoint.com/unix_sockets/network_byte_orders.htm

Not sure if those functions are part of the standard and available for arduino.

1 Like

Welcome

If the values are always 4 digits, then it can fit in an int16_t

You could send 2 bytes, like so

int16_t num = 1328;
server.write(num);      // send low byte
server.write(num >> 8); // send high byte

And receive with something like this:

int16_t num = (uint8_t)buffer[1] << 8 | (uint8_t)buffer[0];

Of course you can do the same thing with 4 bytes too if you really need a int32_t, I prefer using the least amount of data whenever possible..

But before doing that, you should check the return value of recv, it can be an error, it can be 0..

Note, if you prefer using strings, maybe there is a function server.print, which would convert the number to an ASCII string.

1 Like

You don't know how many bytes you are going to get. The call to recv might return 3 bytes only, or it might return parts of multiple messages. You have to synchronize this in some way.

What exactly do you mean by synchronizing it in some way? :thinking:
Wouldn't this already have been a problem while transmitting a char array filled with letters?

Thank you, I'll take a look at it and it sounds like I have to keep that in mind :slight_smile:

Thank you, I'll try that. I want to keep the network traffic as small as possible, so I wouldn't prefer server.print. It's just that server.write only accepts byte or char.

Maybe terminating the string with a null isn't that smart, isn't it? If the ADC stores a value with a zero at the end it could be troubling I think.

Let's say that you send two messages of four bytes:

A1 A2 A3 A4    B1 B2 B3 B4

It is possible that the first call to recv returns A1 A2 and the next call returns A3 A4 B1 B2 B3 B4. The code you posted does not behave correctly in this scenario (ignoring the data encoding problems mentioned by guix).

The data you receive is not a string, so you shouldn't null-terminate it. It is still a 32-bit integer and should be treated as such on the receiver (taking into account framing and byte order).

Once you have figured out the framing, use something like this:

int32_t received = ((uint32_t) buffer[0] << 0) 
                 | ((uint32_t) buffer[1] << 8)
                 | ((uint32_t) buffer[2] << 16)
                 | ((uint32_t) buffer[3] << 24);

The order of the bytes will depend on the Endianness of the sender, you should probably use network byte order, see the previous replies.

Also, if recv returns 64, you're writing the null terminator out of the bounds of your buffer.

Edit: fixed shift offsets, thanks blh64.

1 Like

Thank you very much.
I think I start to understand the first problem. This seems like a tough one.

So recieved contains the shifted bytes from the buffer which are casted to uint32_t?

Also, if recv returns 64, you're writing the null terminator out of the bounds of your buffer.

So I would cut out whatever stands at the end and replace it with a 0 at the moment. Ok, thanks :slight_smile:

It does require some code to keep track of the offset, yes.
In ideal scenarios, you'll probably always receive complete packets, especially if they're only four bytes long and if everything happens on the local networ, but in general you don't really know how the messages are segmented, for example, see Packet segmentation - Wikipedia.

Yes, you first have to make sure that buffer points to the beginning of a message.

But then you lose part of your message. Like I said, you shouldn't null-terminate anything here since you're dealing with binary data, not text. But if you did, I'd probably make the buffer 65 bytes long in that case, and only allow recv to write at most 64 bytes.

Thanks, I've learned a lot from all the answers here. I'll try to solve the problem now and post a solution if it works.

int32_t received = ((uint32_t) buffer[0] << 0) 
                 | ((uint32_t) buffer[1] << 8)
                 | ((uint32_t) buffer[2] << 16)
                 | ((uint32_t) buffer[3] << 24);
2 Likes

Guess that means it's time for me to go to bed, thanks!

Today's update:

On the sending side, this works fine at the moment:

server.write((char*) &num, sizeof(num));

On the recieving side the suggested solution from PieterP and guix works fine too:

int32_t received = ((uint32_t) buffer[0] << 0) 
                 | ((uint32_t) buffer[1] << 8)
                 | ((uint32_t) buffer[2] << 16)
                 | ((uint32_t) buffer[3] << 24);

But what exactly are these lines doing? They shift the contents of the buffer[ ] n+8bits left. A left shift operation is a multiplication if I've read about this correctly. But why is the shift operation needed?

another technique you can implement is to encode your binary data into base64.

Base64 is a group of similar binary-to-text encoding schemes that represent binary data in an ASCII string format by translating it into a radix-64 representation. The term Base64 originates from a specific MIME content transfer encoding.

Base64 encoding schemes are commonly used when there is a need to encode binary data that needs to be stored and transferred over media that are designed to deal with textual data. This is to ensure that the data remain intact without modification during transport. Base64 is commonly used in a number of applications including email via MIME, and storing complex data in XML.

Your understanding of the left shift operator is correct. These shifts are needed to put the bytes back into the correct order.

Imagine you have the value 0x12345678 stored in your int32_t. Let's call those bytes A0 A1 A2 A3. The sender MCU could store those bytes in any order, depending on big or little endian architectures so they may be transmitted A0A1A2A3 or A3A2A1A0 or even A2A3A1A0. Since your receiving part gets the correct number, the sender order is A3A2A1A0 (little endian).

The receiver just has to put those bytes back into the proper order.

In this example 0x12345678 get sent as 0x78, 0x56, 0x34, 0x12 and the receiver re-assembles it as
0x78 << 0 = 0x78
0x56 << 8 = 0x5600
0x34 << 16 = 0x340000
0x12 << 24 = 0x12000000
which sums up to 0x12345678 [note: ORing the values together is the same as adding them since no bits overlap]

1 Like

Thank you all for your help. The code works great for higher numbers like 380 or 1360 but if I send a 210, I get -46 on the recieving side. Why does 380 work and 210 doesn't? Also how do I get a -46 as an unsigned int? Edit: 985 didn't work either.

Change your 'int32_t' to 'uint32_t' Your receiver is not using an unsigned int. It looks like your byte order is not correct. Same goes for your sender. Do a little detective work on your sender

uint32_t num = 0x12345678;
byte buffer[4];
byte *ptr = (byte *) &num;
for ( int i=0; i < 4; ++i ) Serial.print(*ptr++, HEX);

Okay, I'll experiment a little bit :slightly_smiling_face:
A little bit confusing to me is, that my c compiler prints errors if I use uint32_t in the socket program on the recieving computer. I've used unsigned int so far.