Почему функция server.on() из "ESPAsyncWebServer.h" выполняется на стороне setup(), а не на стороне loop()?

Я реализую пример, в котором 2 ESP32 обмениваются данными друг с другом, используя протокол HTTP. Один действует как Сервер, другой - как Клиент. В приведенных примерах мне интересно узнать об объявлении на сервере, поскольку его реализация объявлена в функции setup() (я использую стандартную Arduino IDE), которая должна иметь код, который выполняется только один раз, а не функцию loop() (по крайней мере, в этом примере), который должен обновлять вызов каждый цикл, но он остается пустым:

  Complete project details at https://RandomNerdTutorials.com/esp32-client-server-wi-fi/
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.
  
  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*/

// Импорт необходимых библиотек
#include "WiFi.h"
#include "ESPAsyncWebServer.h"

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

// Установите сетевые учетные данные вашей точки доступа
const char* ssid = "ESP32-Access-Point";
const char* password = "123456789";

/*#include <SPI.h>
#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 5*/

Adafruit_BME280 bme; // I2C
//Adafruit_BME280 bme(BME_CS); // аппаратный SPI
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // программный SPI

// Создать объект AsyncWebServer на порту 80
AsyncWebServer server(80);

String readTemp() {
  return String(bme.readTemperature());
  //возвращаемая строка(1.8 * bme.readTemperature() + 32);
}

String readHumi() {
  return String(bme.readHumidity());
}

String readPres() {
  return String(bme.readPressure() / 100.0F);
}

void setup(){
  // Последовательный порт для целей отладки
  Serial.begin(115200);
  Serial.println();
  
  // Настройка ESP в качестве точки доступа
  Serial.print("Setting AP (Access Point)…");
  // Удалите параметр пароля, если вы хотите, чтобы точка доступа (Точка доступа) была открыта
  WiFi.softAP(ssid, password);

  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(IP);

  server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readTemp().c_str());
  });
  server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readHumi().c_str());
  });
  server.on("/pressure", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readPres().c_str());
  });
  
  bool status;

  // настройки по умолчанию
  // (вы также можете передать объект проводной библиотеки, например &Wire2)
  status = bme.begin(0x76);  
  if (!status) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
  
  // Запустить сервер
  server.begin();
}
 
void loop(){
  
}

Вы можете увидеть объявления "server.on (....)" в части setup() кода и функцию empty loop () в конце.

server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readTemp().c_str());
  });

Итак, я предполагаю, что сервер уже работает в фоновом режиме при выполнении server.begin() и его не нужно вызывать явно, но это все равно меня смущает, потому что в loop() нет явного объявления, такого как server.loop() или что-то подобное. Можете ли вы объяснить, как это работает и почему нет необходимости явно объявлять функцию в цикле ()?

Спасибо -EZ

, 👍1


3 ответа


1

Server.on() задает функцию обратноговызова. Это регистрирует функцию, которая будет выполняться, когда определенное событие произойдет позже. В данном случае это происходит, когда определенный URL-адрес запрашивается с помощью метода GET.

Каждый раз, когда делается запрос на веб-сервер, список зарегистрированных событий "включено" проверяется на предмет того, которое соответствует запросу, затем выполняется связанная функция (в данном случае лямбда-функция).

И все это выполняется асинхронно (отсюда и название) в фоновом режиме в другом потоке, поэтому вам не нужно ничего явно делать.

,

Спасибо, я так и думал, но мне нужно было подтверждение. Асинхронная часть, похоже, выполняет трюк для операции обратного вызова без явного упоминания., @Ed Zamper


1

Конструкция с лямбда - функцией

server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", readTemp().c_str());
  });

это то же самое, что и

server.on("/temperature", HTTP_GET, onGetTemperature);

где onGetTemperature - это функция, определенная перед void setup()

void onGetTemperature(AsyncWebServerRequest *request) {
  request->send_P(200, "text/plain", readTemp().c_str());
}

Не могли бы вы в этом случае спросить, почему он "выполняется в setup()?"

,

Ну, мне кажется, что вы просто отделяете лямбда-функцию от области setup() и вызываете ее изнутри setup() , но мой вопрос был бы не "почему он выполняется в setup", а скорее, почему функция server.on явно не вызывается изнутри функция цикла? Который, я полагаю, Majenko только что подтвердил, что это обратный вызов () и, следовательно, его не требуется вызывать явно, поскольку он запускается сервером и сравнивается с одной из нескольких функций "вкл", которые могут быть перечислены в setup() ., @Ed Zamper


0

Да, server.on() - это функция обратного вызова, но это еще не вся история.

Вы также не видите ничего, связанного с сервером в loop() , потому что где-то в серверной части всего этого кода, вероятно, в server.begin(), сервер подключает свой собственный обратный вызов или ISR к сетевому стеку ESP. Теперь, когда ESP получает сообщение по сети нужного типа, сетевой стек перенаправляет сообщение во внутренний обратный вызов сервера, после чего сервер проверяет просмотренную страницу и выполняет соответствующую функцию server.on, указанную в setup().

У меня еще не было возможности разобраться, как именно обратный вызов сервера привязан к сетевому стеку, но именно так он работает асинхронно. Либо он изначально активируется аппаратным ISR Wi-Fi (что позволяет ему временно приостановить выполнение вашего кода для запуска сервера), либо он запускается небольшим разделом кода, похожим на планировщик, который выполняется вне loop() (это означает, что между итерациями loop()он может быть управляющим сервером. Вероятно, это именно тот случай, поскольку именно в этом случае некоторые части сетевого стека обрабатываются в любом случае).

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

,

он использует обратные вызовы SDK, @Juraj