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

Вопрос Как организовать вызов двух функций с разным циклом выполнения?

Cadil

New member
Используя примеры из Arduino IDE, составил скетч термометра. Информация о температуре отправляется сервер с периодом 1 сек и на е-мейл каждый час. Периодичность отправки определяется delay(). Команды в скетче выполняются последовательно и после отправки письма процесс замораживается на 1 час, естественно, в это время браузер выдает ошибку ВРЕМЯ ОЖИДАНИЯ ИСТЕКЛО ...
Код:
//Сервер со статическим адресом 192.168.1.109
//отправка письма на адрес cadil@ukr.net, 1 час

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <OneWire.h>
OneWire  ds(2);
#include <Arduino.h>

#include <ESP8266WiFiMulti.h>
#include <ESP8266HTTPClient.h>
#define USE_SERIAL Serial
ESP8266WiFiMulti WiFiMulti;

const char *ssid = "Best_VB";
const char *password = "vbvb3591";
MDNSResponder mdns;

ESP8266WebServer server ( 80 );


byte addr[8];
float celsius;

float getTemp(){
  byte data[12];

  if (!ds.search(addr)) {
    Serial.println("No more addresses.");
    while(1);
  }
   ds.reset_search();
  if (OneWire::crc8(addr, 7) != addr[7]) {
      Serial.println("CRC is not valid!");
      while(1);
  }

 
  ds.reset();         
  ds.select(addr);     
  ds.write(0x44);   
  delay(1000);
 
  ds.reset();
  ds.select(addr); 
  ds.write(0xBE);       

  for (int i = 0; i < 9; i++) {        
    data[i] = ds.read();
  }

  int raw = (data[1] << 8) | data[0];
  if (data[7] == 0x10) raw = (raw & 0xFFF0) + 12 - data[6];
  return raw / 16.0;
 
}


const int led = 13;

void handleRoot() {
  digitalWrite ( led, 1 );
  char temp[400];
celsius = getTemp();
Serial.println(celsius);
int tepm= celsius;
  snprintf ( temp, 400,

"<html>\
  <head>\
    <meta charset='UTF-8' >\
    <meta http-equiv='refresh' content='5'/>\
    <title>Термометр</title>\
    <style>\
      body { background-color: #ffffdf; font-family: Arial, Helvetica, Sans-Serif; Color: #bb0000; }\
    </style>\
  </head>\
  <body>\
    <center><h1>Температура:  %d *C</h1></center>\
    <center><p> Tested! </p></center>\
  </body>\
</html>",tepm
  );
  server.send ( 200, "text/html", temp );
  digitalWrite ( led, 0 );
}

void handleNotFound() {
  digitalWrite ( led, 1 );
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += ( server.method() == HTTP_GET ) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";

  for ( uint8_t i = 0; i < server.args(); i++ ) {
    message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n";
  }

  server.send ( 404, "text/plain", message );
  digitalWrite ( led, 0 );
}

void browser ( ) {
IPAddress ip(192, 168, 1, 109); //Node static IP
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

  pinMode ( led, OUTPUT );
  digitalWrite ( led, 0 );
  Serial.begin ( 115200 );
  WiFi.begin ( ssid, password );
  WiFi.config(ip, gateway, subnet);   ////Node static IP
  Serial.println ( "" );

  // Wait for connection
  while ( WiFi.status() != WL_CONNECTED ) {
    delay ( 500 );
    Serial.print ( "." );
  }

  Serial.println ( "" );
  Serial.print ( "Connected to " );
  Serial.println ( ssid );
  Serial.print ( "IP address: " );
  Serial.println ( WiFi.localIP() );

  if ( mdns.begin ( "esp8266", WiFi.localIP() ) ) {
    Serial.println ( "MDNS responder started" );
  }

  server.on ( "/", handleRoot );
  server.on ( "/test.svg", drawGraph );
  server.on ( "/inline", []() {
    server.send ( 200, "text/plain", "this works as well" );
  } );
  server.onNotFound ( handleNotFound );
  server.begin();
  Serial.println ( "HTTP server started" );
  ////

    USE_SERIAL.begin(115200);
   // USE_SERIAL.setDebugOutput(true);

    USE_SERIAL.println();
    USE_SERIAL.println();
    USE_SERIAL.println();

    for(uint8_t t = 4; t > 0; t--) {
        USE_SERIAL.printf("[SETUP] WAIT %d...\n", t);
        USE_SERIAL.flush();
        delay(1000);
    }

    WiFiMulti.addAP("Best_VB", "vbvb3591");
}

void mailer() {
    // wait for WiFi connection
    if((WiFiMulti.run() == WL_CONNECTED)) {

        HTTPClient http;
float celsius;
celsius = getTemp();      //?
String adresse;    
adresse = "cadil@ukr.net";

        USE_SERIAL.print("[HTTP] begin...\n");
        // configure traged server and url
      http.begin("http://skorovoda.in.ua/php/un35.php?mymail="+String(adresse)+"&t="+String(celsius));
 
      USE_SERIAL.print("[HTTP] GET...\n");
        // start connection and send HTTP header
        int httpCode = http.GET();

        // httpCode will be negative on error
        if(httpCode > 0) {
            // HTTP header has been send and Server response header has been handled
            USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode);

            // file found at server
            if(httpCode == HTTP_CODE_OK) {
                String payload = http.getString();
                USE_SERIAL.println(payload);
            }
        } else {
            USE_SERIAL.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
        }

        http.end();
    }

// delay(60000);    //1 минута
delay(3600000);    //1 час
}

void drawGraph() {
  String out = "";
  char temp[100];
  out += "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"400\" height=\"150\">\n";
  out += "<rect width=\"400\" height=\"150\" fill=\"rgb(250, 230, 210)\" stroke-width=\"1\" stroke=\"rgb(0, 0, 0)\" />\n";
  out += "<g stroke=\"black\">\n";
  int y = rand() % 130;
  for (int x = 10; x < 390; x+= 10) {
    int y2 = rand() % 130;
    sprintf(temp, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" stroke-width=\"1\" />\n", x, 140 - y, x + 10, 140 - y2);
    out += temp;
    y = y2;
  }
  out += "</g>\n</svg>\n";

  server.send ( 200, "image/svg+xml", out);
}
//////////////////////////////////////////
void setup () {
browser ();
}
void loop () {
  mdns.update();
  server.handleClient();
/////
mailer();
}
Казалось бы можно решить эту задачу через цикл, в котором функция отправки письма вызывается 1 раз через 360 000. Вот примерчик для иллюстрации, только j <360 000 вместо 5:
Код:
#include <iostream>
using namespace std;

int main()
{
    setlocale(LC_ALL, "rus");
    int symbInLines = 0;
    char ms = 0;

    cout << endl;
    {
 
        {
                for (int j = 1; j <5; j++)
            {
                cout << "browser ";
            }
            {
                cout << "messenger ";
            }
     
        }
        cout << endl;
    }
    cout << "Введите символ: ";
    cin >> ms;
    cout << endl;
    return 0;
}

Но тут другая проблема: команды для браузера прописаны void setup (), а отправки сообщений - в void loop (). Я пытался перенести отправку сообщений в setup () и наоборот - ничего не получается, поскольку тут замешаны еще и опрос датчика, и подключение к точке доступа Wi-Fi, и т.п. ...

Как решить эту проблему?
 
Последнее редактирование:

CodeNameHawk

Moderator
Команда форума
В ваш код не вникал, но подобное реализуется например так :
В цикле
процедура_которая_выполняется_через_каждую_секунду
задержка_1_сек
увеличение_переменной_прошедших_секунд на единицу
если переменная_прошедших_секунд больше заданной задержки выполняем вторую_процедуру и обнуляем переменную_прошедших_секунд

Второй вариант использовать millis()
 

Cadil

New member
В цикле
процедура_которая_выполняется_через_каждую_секунду
задержка_1_сек
увеличение_переменной_прошедших_секунд на единицу
если переменная_прошедших_секунд больше заданной задержки выполняем вторую_процедуру и обнуляем переменную_прошедших_секунд
Верно! Этот алгоритм только другими словами описан у меня в примере выше. Но как его реализовать, когда процедуру_1 и процедуру_2 выделить в чистом виде затруднительно?
 

Сергей_Ф

Moderator
Команда форума
@Cadil они у вас выделены в чистейшем виде. Никаких проблем нет и переносить ничего не надо.
Отправка письма - mailer();
Обработка запросов веб - handleClient().

Просто вызывайте mailer раз в час и всё.
 

pazyzy

New member
Выше вам @CodeNameHawk все верно ответил.
Я не эксперт в этих библиотеках, поэтому предполагаю на основе методов и их названий.
server.handleClient() - отвечает за обработку запроса на web server (надеюсь, что он не ждет, если от клиента ничего не пришло).
mailer - ваш метод отправки почты.

Вызывая sleep в методе mailer, вы замораживаете систему, и поэтому ваш web server не отвечает браузеру на запрос.
Попробуйте сделать следующее:
1. В строке 199 убрать строку delay(3600000);
2. Метод loop будет выглядеть так
Код:
int seconds_count = 0;
void loop () {
  mdns.update();
  server.handleClient();
/////
if(seconds_count >= 3600)
{
mailer();
seconds_count = 0;
}
seconds_count++;
sleep(1000);
}
Таким образом loop будет работать каждую секунду, позволяя работать обработчику webServer, а почта будет отправляться раз в час.
 

Сергей_Ф

Moderator
Команда форума
@pazyzy зачем тут sleep(1000)? Для проблем работы с обработкой http?
Есть millis. Его и надо пользовать.
 

pazyzy

New member
@pazyzy зачем тут sleep(1000)? Для проблем работы с обработкой http?
Есть millis. Его и надо пользовать.
Про проблемы с обработкой http не в курсе. Просто так использовал, т.к. @Cadil уже использовал sleep в примере, поэтому sleep (или delay) было проще понять и объяснить в этом куске кода.
Можно сделать тоже самое с использованием millis.

Относительно данного примера (если игнорировать возможную задержку ответа http serverа до одной секунды) есть какие-то преимущества использования millis vs sleep?
 

Сергей_Ф

Moderator
Команда форума
какие-то преимущества использования millis vs sleep?
1.не вести собственный счётчик
2.ваш счётчик имеет непредсказуемый шаг, поскольку помимо задержки в секунду будут ещё задержки обработки http.
3.возможность обработки запросов с нескольких клиентов.
 

pazyzy

New member
1.не вести собственный счётчик
2.ваш счётчик имеет непредсказуемый шаг, поскольку помимо задержки в секунду будут ещё задержки обработки http.
1. Нужно хранить время последнего выполнения метода для сравнения со счетчиком, плюс помнить о преполнении (раз в 50 дней?).
2. Тут согласен, будет точнее. Однако все равно все может замереть на несколько секунд при отправке уведомления на почту. Все зависит на сколько нужна какая точность.

@Cadil итого: sleep использовать можно, и все будет работать, но best practice будет использовать millis.
 

Сергей_Ф

Moderator
Команда форума
Нужно хранить время последнего выполнения метода для сравнения со счетчиком, плюс помнить о преполнении (раз в 50 дней?).
Если это сложно, то можно воспользоваться библиотекой Ticker и ничего не хранить и не проверять.
 

Cadil

New member
@Cadil они у вас выделены в чистейшем виде. Никаких проблем нет и переносить ничего не надо.
Отправка письма - mailer();
Обработка запросов веб - handleClient().
Спасибо! Обработка запросов веб - handleClient() - ключевая мысль, которая расставила все по своим местам.
 

Cadil

New member
Попробуйте сделать следующее:
1. В строке 199 убрать строку delay(3600000);
2. Метод loop будет выглядеть так
....
Искренне благодарю за проделанную работу! Скетч с учетом ваших предложений - работает. Единственное, пришлось заменить функцию sleep() (не задекларирована) на delay(). Хотя, как я понял, и sleep() и delay() в этом случае не есть хорошо...
Успехов!
 
Сверху Снизу