Как создать минимальный пример веб-сервера с выпадающим меню?

Я хочу создать меню из трех пунктов: Verizon, T-Mobile, AT&T. Поскольку в примере используются светодиоды, я сделаю то же самое, но с выпадающим меню для выбора из трех.

Я пытаюсь объединить эти два примера:

  1. https://randomnerdtutorials.com/esp8266-web-server
  2. https://www.w3schools.com/html/tryit.asp?filename=tryhtml_elem_select

За исключением случаев, когда вместо автомобилей используются операторы мобильной связи:

Я с трудом справляюсь с полиглотной частью программирования, совмещая в середине программу HTML и Arduino:

        // Заголовок веб-страницы
        client.println("<form action='/action_page.php'>");
        client.println("  <select id='providor' name='providor'>");
        client.println("    <option value='verizon'>Verizon</option>");
        client.println("    <option value='tmobile'>T-Mobile</option>");
        client.println("    <option value='atnt'>AT&T</option>");
        client.println("  </select>");
        client.println("  <input type='submit'>");
        client.println("</form>");
        
        if (providor==verizon)
          client.println("<p>Provider Selected: Verizon</p>");
        else if (providor==tmobile)
          client.println("<p>Provider Selected: T-Mobile</p>");
        else if (providor==atnt)
          client.println("<p>Provider Selected: AT&T</p>");    

Вот остальная часть программы:

/*

Based from 
https://randomnerdtutorials.com/esp8266-web-server

HTML tool
https://www.w3schools.com/html/tryit.asp?filename=tryhtml_elem_select
*/

#include <ESP8266WiFi.h>

// Замените своими сетевыми учетными данными
const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

// Установите номер порта веб-сервера на 80
WiFiServer server(80);

// Переменная для хранения HTTP-запроса
String header;

// Вспомогательные переменные для хранения текущего состояния вывода
//Строка вывода5State = "выключено";
//String output4State = "off";

// Назначаем выходные переменные контактам GPIO
const int output5 = 14;    // версия D5
const int output6 = 12;    // D6 мобильный
const int output7 = 13;    // D7 внимание

// Текущее время
unsigned long currentTime = millis();
// Прошлый раз
unsigned long previousTime = 0; 
// Определить время тайм-аута в миллисекундах (пример: 2000 мс = 2 с)
const long timeoutTime = 2000;

void setup() {
  Serial.begin(115200);
  // Инициализируем выходные переменные как выходные данные
  pinMode(output5, OUTPUT);
  pinMode(output6, OUTPUT);
  pinMode(output7, OUTPUT);  
  // Установка выходов на НИЗКИЙ уровень
  digitalWrite(output5, LOW);
  digitalWrite(output6, LOW);
  digitalWrite(output7, LOW);

  // Подключаемся к сети Wi-Fi с SSID и паролем
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // Распечатываем локальный IP-адрес и запускаем веб-сервер
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();
}

void loop()
{
  WiFiClient client = server.available();   // Прослушиваем входящих клиентов

  if (client) {                             // Если подключается новый клиент,
    Serial.println("New Client.");          // распечатываем сообщение в последовательный порт
    String currentLine = "";                // создаем строку для хранения входящих данных от клиента
    currentTime = millis();
    previousTime = currentTime;
    while (client.connected() && currentTime - previousTime <= timeoutTime) { // цикл, пока клиент подключен
      currentTime = millis();         
      if (client.available()) {             // если есть байты, которые нужно прочитать от клиента,
        char c = client.read();             // читаем байт, затем
        Serial.write(c);                    // распечатываем его на последовательном мониторе
        header += c;
        if (c == '\n') {                    // если байт является символом новой строки
          // если текущая строка пуста, значит, вы получили два символа новой строки подряд.
          // это конец HTTP-запроса клиента, поэтому отправьте ответ:
          if (currentLine.length() == 0) {
            // HTTP-заголовки всегда начинаются с кода ответа (например, HTTP/1.1 200 OK)
            // и тип контента, чтобы клиент знал, что произойдет, затем пустая строка:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();


            
            // включает и выключает GPIO
            if (header.indexOf("GET /5/on") >= 0) 
            {
              Serial.println("D5 on");
              output5State = "on";
              digitalWrite(output5, HIGH);
            }
            else if (header.indexOf("GET /5/off") >= 0) 
            {
              Serial.println("D5 off");
              output5State = "off";
              digitalWrite(output5, LOW);
            }
            else if (header.indexOf("GET /6/on") >= 0) 
            {
              Serial.println("D6 on");
              output4State = "on";
              digitalWrite(output6, HIGH);
            } 
            else if (header.indexOf("GET /6/off") >= 0) 
            {
              Serial.println("D6 off");
              output4State = "off";
              digitalWrite(output6, LOW);
            }
            else if (header.indexOf("GET /7/on") >= 0) 
            {
              Serial.println("D7 on");
              output4State = "on";
              digitalWrite(output7, HIGH);
            } 
            else if (header.indexOf("GET /7/off") >= 0) 
            {
              Serial.println("D7 off");
              output4State = "off";
              digitalWrite(output7, LOW);
            }
            // Отображение веб-страницы HTML
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // CSS для оформления кнопок включения/выключения
            // Не стесняйтесь изменять атрибуты background-color и font-size в соответствии с вашими предпочтениями.
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { background-color: #195B6A; border: none; color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button2 {background-color: #77878A;}</style></head>");

            // ЗДЕСЬ
            // ↓
            // ↓
            // ↓
            // ↓
              
            // Заголовок веб-страницы
            client.println("<form action='/action_page.php'>");
            client.println("  <select id='provider' name='providor'>");
            client.println("    <option value='verizon'>Verizon</option>");
            client.println("    <option value='tmobile'>T-Mobile</option>");
            client.println("    <option value='atnt'>AT&T</option>");
            client.println("  </select>");
            client.println("  <input type='submit'>");
            client.println("</form>");
            
            if (providor==verizon)
              client.println("<p>Provider Selected: Verizon</p>");
            else if (providor==tmobile)
              client.println("<p>Provider Selected: T-Mobile</p>");
            else if (providor==atnt)
              client.println("<p>Provider Selected: AT&T</p>");    
                
            // ↑
            // ↑
            // ↑
            // ↑
            
            client.println("</body></html>");
            
            // Ответ HTTP заканчивается еще одной пустой строкой
            client.println();
            // Выходим из цикла while
            break;
          } else { // если появилась новая строка, то очистим currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // если у вас есть что-то еще, кроме символа возврата каретки,
          currentLine += c;      // добавляем его в конец текущей строки
        }
      }
    }
    // Очищаем переменную заголовка
    header = "";
    // Закрываем соединение
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}

Есть предложения или решения по изменению или правильному коду?

, 👍-1

Обсуждение

что вы ожидаете от выпадающего меню?, @jsotola

Измените переменную «поставщика» на любую выбранную. И в этом случае включить соответствующий ему светодиод (должен гореть один из трех светодиодов)., @adamaero

поместите HTML-код раскрывающегося меню сразу после второй кнопки, @jsotola

Я использую <script>document.getElementById('providor').selectedIndex = 'tmobile';</script>, @Juraj

Посмотрите код в вашем посте. Он выделен синтаксисом, т.е. имеет цветовую кодировку в соответствии с грамматикой языка C++. Разве вы не видите что-то странное в цветах вокруг «ЗДЕСЬ»? Это индикатор синтаксиса, показывающий вашу синтаксическую ошибку., @Edgar Bonet

И далее в первом блоке кода. Ваши цитаты не совпадают повсюду., @Nick Gammon


2 ответа


0

Во-первых: будьте осторожны при сопоставлении цитат. Каждый строковый литерал следует писать как "бла, бла, бла", без неэкранированных двойных кавычек между. Обязательно используйте подсветку синтаксиса: она здесь именно для помогая вам обнаружить такого рода тривиальные ошибки программирования. Кроме того, для при написании очень длинных константных строк используйте «необработанный строковый литерал» C++. особенность: это упрощает задачу.

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

GET /action_page.php?providor=verizon HTTP/1.1
Some-Header-Name: some header value
Other-Header-Name: other header value
...

Вам следует проанализировать это, чтобы найти фактического поставщика между '=' символ и следующий пробел. Также избавьтесь от action_page.php, здесь это бесполезно.

Тогда несколько случайных замечаний:

  • Не пишите «поставщик»: правильное написание — «поставщик» (хотя в этом контексте вы можете предпочесть «перевозчик»).

  • В HTML-документе амперсанд должен быть записан в виде &amp;.

  • Чтобы HTML-страница была допустимой, необходим элемент <title>.

  • Удалите весь раздел кода под названием «включает GPIO и выключено»: это не имеет смысла в вашем контексте.

Теперь, чтобы проиллюстрировать эти моменты, я бы описал код, анализирующий запрос:

// Найдите поставщика по URL-адресу.
String provider;
const String provider_before = "GET /?provider=";
int request_begin = header.indexOf(provider_before);
if (request_begin < 0) {
    provider = "unknown";
} else {
    int provider_begin = request_begin + provider_before.length();
    int provider_end = header.indexOf(' ', provider_begin);
    provider = header.substring(provider_begin, provider_end);
}

А вот как бы я отправил страницу клиенту:

// В начале скетча (в глобальном контексте).
const char page_before_provider[] = R"rawstr(<!DOCTYPE html>
<html>
<head>
<title>Phone carrier selection</title>
[...]
<p>Provider Selected: )rawstr";
const char page_after_provider[] = "</p>\n</body>\n</html>\n";

// В том месте, куда должна быть отправлена страница.
client.print(page_before_provider);
if (provider == "verizon")
    client.print("Verizon");
else if (provider == "tmobile")
    client.print("T-Mobile");
else if (provider == "atnt")
    client.print("AT&amp;T");
else
    client.print(provider);
client.print(page_after_provider);
,

Меня смущает глобальная константа char. Это функция, которую я должен создать? Попробовал добавить что-то из этого, но довольно запутался: https://github.com/adamaero/WebServer/blob/main/polyglot/ESP8266dropDownMenu/ESP8266dropDownMenu.ino, @adamaero

@adamaero: Нет, не в функции. «Глобальный контекст» означает вне какой-либо функции. Это объявление константы, как и ssid, которое нужно поместить в начало программы. PS: не беспокойтесь об этом до тех пор, пока не решите первую, самую важную проблему: **согласовать ваши котировки**., @Edgar Bonet

Котировки совпали., @adamaero

Поскольку все текущие ответы мне не ясны, я попробую придумать свой с помощью const char page_before_provider[] = R"rawstr(<!DOCTYPE html> )rawstr";, @adamaero

@adamaero: 1. Считаете ли вы, что кавычки совпадают в client.println("<p>выбран оператор связи: T-Mobile" </ p > ");? 2. Если что-то неясно в этом ответе, сообщите, пожалуйста, что именно вам не понятно, поэтому я могу уточнить этот конкретный момент., @Edgar Bonet

Хорошо, теперь кавычки совпадают... в псевдокоде, который изначально ничего не делает., @adamaero


0

Если не считать синтаксических ошибок (вы не используете двойные кавычки в нескольких местах), веб-страницы так не работают.

Чтобы сделать что-то вроде вашей формы, вы:

  1. Отправьте форму примерно так же, как и вы.

  2.    if (providor==verizon)
           client.println("<p>Providor Selected: Verizon</p>");
    

    Проверить провайдера (провайдера?) тут же нельзя. Вы имеете в виду if (provider=="verizon")?

  3. Форма должна быть отправлена клиенту, пользователь должен ее заполнить и отправить обратно. Таким образом, вы не можете проверить, что они заполнили в строке или около того при отправке формы.

Итак, тестирование того, что они ввели, — это следующий шаг. Это немного сложно объяснить, не рассмотрев десятки абзацев. Советую прочитать, как работают веб-формы.


Вот аналогия:

Предположим, вы хотите написать в компанию и подать заявку на работу. Вы заполняете форму и отправляете ее. Через мгновение вы не проверите, получили ли вы работу. Форма должна быть отправлена в компанию. Кто-то должен это обработать. Затем они дают ответ. В ответе может быть сказано: «В отношении вашего заявления о приеме на работу от 11 сентября 2023 года вы были/не удовлетворены».

То же самое и с HTML. Форма, которую заполняет пользователь, является начальным шагом. Затем пользователь заполняет его. Это может быть час спустя. Затем они нажимают «Отправить», и вам возвращается ответ. Это время обработки ответа.

,