简体   繁体   English

如何使用套接字将Arduino连接到C ++?

[英]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). 我的C ++客户端套接字代码仅收集Arduino套接字服务器提供的输出的第一行(我想我可以这样称呼)。 Can you please tell me where I am going wrong with my C++ code and how to fix it? 您能告诉我我的C ++代码在哪里出问题以及如何解决吗? If the question is too verbose please jump to the C++ code at the bottom. 如果问题太冗长,请跳至底部的C ++代码。

Hardware setup : Arduino Mega with an Ethernet card (shield) and an Intel NUC with Ubuntu 16.04. 硬件设置 :带有以太网卡(屏蔽)的Arduino Mega和带Ubuntu 16.04的英特尔NUC。 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. Arduino Side :我从Arduino以太网库中的Web服务器示例开始,然后修改了代码,直到能够收集所有I / O的状态,处理I / O数据并将结果提供给Web客户端。 Image below shows a snapshot of the HTML my Arduino dishes out. 下图显示了我的Arduino提供的HTML的快照。 在此处输入图片说明

The Arduino code that handles the socket server side of things is as follows (everything works, but thought to include to show everything): 处理事物的套接字服务器端的Arduino代码如下(一切正常,但认为包括在内可以显示所有内容):

#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 . 英特尔NUC与Ubuntu 16.04和C ++ 11 :我一直在关注一个教程( https://www.binarytides.com/socket-programming-c-linux-tutorial/ )来找出套接字客户端,并学习C ++来自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: 到目前为止,我已经能够向Google服务器发送请求,并使用套接字收集HTML页面,然后将HTML打印到终端: 在此处输入图片说明

Here is my C++ code: 这是我的C ++代码:

#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. 问题 :当我在C ++程序中将IP地址从Google更改为Arduino时,cpp客户端套接字程序仅收集Arduino套接字服务器输出的第一行。 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. 我知道这是第一行,因为我通过添加“ ..但没关系”修改了Arduino服务器的第一行,并且更改显示在c ++程序的标准输出窗口中。 I need the C++ program to collect the entire output, not just the first line. 我需要C ++程序来收集整个输出,而不仅仅是第一行。 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)? 我如何收集整个Arduino消息(而不仅仅是第一行)? What modification do I need to do to my C++ program? 我需要对C ++程序进行哪些修改? 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. 该设置的整个目标是将2个浮点数和6个整数从Arduino传递到我的C ++程序。 Very soon I am going to do away with the whole HTML thing. 很快我将取消整个HTML内容。 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. 例如:“ Aint1A Bint2B Cint3C Dfloat1D ...”,依此类推。 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? 您是否可以推荐一些帖子/教程/网页,以提供最好的方式来打包和处理通过套接字到达C ++程序的数​​据?

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). 我为“ fix-my-codez”问题事先表示歉意,但是我读过的所有问题对我来说都太高级了,因为它们解决了缓冲区溢出,安全性,字节序问题,错误处理,格式错误的消息等。远远超出了我的需求(能力可能更准确)。 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. 非常感谢Jonathan Potter和Remy Lebeau在评论中提供了有效的答案。 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: 我没有触摸Arduino代码,而是在CPP代码中进行了以下更改:

In the CPP code in the question remove everything from (and including) char buffer [20000] = {}; 在问题中的CPP代码中,从char buffer [20000] = {};删除所有内容(包括其中) 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. 这些变化(我确信可以从这里到月球和从月球来回批评)使我能够获取arduino发送的数据而不会丢失单个ASCII字符。 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. 另外,感谢David Schwartz和Remy Lebeau指出我使用的HTTP协议非常糟糕。 I used the HTTP as I knew it was a working example in the Arduino code; 我使用HTTP,因为我知道这是Arduino代码中的一个有效示例。 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). 现在的目标是删除HTML,并找到一种更有效的方法(仅使用套接字)将值传递给cpp代码。 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. 好的,因此,如果您仍在阅读本文,则必须使用以太网将一些信息从Arduino传递到cpp程序。 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...). 在指导我如何接收完整的Arduino响应之后,我删除了所有HTML和HTTP,并通过字母填充(例如:Aint1B,Cint2D,Efloat1F等)从Arduino发送了我需要的值。 I marked the end of transmission from the Arduino with ~~~ characters. 我用~~~字符标记了Arduino的传输结束。 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. 但是,由于某种原因,有时我会得到整个Arduino响应,有时它会丢失一些消息尾部。 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. recv读取的位置(我不知道的内存或系统调用)有时只能具有一个char值。
  2. sometimes the \\n characters is all a recv fetches! 有时\\n字符全部都是recv提取的!
  3. sometimes the location that the recv reads from can have multiple values! 有时recv读取的位置可以有多个值! There were times when the recv method returned 6 characters, and there were times when it returned only 4. This behavior seemed unpredictable. 有时recv方法返回6个字符,有时仅返回4个字符。此行为似乎不可预测。

Taking this behavior into account I modified my cpp code. 考虑到此行为,我修改了cpp代码。 This code receives the entire message, and when it does, the additional needless looping of the recv method is stopped. 该代码接收到整个消息,并且当它收到时,将停止recv方法的其他不必要的循环。 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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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