简体   繁体   中英

How to connect Arduino to C++ using sockets?

My C++ client side socket code only collects the very first line of the output provided by an Arduino socket server (I guess I can call it that). Can you please tell me where I am going wrong with my C++ code and how to fix it? If the question is too verbose please jump to the C++ code at the bottom.

Hardware setup : Arduino Mega with an Ethernet card (shield) and an Intel NUC with Ubuntu 16.04. The two devices are connected using cable and an unmanaged switch.

在此处输入图片说明

Arduino Side : I started out with a web server example from the Arduino Ethernet library, and modified the code until I was able to collect the status of all I/O, process the I/O data, and make results available to a web client. Image below shows a snapshot of the HTML my Arduino dishes out. 在此处输入图片说明

The Arduino code that handles the socket server side of things is as follows (everything works, but thought to include to show everything):

#include <SPI.h>
#include <Ethernet.h>

byte mac[] = {0xBA, 0xDA, 0x55, 0x12, 0x34, 0x56};

IPAddress ip(192, 168, 0, 21);

EthernetServer server(80);

void setup() 
{
  Ethernet.begin(mac, ip);

  // Check for Ethernet hardware present
  if (Ethernet.hardwareStatus() == EthernetNoHardware) 
  {
    Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware.");
   while (true) 
   {
      delay(1); // do nothing, no point running without Ethernet hardware
   }
}

if (Ethernet.linkStatus() == LinkOFF) 
{
    Serial.println("Ethernet cable is not connected.");
}

server.begin();
}

void loop()
{
    //true when there is an incoming connection
   if (client) 
   {
     Serial.println("new client");
     // an http request ends with a blank line
     boolean currentLineIsBlank = true;
     while (client.connected()) 
     {
       if (client.available()) 
       {
          //the next two lines print the client HTTP GET request to serial.
          char c = client.read();
          Serial.write(c);
          // if you've gotten to the end of the line (received a newline
          // character) and the line is blank, the http request has ended,
          // so you can send a reply
          if (c == '\n' && currentLineIsBlank) 
          {
             // send a standard http response header
             client.println("HTTP/1.1 200 OK");
             client.println("Content-Type: text/html");
             client.println("Connection: close");  
             client.println("Refresh: 1");  
             client.println();
             client.println("<!DOCTYPE HTML>");
             client.println("<html>");

             //whole bunch of client.println("...."); to dish out a web page.

              client.println("</html>");
              break;
            }

            if (c == '\n') 
            {
               // you're starting a new line
               currentLineIsBlank = true;
            } else if (c != '\r') 
            {
              // you've gotten a character on the current line
              currentLineIsBlank = false;
            }
         }
      }
     // give the web browser time to receive the data
     //delay(1);
     // close the connection:
     client.stop();
     Serial.println("client disconnected");
  }
}

Intel NUC with Ubuntu 16.04 and C++11 : I have been following a tutorial ( https://www.binarytides.com/socket-programming-c-linux-tutorial/ ) to figure out the socket client side, and learning C++ from https://www.learncpp.com . So far I am able to send a request to a Google server and collect the HTML page using my socket, and print the HTML to the terminal: 在此处输入图片说明

Here is my C++ code:

#include <iostream> //for std::cout & std::endl
#include<arpa/inet.h> //inet_addr
#include<string.h> // for strlen

int main()
{
    int my_socket = socket(AF_INET , SOCK_STREAM , 0);

    //make sure the socket we got is OK
    if (my_socket == -1)
    {
         std::cout << "problem creating a socket." << std::endl;
    }

    struct sockaddr_in connectionToServer;

    connectionToServer.sin_addr.s_addr = inet_addr("172.217.2.99");//google IP
    connectionToServer.sin_family = AF_INET; //type of IP addres. where AF_INET is IPv4
    connectionToServer.sin_port = htons(80); //port is set via a method


    if (connect(my_socket , (struct sockaddr *)&connectionToServer , sizeof(connectionToServer)) < 0)
    {
        std::cout << "connect error" << std::endl;
        return 1;
    }

    std::cout << "Connected" << std::endl;

    //send a request to get a page
    char *message = "GET / HTTP/1.1\r\n\r\n";
    if( send(my_socket , message , strlen(message) , 0) < 0)
    {
        std::cout << "Send failed" << std::endl;
        return 1;
    }

    std::cout << "Data Sent\n" << std::endl;


    char buffer [20000] = {};
    if( recv(my_socket, buffer , sizeof(buffer) , 0) < 0)
    {
        std::cout << "recv failed" << std::endl;
    }

    for(int i=0; i<20000 ; i++)
    {
        std::cout<< buffer[i];
    }

    return 0;
 }

Problem : when I change the IP address from Google to my Arduino in my C++ program, the cpp client socket program only collects the very first line that the Arduino socket server outputs. I know it is the very first line as I modified the first line the Arduino server dishes out by adding "..but it doesn't matter" and the change showed up in the standard output window of the c++ program. I need the C++ program to collect the entire output, not just the first line. for the life of me I can't figure out how.

在此处输入图片说明

Could you please help me with the following questions:

  1. How do I collect the entire Arduino message (and not just the first line)? What modification do I need to do to my C++ program? The system needs to be able to pass data from one device to another.
  2. The entire objective of this setup is to pass 2 floating point numbers and 6 integers from the Arduino to my C++ program. Very soon I am going to do away with the whole HTML thing. What kind of protocol would you recommend I use when passing the data over? I was thinking of padding each value in letters. Ex: “Aint1A Bint2B Cint3C Dfloat1D ...” and so on. Can you recommend some post/tutorial/web page that suggests the best way to package and process data that arrives to a C++ program via a socket?

I apologize in advance for the 'fix-my-codez' question, but all the questions I have read are a bit too advanced for me as they address buffer overflows, security, endian issues, error handling, malformed messages and so on, which is way beyond my needs (ability is probably more accurate). Thank you very much for your time and help.

A huge thanks to Jonathan Potter and Remy Lebeau for providing a working answer in the comments. I carried out your suggestions/answers and everything worked. I did not touch the Arduino code, and made the following changes in the CPP code:

In the CPP code in the question remove everything from (and including) char buffer [20000] = {}; and replace it with:

//***********receive the results**************************
//initialize a string
std::string totalResults;

//create a temp array. This will hold a single line recieved from
//the arduino.
char tempBuffer [300] = {};

//the number of lines I want to recieve from the arduino
int magicNumber = 100;

//recieve the arduino response
for(int i = 0; i < magicNumber ; i++)
{
    //call the recv method over and over as it gets a single arduino line with
    //every iteration.
    if( recv(my_socket, tempBuffer , sizeof(tempBuffer) , 0) < 0)
    {
    std::cout << "recv failed" << std::endl;
    }

    //write out the single line we recieved to a string (which grows on the fly)
    for(int i = 0; i < 300; i++ )
    {
        totalResults = totalResults+tempBuffer[i];

        //kill the loop the moment there is a null character. When i created the
        //array i initialized it with NULL characters. so if I am seeing a null
        //character it means that the data I recieved from the arduino has all been
        //given to the string.
        if(tempBuffer[i] == NULL)
        {
            break;
        }
    }

    //empty array - see: https://stackoverflow.com/questions/632846/clearing-a-char-array-c
    std::fill(&tempBuffer[0], &tempBuffer[300], 0);

}

//print the results to the standard output.
std::cout << totalResults << std::endl;

These changes (which I am sure can be critiqued from here to the moon and back) allowed me to get the data the arduino was sending without missing a single ASCII character. Thanks!

在此处输入图片说明

On a separate note, thank you to David Schwartz and Remy Lebeau for pointing out that the HTTP protocol I was using is pretty bad. I used the HTTP as I knew it was a working example in the Arduino code; and now the objective is to remove the HTML and to find a more efficient way to pass the values to the cpp code (using only sockets). Thank you very much for your comments!

****EDIT****

OK, so if you are still reading this, you must be passing some information from an Arduino to a cpp program using Ethernet. If so read on. After I was guided on how to receive the full Arduino response, I removed all the HTML and HTTP, and simply sent the values I needed from the Arduino with letter padding (ex: Aint1B, Cint2D, Efloat1F, etc...). I marked the end of transmission from the Arduino with ~~~ characters. Cool. But, for some reason sometimes I would get the entire Arduino response, and sometimes it would be missing some amount of the tail of the message. Here is what I learned:

  1. The location (memory or system call I have no idea) that the recv reads from, can sometimes have only one char value.
  2. sometimes the \\n characters is all a recv fetches!
  3. sometimes the location that the recv reads from can have multiple values! There were times when the recv method returned 6 characters, and there were times when it returned only 4. This behavior seemed unpredictable.

Taking this behavior into account I modified my cpp code. This code receives the entire message, and when it does, the additional needless looping of the recv method is stopped. Hope you find it useful:

//***********receive the results**************************
//initialize a string
std::string totalResults = "";

//create a temp array. This will hold a single line recieved from
//the arduino.
char tempBuffer [300] = {};

//the number of lines I want to receive from the Arduino. This is an unusual
//value for the following reasons (figured out via println):
//(1) sometimes the buffer where the recv method reads from has only one value.
//    ex: letter A only (as per my,*ahem", "protocol".
//(2) sometimes the \n is all a recv fetches!
//(3) sometimes the buffer where the recv method reads has multiple values, so
//    the recv fetches many items that get unpacked in the second loop. This is
//    why sometimes we increase the value by only 1, but get WAY more values. I
//    observed this behaviour to be non repeating. Sometimes it reads 5 values,
//    and sometimes it reads only 3 values.
// At a value of 60 I am always getting the message, and run the recv command
// unnecesserily. For this reason I have implemented the "end transmission"
// characters (~~~), which allow me to kill the for loop once the full message is
// retrieved.
int numberOfTimesRecvRuns = 60;

//number of characters per line. do not reduce as it is needed to be this size to
// get the full insult if the protocol is not followed.
int arduinoNumberOfCharsPerLine = 50;

bool fullResponseRecieved = false;

//recieve the entire arduino response. The magic number is the number of times
// we call the recv method (which reads a line from the socket).
for(int i = 0; i < numberOfTimesRecvRuns; i++)
{
    //call the recv method over and over as it gets a single arduino line with
    //every iteration.
    if( recv(my_socket, tempBuffer , sizeof(tempBuffer) , 0) < 0)
    {
    std::cout << "recv failed" << std::endl;
    }

    //write out the single line we recieved to a string (which grows on the fly). 300 because
    //i dont believe I will have more than 300 characters per line.
    for(int j = 0; j < arduinoNumberOfCharsPerLine; j++ )
    {
        totalResults = totalResults+tempBuffer[j];
        std::cout << "i: " << j << " value recv read: " << tempBuffer[j]<< std::endl;

        //kill the loop the moment there is a null character. When i created the
        //array i initialized it with NULL characters. so if I am seeing a null
        //character it means that the data I recieved from the arduino has all been
        //given to the string.
        if(tempBuffer[j] == NULL )
        {
            std::cout << "I ran... See ya" << std::endl;
            break;
        }

        //end of transmission detected
        if(tempBuffer[j] == '~')
        {
            fullResponseRecieved = true;
        }
    }

    //empty array - see: https://stackoverflow.com/questions/632846/clearing-a-char-array-c
    std::fill(&tempBuffer[0], &tempBuffer[300], 0);

    // A '~' character means the full message has been recieved and there is no
    // need to keep looping for the purpose of running the recv method.
    if(fullResponseRecieved == true)
    {
        //reset the value
        fullResponseRecieved = false;
        std::cout << "killing recv loop" << std::endl;
        break;
    }

}

//print the results to the standard output.
std::cout << totalResults << std::endl;

return 0;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM