Как подключить Arduino к C ++ с помощью сокетов? - PullRequest
0 голосов
/ 11 октября 2018

Мой код сокета на стороне клиента C ++ собирает только самую первую строку вывода, предоставленную сервером сокетов Arduino (я думаю, я могу это так назвать).Подскажите, пожалуйста, где я ошибаюсь с моим C ++ кодом и как это исправить?Если вопрос слишком подробный, перейдите к коду C ++ внизу.

Настройка оборудования : Arduino Mega с картой Ethernet (щит) и Intel NUC с Ubuntu 16.04.Два устройства соединены с помощью кабеля и неуправляемого коммутатора.

enter image description here

Сторона Arduino : я начал с примера веб-сервераиз библиотеки Arduino Ethernet и изменял код, пока я не смог собрать состояние всех операций ввода-вывода, обработать данные ввода-вывода и сделать результаты доступными для веб-клиента.На изображении ниже показан снимок HTML-кода с тарелками my Arduino.enter image description here

Код 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 с Ubuntu 16.04 и C ++ 11 : я следовал учебному пособию (https://www.binarytides.com/socket-programming-c-linux-tutorial/), чтобы выяснить сторону клиента сокета и изучил C ++ из https://www.learncpp.com. ИтакПока я могу отправить запрос на сервер Google и собрать HTML-страницу, используя мой сокет, и распечатать HTML-код в терминал: enter image description here

Вот мой код 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;
 }

Проблема : когда я меняю IP-адрес с Google на Arduino в моей программе на C ++, клиентская программа сокета cpp собирает только самую первую строку, которую выводит сервер сокетов ArduinoЯ знаю, что это самая первая строка, так как я изменил первую строку, которую сервирует сервер Arduino, добавив «... но это не имеет значения», и изменение обнаружилось в стандартном окне вывода программы c ++. Мне нужноC ++ программа для сборавесь вывод, а не только первая строка.что касается меня, я не могу понять, как.

enter image description here

Не могли бы вы помочь мне со следующими вопросами:

  1. Как мне собрать все сообщение Arduino (а не только первую строку)?Какую модификацию мне нужно сделать с моей программой на C ++?Система должна иметь возможность передавать данные с одного устройства на другое.
  2. Вся цель этой установки - передать 2 числа с плавающей запятой и 6 целых чисел из Arduino в мою программу на C ++.Очень скоро я собираюсь покончить со всем HTML.Какой протокол вы бы порекомендовали использовать при передаче данных?Я думал о заполнении каждого значения буквами.Пример: «Aint1A Bint2B Cint3C Dfloat1D ...» и так далее.Можете ли вы порекомендовать какой-нибудь пост / учебник / веб-страницу, предлагающую лучший способ упаковки и обработки данных, поступающих в программу на C ++ через сокет?

Заранее извиняюсь за исправлениевопрос Codez, но все вопросы, которые я прочитал, слишком сложны для меня, поскольку они касаются переполнения буфера, безопасности, порядковых номеров, обработки ошибок, искаженных сообщений и т. д., что выходит далеко за рамки моих потребностей (возможно, способность более точна)).Большое спасибо за ваше время и помощь.

1 Ответ

0 голосов
/ 13 октября 2018

Огромное спасибо Джонатану Поттеру и Реми Лебо за рабочий ответ в комментариях.Я выполнил ваши предложения / ответы, и все заработало.Я не трогал код Arduino и сделал следующие изменения в коде CPP:

В коде CPP в вопросе удалите все из (и в том числе) char buffer [20000] = {}; и замените его на:

//***********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: /504146/ochistka-massiva-simvolov-c
    std::fill(&tempBuffer[0], &tempBuffer[300], 0);

}

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

Эти изменения (которые, я уверен, могут быть подвергнуты критике отсюда на Луну и обратно) позволили мне получить данные, которые посылал arduino, без пропуска ни одного символа ASCII.Спасибо!

enter image description here

Отдельно хочу поблагодарить Дэвида Шварца и Реми Лебо за то, что они указали, что используемый мной протокол HTTP довольно плох.Я использовал HTTP, так как знал, что это рабочий пример в коде Arduino;и теперь цель состоит в том, чтобы удалить HTML и найти более эффективный способ передачи значений в код cpp (используя только сокеты).Большое спасибо за ваши комментарии!

**** РЕДАКТИРОВАТЬ ****

ОК, поэтому, если вы все еще читаете это, вы должны передавать некоторую информацию от Arduino кпрограмма cpp, использующая Ethernet.Если так, читайте дальше.После того, как я получил инструкции о том, как получить полный ответ Arduino, я удалил все HTML и HTTP и просто отправил нужные значения из Arduino с добавлением букв (например, Aint1B, Cint2D, Efloat1F и т. Д.).Я отметил конец передачи от Arduino с ~~~ символов.Здорово.Но по какой-то причине иногда я получал полный ответ Arduino, а иногда ему не хватало некоторой части сообщения.Вот что я узнал:

  1. Местоположение (память или системный вызов, о котором я не знаю), из которого читается recv, может иногда иметь только одно значение символа.
  2. иногда \n символов - это все извлечения из recv!
  3. иногда местоположение, из которого читается recv, может иметь несколько значений!Были времена, когда метод recv возвращал 6 символов, и были случаи, когда он возвращал только 4. Это поведение казалось непредсказуемым.

Учитывая это поведение, я модифицировал свой код cpp.Этот код получает все сообщение, и когда это происходит, дополнительная ненужная зацикливание метода recv останавливается.Надеюсь, вы найдете это полезным:

//***********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: /504146/ochistka-massiva-simvolov-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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...