Link to home
Start Free TrialLog in
Avatar of Rocco Galati
Rocco Galati

asked on

How to send HEX commands in C/C++ over TCP/IP to control a relays board

I have an electronics board which has 16 relays and it works over TCP/IP.
The IP address of the board is 192.168.1.4 and the port is 3000.
I would like to control it with C/C++ under Ubuntu.

There is a list of HEX commands that it is possible to use in order to remotely switch ON and OFF each relay on the board.
This is the list:
"580112000000016C",  // switch on the relay 1

"580111000000016B",  // switch off the relay 1
"580112000000026D", // switch on the relay 2
"580111000000026C", // switch off the relay 2
"580112000000036E", // so on..
"580111000000036D",
"580112000000046F",
"580111000000046E",
"5801120000000570",
"580111000000056F",
"5801120000000671",
"5801110000000670",
"5801120000000772",
"5801110000000771",
"5801120000000873",
"5801110000000872",
"5801120000000974",
"5801110000000973",
"5801120000000A75",
"5801110000000A74",
"5801120000000B76",
"5801110000000B75",
"5801120000000C77",
"5801110000000C76",
"5801120000000D78",
"5801110000000D77",
"5801120000000E79",
"5801110000000E78",
"5801120000000F7A",
"5801110000000F79",
"580112000000107B",
"580111000000107A",
"5801130000FFFF77",
"580113000000007B",
"5801100000000069"

I'm correctly able to switch on and off each relay by sending command line commands under Ubuntu:

echo '580112000000016C' | xxd -r -p | nc 192.168.1.4 3000

Open in new window

The above code correctly turns on the relay.

I would like to do the same with C/C++ code since I want to control the board from a WxWidgets application.
For the moment, I'm starting from base and I'm just using C/C++ code in order to test the tcp connection.

This is my code:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <string>
#include <unistd.h>

using namespace std;

int main(int argc, char**argv) {
    int sockfd, n;
    struct sockaddr_in servaddr;
    char sendline[1000];
    char recvline[1000];

    std::string serveraddr = "192.168.1.4";

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(serveraddr.c_str());
    servaddr.sin_port = htons(3000);

    connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));

    int number = 0x580112000000016C;

    int sendsize;
    sendsize = snprintf(sendline, sizeof(sendline), "%x", number);

    send(sockfd, sendline, sendsize * sizeof(char), 0);

}

Open in new window


How should I send the HEX commands?
When I compile this code I get this warning:

g++ tcp.c -o tcp
tcp.c: In function ‘int main(int, char**)’:
tcp.c:29:18: warning: overflow in implicit constant conversion [-Woverflow]
     int number = 0x580112000000016C;
                  ^~~~~~~~~~~~~~~~~~

and obviously the program doesn't work.

Can you help me please? I'm a beginner and I'm not understanding how to correctly send the HEX instructions.
Avatar of Qlemo
Qlemo
Flag of Germany image

You have to send the hex digits as a string, like with your echo, instead of as integer. If you define your constant as char[], and just send that, it should work.
Avatar of Rocco Galati
Rocco Galati

ASKER

Thank you for your support.

I tried to do it in this way:

uint8_t buffer[] = { 0x58, 0x01, 0x12, 0x00, 0x00, 0x00, 0x01, 0x6C};
    int sendsize;
    sendsize = snprintf(sendline, sizeof(sendline), "%u", buffer);

    send(sockfd, sendline, sendsize * sizeof(char), 0);

Open in new window


but it still doesn't work.
6341369541523669356 is the integer equivalent of your HEX string, so will over flow almost any compiler's default int type.

As Qlemo said, almost all HEX commands sent are sent as character strings or are written to individual 8/16/32 bit registers.

To understand how best to accomplish this, refer to docs of your specific embedded device (electronics board).

If docs are incomplete, just pull the chip number from the chip closest to your incoming wiring + likely the manufacturer data sheet will provide required info.
I also tried to send it like a string:

char buffer[] ="580112000000016C";
    int sendsize;
    sendsize = snprintf(sendline, sizeof(sendline), "%s", buffer);

    send(sockfd, sendline, sendsize * sizeof(char), 0);

Open in new window


but nothing changed.
your echo command implicetly sends a \n character as well perhaps it is required.  just change

buffer to "580112000000016C\n";
to make a first test
Thank you for your help, gelonida.

I tried to add the \n, but it seems nothing changed.

char buffer[] ="580112000000016C\n";
    int sendsize;
    sendsize = snprintf(sendline, sizeof(sendline), "%s", buffer);

    send(sockfd, sendline, sendsize * sizeof(char), 0);
The control transmissions have to come sequentially or in a sequence,
Currently your example sends a sequence without checking whether it is expected.
Buffer handling on the other side in a blind charcter, string send might receive the first but miss the seei daily/subsequent.

Your working example uses xxd as the intermediary converter
Try to see what is output from that

echo '580112000000016C' | xxd -r -p | cat -v

Open in new window


See what it sends, I.e. '\0' or '^@' as terminator between control characters
This is the output:

echo '580112000000016C' | xxd -r -p | cat -v
X^A^R^@^@^@^Al

Can't understand why I get the Al terminal character.
cat -v tells the system to pass everything including the control characters.
^@ is the equivalent of \0 end of string in c/c++
so you are sending X ctrl-A ctrl-R \0 \0 ctrl-A I
It is not AI it is ctrl-A and then I.

try the following
printf (   "%c%c%c%c%c%c%c%c",'X','0x01','0x12','\0','\0','\0',''0x01','I')
Where should I try it? should I send it via TCP?
yes, replace the string you are trying to send with the formulation the printf i provided as an example and see if it makes a difference

or use the

in your code
sendsize = snprintf(sendline, sizeof(sendline), "%s", buffer);

sendline is 1000 you are only passing it 8-10  characters that might be your issue, it needs to be size of buffer

sendsize = snprintf(sendline, sizeof(buffer), "%s", buffer);
You can setup  binary fies as follows:
echo '580112000000016C' | xxd -r -p > r1_on.txt

Open in new window

In case you are interested in seeing what binary you are sending, you can do this:
$ od -x r1_on.txt 
0000000 0158 0012 0000 6c01
0000010

Open in new window

(On my x86, we are actually sending the binary equivalent of 0x58 followed by 0x01 due to its endianess. Your od -x results may vary if using a different endianess.)
For starters, you can now do this:
 #include <stdlib.h>
int main()
{
system("cat r1_on.txt |  nc 192.168.1.4 3000");
}

Open in new window

Depending on the asker goal, but using system calls from a c/c++ will defeat the purpose for which the application, program is being developed.


If the intent was there, the asker might have done this in bash as a script.
>> Depending on the asker goal
That is why I said "For starters". For finishing, we can wait for the asker goals.

My intent was to avoid having to analyze exactly what is being sent, and instead, just pointing out how to capture the binary message to a binary file. Then we don't have to interpret and try to capture the binary details in a local variable.
unfortunately I need to use it with C/C++ code. Honestly, the final goal is to send commands through my WxWidgets application but I started with C code in order to keep the things easier; I will import the code in my wxwidgets program when I'll get something work correctly.

I can't use system() because it doesn't work with wxwidgets.
All suggestion I made presume that your connection setup works. After the most recent thread, you are not checking whether the connection has been established. Etc.

You shoukd test/confirm at each stage that the preceding step has succeeded.
Just read the r1_on.txt file and note the number of bytes read in. Then send that buffer out.
ASKER CERTIFIED SOLUTION
Avatar of Duncan Roe
Duncan Roe
Flag of Australia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I solved the problem by using the byte notation as you suggested.
Thank you!
did you also change the sizeof to the buffer length?
I did in this way:

uint8_t command[] = {0x58, 0x01, 0x12, 0x00, 0x00, 0x00, 0x03, 0x6E}; // and so on..

    int bytes_to_send = sizeof(command);
    int bytes_sent = 0;

    do
    {
        n = send(sockfd, command + bytes_sent, bytes_to_send - bytes_sent, 0);
        if ( n < 0 )
        {
            cerr << "Error writing to socket!" << strerror(errno) << endl;
            close(sockfd);
            return 1;
        }
        bytes_sent += n;
    }
    while (bytes_sent < bytes_to_send);
Nicely done.
You might just like to guard against EINTR from some random event (like re-sizing the terminal window, which signals SIGWINCH)
#define SYSCALL(x, y) do x = y; while(x == -1 && errno == EINTR)
...
SYSCALL(n, send(sockfd, command + bytes_sent, bytes_to_send - bytes_sent, 0));

Open in new window

I wrap all my system calls that way
Thanks for accepting, btw
Thank you, Duncan!