Любительская метеостанция. Контроллер. Обмен данными с сервером.

Любительская метеостанция. Контроллер. Обмен данными с сервером.

Пример обмена данными контроллера ESP8266 c веб-сервером по HTTP-протоколу.

В предыдущей статье были рассмотрены способы соединения платы контроллера ESP8266 с компьютером, а также его подключение к Wi-Fi сети. Следующий шаг - процесс обмена данными с контроллером.

Являясь центром любительской метеостанции, контроллер будет получать данные с различных датчиков, измеряющих метеопараметры, но все это бессмысленно, если контроллер "не умеет общаться" с внешним миром. В данном проекте метеостанции данные передаются на сервер в Интернете, который отвечает за их хранение, обработку и отображение.

Понятие "сервер" в данном случае комплексное, поскольку сосотоит из веб-сервера, позволяющего вам увидеть данный сайт погода.вмолодечно.бел, скриптового языка PHP, на котором написана логика работы сайта, и базы данных MySQL, где хранится вся информация. Исходя из этого, необходимо "научить" контроллер обмениваться данными с веб-сервером, а значит, "разговаривать" будем по протоколу HTTP - это тот же протокол, по которому браузер загружает и показывает для вас этот сайт. На PHP написаны скрипты, которые производят необходимые манипуляции с данными, получаемые от контроллера метеостанции: обрабатывают их, производя необходимые расчеты, и помещают на хранение в базу данных. Рассмотрим HTTP-методы GET и POST — самые распространённые способы обмена данными с веб-сервером. 

Получение данных с сервера

GET — метод для чтения данных с сайта. Например, для доступа к этой статье. Он говорит серверу, что клиент хочет прочитать определенную страницу. Получив такой запрос, веб-сервер определяет, существует ли такая страница на сервере, и, если она есть, возвращает её содержимое. В нашем случае контроллеру не нужна полноценная веб-страничка, поэтому, по GET-запросу от контроллера мы передаем только нужные данные.

Пример программы:

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecureBearSSL.h>

const char* ssid     = "SSID_Name";
const char* password = "SSID_Password";

void setup() {
  Serial.begin(115200);
  Serial.println("starting");
  delay(500);
}

void loop() {
  if (WiFi.status() == WL_CONNECTED) {
    
    std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure);
    client->setInsecure();

    HTTPClient https;

    Serial.print("[HTTPS] begin...\n");
    if (https.begin(*client, "https://xn--80afd2bbd.xn--b1aedwejdbd2g.xn--90ais/api/test")) {
      Serial.print("[HTTPS] GET...\n");
      // start connection and send HTTP header
      int httpCode = https.GET();

      // httpCode will be negative on error
      if (httpCode > 0) {
        // file found at server, code 200, get string from server
        if (httpCode == HTTP_CODE_OK) {
          String payload = https.getString();
          Serial.println(payload);
        } else {
          Serial.printf("[HTTPS] GET another code: %s\n", httpCode);
        }
      } else {
        Serial.printf("[HTTPS] GET failed, error: %s\n", https.errorToString(httpCode).c_str());
      }

      https.end();
      
    } else {
      Serial.printf("[HTTPS] Unable to connect\n");
    }

    

    Serial.println("wait 10 sec...");
    delay(10000);    
  } else {
    Serial.println();
    Serial.println();
    Serial.print("Wait for WiFi... ");
 
    WiFi.begin(ssid, password);
    
    while (WiFi.status() != WL_CONNECTED) {
      Serial.print("*");
      delay(500);
    }
    
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
    
    delay(500);
  }
}

Рассмотрим программу.

Как видно из текста программы - оставлена часть, которая отвечает за подключение к Wi-Fi сети.

После того, как произойдет подключение, отправляется GET-запрос по адресу https://xn--80afd2bbd.xn--b1aedwejdbd2g.xn--90ais/api/test Адрес выглядит непонятно, но это специально закодированное представление национального домена для ссылки https://погода.вмолодечно.бел/api/test

Т.е. на сайте погода.вмолодечно.бел мы просим сервер отобразить страницу по адресу api/test.

Как уже говорилось выше, если такая страница будет найдена веб-сервером, то он ее нам и перешлет. Но бывают ситуации, когда страницы нет на сервере: была удалена, мы допустили ошибку в адресе страницы или по какой-то другой причине. Для обработки таких ситуаций в протоколе HTTP существуют коды состояния. Нас интересует код 200, который указывает, что запрос выполнен успешно. Во всех других случаях возвращаемую информацию отбрасываем. Обработка кода ведется через переменную httpCode.

Есть ещё один нюанс в данной программе. Если вы заметили, запрашиваемый адрес начинается не с http, а https. Это значит, что сайт работает по защищенному SSL-соединению. Запрос по http вернет нам ошибку. Поэтому начальные строки инициализируют режим соединения по https:

std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure);
client->setInsecure();

HTTPClient https;

Строкой client->setInsecure(); отменяем проверку SSL-сертификата для упрощения работы, но для полноценного обмена через защещенное соединение его стоит использовать.

В строке String payload = https.getString(); мы получаем в строковую переменную ответ от сервера.

На стороне сервера написан небольшой скрипт, который обрабатывает запрос api/test и возвращает строку ответа, как бы говоря веб-серверу, что данная страница есть, верни по запросу ответ.

В случае удачного подключения в мониторе порта будет отображаться приблизительно следующее:

Из листинга видно, что произошло успешное подключение к Wi-Fi, получен IP-адрес от роутера, отправлен GET-запрос и получена строка ответа.

Данный скрипт оставлю на сервере. Интересующиеся могут протестировать своё подключение по запросу в программе выше.

Передача данных на сервер

Передача данных по HTTP-протоколу осуществляется методом POST. В программе ниже показано использование данного метода:

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecureBearSSL.h>

const char* ssid     = "SSID_Name";
const char* password = "SSID_Password";

void setup() {
  Serial.begin(115200);
  Serial.println("starting");
  delay(500);
}

void loop() {
  if (WiFi.status() == WL_CONNECTED) {
    
    std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure);
    client->setInsecure();

    HTTPClient https;

    Serial.print("[HTTPS] begin...\n");
    
    https.begin(*client, "https://xn--80afd2bbd.xn--b1aedwejdbd2g.xn--90ais/api/post");
    https.addHeader("Content-Type", "application/x-www-form-urlencoded");
    Serial.print("[HTTP] POST...\n");
    
    // start connection and send HTTP header and body
    int httpCode = https.POST("temp=-7.2&hum=98.5&press=744.3");
    
    // httpCode will be negative on error
    if (httpCode > 0) {
      // HTTP header has been send and Server response header has been handled
      Serial.printf("[HTTP] POST... code: %d\n", httpCode);

      // file found at server
      if (httpCode == HTTP_CODE_OK) {
        const String& payload = https.getString();
        Serial.println("received payload:\n<<");
        Serial.println(payload);
        Serial.println(">>");
      }
    } else {
      Serial.printf("[HTTP] POST... failed, error: %s\n", https.errorToString(httpCode).c_str());
    }

    https.end();
    
    Serial.println("wait 10 sec...");
    delay(10000);    
  } else {
    Serial.println();
    Serial.println();
    Serial.print("Wait for WiFi... ");
 
    WiFi.begin(ssid, password);
    
    while (WiFi.status() != WL_CONNECTED) {
      Serial.print("*");
      delay(500);
    }
    
    Serial.println("");
    Serial.println("WiFi connected");
    Serial.println("IP address: ");
    Serial.println(WiFi.localIP());
    
    delay(500);
  }
}

Аналогично получению данных инициируем поключение к сети. Обрабатываем https. Далее формируем POST-запрос. 

Строкой 

https.addHeader("Content-Type", "application/x-www-form-urlencoded");

эмулируем передачу данных, как если бы пользователь заполнил форму на сайте и нажал кнопку отправить. Данные такой виртуальной формы передаем в следующем виде:

int httpCode = https.POST("temp=-7.2&hum=98.5&press=744.3");

Видим, что полезная информация находится тут: "temp=-7.2&hum=98.5&press=744.3". Впоследствии на место числовых значений подставим реальные данные от датчиков, а к полям temp, hum и press можно будет обратиться на сервере и прочитать, что в них было передано. Так и происходит в следующем листинге с порта:

Сервер в качестве теста возвращает строку "Post is ok. Temperature: -7.2 Humidity: 98.5 Pressure: 744.3 " где видно, что поля виртуальной формы были успешно распознаны, нужные данные из них получены. Далее мы можем их обработать как нам нужно и сохранить в базу данных для дальнейшего использования. Здесь же сервер их подставил в строку ответа, которую мы получили на наш запрос.

На стороне сервера эта операция выглядит следующим образом:

echo 'Post is ok. Temperature: '.$_POST['temp'].' Humidity: '.$_POST['hum'].' Pressure: '.$_POST['press'];

Достаточно несложно организовать обмен данными контроллера ESP8266 с сервером по протоколу HTTP(S). Приведенные программы для контроллера можно расширить как в сторону обработки HTTP-кодов и реакции на них, так и в сторону обработки ответов от сервера.