• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

POST и GET запросы и вписывание данных в форму на примере передачи данных с DHT11

Доброго времени суток. К своему огромному сожалению из-за вынужденного безделья все забыл и закостенел. Но как всегда в спешке пробую разобраться. Хочу на вэб странице в форму вбить данные с датчика. На самом деле датчик не важен, но в данной ситуации это DHT11.
Есть несколько проблем:
1. Функция HTTP запроса, где выводятся все данные (пока без вписывания в поля формы на странице) если датчик не подключен пишет в данных ерунду или не компилируется скетч.
2. Хотелось бы использовать для заполнения форм на странице json. Как по мне, так удобнее читать и проще отправлять POST
3. Как все таки заполнить данными формы на странице? Понимаю, что нужен jscript

Что я делаю:
1. Отрезаю на ESP место под фтп и заливаю туда простенький index.html Туда же потом зальются файл стилей и jscript
2. Подключаю библиотеки вэбсервера, фтп сервера, самой есп, для работы с файловой системой, для вайфая и для датчика
3. Все запускаю и получаю айпи для своей есп.
4. Оформляю блок для HTTP запросов
5. Объявляю стринговские переменные для этих запросов

Если делать запрос по каждому показателю (статус датчика, температура, влажность) датчика, то все работает. Если же хочу вывести сразу все, то если датчик подключен, все выводится, а если его отключить (типа сгорел или ещё что-то), то вместо данных температуры и влажности выскакивают какие-то непонятные цифры или же скетч не компилируется. Пишет ошибку преобразования (пробовал int, char, сразу стринг).
Подробности ниже в коде:
Код:
#include <ESP8266WiFi.h>                                                // Библиотека для создания Wi-Fi подключения (клиент или точка доступа)
#include <WiFiClient.h>
#include <ESP8266WebServer.h>                              // Библиотека для управления устройством по HTTP (например из браузера)
#include <FS.h>                                                         // Библиотека для работы с файловой системой
#include <ESP8266FtpServer.h>                                // Библиотека для работы с SPIFFS по FTP
#include "DHTesp.h"

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

ESP8266WebServer HTTP(80);                                 // Определяем объект и порт сервера для работы с HTTP
FtpServer ftpSrv;                                                      // Определяем объект для работы с модулем по FTP (для отладки HTML)
DHTesp dht;                                                             // Определяем объект для работы с модулем DHT

void setup() {
  Serial.begin(9600); 
  dht.setup(D4, DHTesp::DHT11);                             // Определяем на какой пин подается данные температуры и влаги

  WiFi.begin(ssid, password);                                   // Инициализируем подключение к указанной вайфай сети
 
  SPIFFS.begin();                                                       // Инициализируем работу с файловой системой                         
  HTTP.begin();                                                         // Инициализируем Web-сервер
  ftpSrv.begin("","");                             // Поднимаем FTP-сервер для удобства отладки работы HTML (логин: relay, пароль: relay)

   while (WiFi.status() != WL_CONNECTED) {            // Запускаем ожидание подключения к роутеру или вайфай серверу
   delay(1000); Serial.println("waiting...");
   } 
Serial.println(WiFi.localIP()); 

// Обработка HTTP-запросов
  HTTP.on("/get_all", [](){                                        // При HTTP запросе вида http://адрес есп/get_all
      HTTP.send(200, "text/plain", get_all());             // Отдаём клиенту код успешной обработки запроса, сообщаем, что формат ответа текстовый и возвращаем результат выполнения функции relay_switch
  });
  HTTP.on("/temper", [](){                                        // При HTTP запросе вида http://адрес есп/temper
      HTTP.send(200, "text/plain", temper());                     // Отдаём клиенту код успешной обработки запроса, сообщаем, что формат ответа текстовый и возвращаем результат выполнения функции relay_status
  });
  HTTP.on("/vlaga", [](){                                        // При HTTP запросе вида http://адрес есп/vlaga
      HTTP.send(200, "text/plain", vlaga());                     // Отдаём клиенту код успешной обработки запроса, сообщаем, что формат ответа текстовый и возвращаем результат выполнения функции relay_status
  });
  HTTP.on("/status", [](){                                        // При HTTP запросе вида http://адрес есп/status
      HTTP.send(200, "text/plain", status());                     // Отдаём клиенту код успешной обработки запроса, сообщаем, что формат ответа текстовый и возвращаем результат выполнения функции relay_status
  });
  HTTP.onNotFound([](){                                       // Описываем действия при событии "Не найдено"
  if(!handleFileRead(HTTP.uri()))                            // Если функция handleFileRead (описана ниже) возвращает значение false в ответ на поиск файла в файловой системе
      HTTP.send(404, "text/plain", "Not Found");    // возвращаем на запрос текстовое сообщение "File isn't found" с кодом 404 (не найдено)
  });
}

void loop() {
    delay(dht.getMinimumSamplingPeriod());

  float humidity = dht.getHumidity();                                // Тут на всякий случай получаем температуру и влажность
  float temperature = dht.getTemperature();
    HTTP.handleClient();                                                // Обработчик HTTP-событий (отлавливает HTTP-запросы к устройству и обрабатывает их в соответствии с выше описанным алгоритмом)
    ftpSrv.handleFTP();                                                 // Обработчик FTP-соединений
    
}

String get_all() {                                                 // Функция передачи всех данных
  const char* state;
  int temper;
  int vlag;
  float humidity = dht.getHumidity();
  float temperature = dht.getTemperature();
 
  if (dht.getStatus() == 0)
    {
    state = "OK";
    temper = temperature;
    vlag = humidity;
    }
  else                                                                  // И ВОТ ТУТ ПРОБЛЕМА
    {
    state = "NO"; 
    temper = "Noo";
    vlag = "Nine";
    }
  return String(state)+"+"+String(temper)+"+"+String(vlag);   // возвращаем результат, преобразовав число в строку
}

String temper() {
  int temper;
  float temperature = dht.getTemperature();
  temper = temperature;
  return String(temper); 
}

String vlaga() {
  int vlag;
  float humidity = dht.getHumidity(); 
  vlag = humidity;
  return String(vlag);
}

String status() {                                         // Функция для определения текущего статуса датчика
  const char* state;
  if (dht.getStatus() == 0)                          // Если датчик передает данные 
    state = "OK";                                         //  то запоминаем его как ОК
  else
    state = "NO";                                         //  запоминаем его как НЕТ
  return String(state);                                  // возвращаем результат, преобразовав в строку
}

bool handleFileRead(String path){                     // Функция работы с файловой системой
  if(path.endsWith("/")) path += "index.html";            // Если устройство вызывается по корневому адресу, то должен вызываться файл index.html (добавляем его в конец адреса)
  String contentType = getContentType(path);           // С помощью функции getContentType (описана ниже) определяем по типу файла (в адресе обращения) какой заголовок необходимо возвращать по его вызову
  if(SPIFFS.exists(path)){                                              // Если в файловой системе существует файл по адресу обращения
    File file = SPIFFS.open(path, "r");                                 //  Открываем файл для чтения
    size_t sent = HTTP.streamFile(file, contentType);        //  Выводим содержимое файла по HTTP, указывая заголовок типа содержимого contentType
    file.close();                                                       //  Закрываем файл
    return true;                                                        //  Завершаем выполнение функции, возвращая результатом ее исполнения true (истина)
  }
  return false;                                                         // Завершаем выполнение функции, возвращая результатом ее исполнения false (если не обработалось предыдущее условие)
}

String getContentType(String filename){                                 // Функция, возвращающая необходимый заголовок типа содержимого в зависимости от расширения файла
  if (filename.endsWith(".html")) return "text/html";                   // Если файл заканчивается на ".html", то возвращаем заголовок "text/html" и завершаем выполнение функции
  else if (filename.endsWith(".css")) return "text/css";                // Если файл заканчивается на ".css", то возвращаем заголовок "text/css" и завершаем выполнение функции
  else if (filename.endsWith(".js")) return "application/javascript";   // Если файл заканчивается на ".js", то возвращаем заголовок "application/javascript" и завершаем выполнение функции
  else if (filename.endsWith(".png")) return "image/png";               // Если файл заканчивается на ".png", то возвращаем заголовок "image/png" и завершаем выполнение функции
  else if (filename.endsWith(".jpg")) return "image/jpeg";              // Если файл заканчивается на ".jpg", то возвращаем заголовок "image/jpg" и завершаем выполнение функции
  else if (filename.endsWith(".gif")) return "image/gif";               // Если файл заканчивается на ".gif", то возвращаем заголовок "image/gif" и завершаем выполнение функции
  else if (filename.endsWith(".ico")) return "image/x-icon";            // Если файл заканчивается на ".ico", то возвращаем заголовок "image/x-icon" и завершаем выполнение функции
  return "text/plain";                                                  // Если ни один из типов файла не совпал, то считаем что содержимое файла текстовое, отдаем соответствующий заголовок и завершаем выполнение функции
}
 
И ниже простенький код index.htmlс тремя полями, без стилей и скрипта:
Код:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
  <meta charset="utf-8">
  <title>Проба датчика температуры</title>

</head>

<body>
<form name = "Data_DHT11" method = "POST" action = "">
<label>Статус<textarea name="Data_status"></textarea></label>
<button type="submit" name="submit_status" value="">Получить статус</button><br>
<label>Температура<textarea name="Data_temp"></textarea></label>
<button type="submit" name="submit_temper" value="">Получить температуру</button><br>
<label>Влажность<textarea name="Data_vlag"></textarea></label>
<button type="submit" name="submit_vlag" value="">Получить влажность</button><br><br><br>
<button type="submit" name="submit_all" value="">Получить все данные</button>
</form>
</body>

В таком виде по идее легко можно управлять как датчиком в отдельности от сервера, так и с сервера. Так сказать вырабатывается что-то типа API. Хотя может я и не прав и все слишком усложняю, но хотелось бы сперва сделать такой вариант
 

aZholtikov

Active member
3. Как все таки заполнить данными формы на странице? Понимаю, что нужен jscript
Я делаю через "...опу", но работает.
Все данные пишу в json файл. Страница его читает из flash памяти и заполняет форму. При изменении на странице данных файл перезаписывается.
Не для рекламы...
 

enjoynering

Well-known member
Я в своих проектах отказался от встроенной
Код:
streamFile(file, contentType);
Очень медленная. Написал свою реализацию. Раньше esp8266 отдавала 2КБ файл за 2сек, а сейчас за 1сек максимум. Вебморды просто летают.
 
То, что вы пытаетесь сделать, уже давно показано у Третьякова
Да, посмотрел, идея похожа на мою. И это нормально. Я то можно сказать год назад начал заниматься ардуино, и то потом 9 месяцев, если не больше был перерыв. А тут люди с опытом.
Но Третьяков больше рекламирует свое решение. Та же настройка вэбморды с помощью json показана поверхностно. Так сказать, уже готовый результат. А внутренности писал даже не он, а его товарищ Ренат. У него тоже хороший цикл роликов. Но их сегодня не осилил.
В то же время у меня есть и различия в самом подходе. Начиная с того, что у Третьякова не понятно как реализован выбор управления с сервера или напрямую с ЕСП. Обращение к ЕСП у него сделано через SSDS, а я у себя реализовал через mDNS (тут, в моем примере не показан, так же как и сканирование сети). Что из них лучше - покажет время, когда попробую и тот и тот вариант.
Опять же, у него все посторено на ГЕТ запросах, а мне больше нравиться ПОСТ И куча других отличий.
И конечно же есть чему поучиться. Один плагин для работы с файловой системой чего стоит. Правда у меня на Arduino IDE 2.0.0, да еще на портативный, он не становится. А хотелось бы.
Опять же, не совсем понятна роль сокетов. Вроде как проходили эту технологию, но я не успел в ней разобраться. А за TickerScheduler отдельный респект.
То, что надо проверять данные после получения или перед одправкой, не сложно додуматься, но как они могут влиять на компиляцию, остается загадкой.
На самом деле тут у меня проблема в знании языка. Если вырвать из кода, то вот проблемный кусочек:
Код:
String get_all() {                                           // Функция передачи всех данных
  const char* state;
  int temper;
  int vlag;
  float humidity = dht.getHumidity();
  float temperature = dht.getTemperature();
 
  if (dht.getStatus() == 0)
    {
    state = "OK";
    temper = temperature;
    vlag = humidity;
    }
  else                                                        // И ВОТ ТУТ ПРОБЛЕМА
    {
    state = "NO";
    temper = "Noo";
    vlag = "Nine";
    }
  return String(state)+"+"+String(temper)+"+"+String(vlag);   // возвращаем результат, преобразовав число в строку
  // Если датчик не отвечает, то String(temper)+"+"+String(vlag) выдает ерунду
}
Понятно, что можно получить все по-отдельности, но хотелось все в одну функцию втиснуть. Как и что реализовывать практически и где это применять, думал бы уже потом.
Так что спасибо за пример. Буду учиться и подсматривать по вашей ссылке.
 
Очень медленная. Написал свою реализацию. Раньше esp8266 отдавала 2КБ файл за 2сек, а сейчас за 1сек максимум. Вебморды просто летают.
Я ж только учусь. И тут к вам сразу несколько просьб:
1. Показать ваш код, вашу реализацию
2. Давно задаюсь вопросом, как мерять скорость и вообще загруженность контролера и всего кода?
 
Я делаю через "...опу", но работает.
Все данные пишу в json файл. Страница его читает из flash памяти и заполняет форму. При изменении на странице данных файл перезаписывается.
Не для рекламы...
Никак не могу с вашим примером разобраться. Решил его на чуть позже оставить, ведь там главный прикол с реализации МЕШ. И вот тут у меня вообще сомнения берут, работает ли он так же с HTTP запросами, или там вообще как-то по-другому все строится.
 
При использовании asyncwebserver, так может выглядеть передача стиля:
Кстати, никогда не видел такую передачу стиля. Не все понял. В таком случае сам css нужен?

И ещё, возвращаясь к Третьякову. Подскажите, как сделать разделение скетча на вкладки?
 
Что вам мешает проверить temper, до того как вы его преобразуете в стринг?
Тут как бы логика в том, что если датчик не работает, не активен, то и данные температуры и влажности отдаваться не будут. Потому их можно чем-то заменить, например, строковой переменной. Для статуса датчика это работает, а для температуры и влажности - нет.
 
Возможно она выдает "Nan", когда датчик отключился.
Да, именно это и выдает. Как я понимаю это значит, что "Not a Number" , типа нет нужного числа или вне чисел, или не число. Но не понимаю, какое это имеет значение. Ведь все равно идет преобразование в строковое значение. И даже больше, у меня же специально для этого обьявляется:
Код:
  int temper;
  int vlag;
Тоесть по идее тут можно присваивать любые свои значения для переменных, хоть в буквенном выражении, хоть в числовом. И int уже потом переводится в стринг
 
Для меня Си не родной язык. Я его знаю (изучаю) ровно столько, сколько занимаюсь ардуино. И многие вещи не понятны.
Но вот поясните, зачем напр, этот пример?
При выводе в Сериал порт у меня тоже все нормально отрабатывает, пишет именно этот Nan. А вот на вэб страницу не выводит. Вернее выводит то максимальный число в диапазоне (2147483647), то какие-то другие числа. А нужно буквенное сообщение типа "Нет данных".
Как бы и в первом примере тоже нет никаких преобразований в строковую переменную. Как мне этот пример может помочь?
Как мне на вэбстранице вывести какое-то сообщение, вместо вывода бесконечности, ну или предельного числа в диапазоне (2147483647). Не знаю, как точно это число называется. Но это размеры диапазона float.
Можно переделать в такой вид:
Код:
  if (dht.getStatus() == 0)                                               // Если датчик передает данные 
    {
    state = "OK";                                                          //  то запоминаем его как OK
    temper = temperature;
    vlag = humidity;
    return String(state)+"+"+String(temper)+"+"+String(vlag);                                                 // возвращаем результат, преобразовав число в строку
    }
  else                                                                  // иначе
    {
    state = "NO";                                                          //  запоминаем его как NO
    return String(state) +"  No data off temperature "+" No dato off vlaga";
    }
Но на мой взгляд так не очень верно.
 
В нем есть решение проблемы, которая вам мешает.
В шапке этой темы я написал, что все что знал даже немного по теме - забыл, да ещё и закостенел. Вспоминаю с трудом, и мозги ворочаются тоже с трудом. Но понимаю, что в http запросах в таком виде передаются строковые значения. При этом абсолютно не понимаю, как в такой ситуации даже на основе ваших примеров это осуществить. По идее можно было бы использовать функцию isnan(), но не понимаю, как это сделать. Если это конечно то, о чем вы говорите.
 
Вот я и спрашивал, как мне поместить туда другой текстовый ответ, используя один RETURN
Практически решил
Код:
String get_all() {                                                 // Функция передачи всех данных
  String res;
  float humidity = dht.getHumidity();
  float temperature = dht.getTemperature();
 
res = (dht.getStatus() == 0) ? String("OK") : String("NO");
res += String("+") + (isnan(temperature) ? String("  No data  " ) : String(temperature));
res += String("+") + (isnan(humidity) ? String("  No data  " ) : String(humidity));
return res;
}
Только вот не получается округлить, напр, до одной цифры после запятой. Пробовал:
Код:
res += String("+") + (isnan(temperature) ? String("  No data  " ) : String(RoundTo(temperature, -1)));
Ругается, что
'RoundTo' was not declared in this scope
 
А есть такая функция в ардуино?
Взял из С++. Тут же вроде как основано на С++, но постоянно спотыкаюсь, не понимаю, что работает из С++, а что нет.
Сделал так:
Код:
String get_all() {                                                 // Функция передачи всех данных
  String res;
  float humidity = dht.getHumidity();
  float temperature = dht.getTemperature();

    res = (dht.getStatus() == 0) ? String("OK") : String("NO");
    res += String("+") + (isnan(temperature) ? String("  No data  " ) : String(int(round(temperature*10))/10)+","+int(round(temperature*10))%10);
    res += String("+") + (isnan(humidity) ? String("  No data  " ) : String(int(round(humidity*10))/10)+","+int(round(humidity*10))%10);
    return res;
}
Хотя, правда, ещё остался вопрос с русской кодировкой. В HTTP запросе забыл, как это делается.
 
Ну и http запрос с русской кодировкой:
Код:
      HTTP.on("/get_all", [](){                                                      // При HTTP запросе вида http://адрес есп/get_all
      HTTP.send(200, "text/plain; charset=utf-8", get_all());      // Отдаём клиенту код успешной обработки запроса, сообщаем, что формат ответа текстовый и возвращаем результат выполнения функции get_all
  });
И вспомнилось. Тут говорили, что есть 100500 способов сделать мою первую хотелку про один return. Может ещё кто-то подсказать варианты, чтоб можно было понять, куда мне стремиться со своим кодом?
 
Сверху Снизу