Вэбсокет и извращения над ним.

Adolf_Balalaykin
Offline
Зарегистрирован: 01.02.2021

Добрыйго времени суток!

Как пример привожу код управление одной кнопкой про протоколу websocket. Хотелось бы поизвращатся над ним так, что бы получить более широкие возможности и понять как это рабтает. Это очень бы пригодилось в разных проектах и наверное не только мне. И так, сам исходник.

Код:

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Replace with your network credentials
const char* ssid = "ssid";
const char* password = "******";

bool ledState = 0;

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");

void notifyClients() {
  ws.textAll(String(ledState));
}

void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
  AwsFrameInfo *info = (AwsFrameInfo*)arg;
  if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
    data[len] = 0;
    if (strcmp((char*)data, "toggle") == 0) {
      ledState = !ledState;
      notifyClients();
    }
  }
}

void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
             void *arg, uint8_t *data, size_t len) {
  switch (type) {
    case WS_EVT_CONNECT:
      Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
      break;
    case WS_EVT_DISCONNECT:
      Serial.printf("WebSocket client #%u disconnected\n", client->id());
      break;
    case WS_EVT_DATA:
      handleWebSocketMessage(arg, data, len);
      break;
    case WS_EVT_PONG:
    case WS_EVT_ERROR:
      break;
  }
}

void initWebSocket() {
  ws.onEvent(onEvent);
  server.addHandler(&ws);
}

String processor(const String& var){
  Serial.println(var);
  if(var == "STATE"){
    if (ledState){
      return "ON";
    }
    else{
      return "OFF";
    }
  }
  return String();
}

void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);

  pinMode(25, OUTPUT);
  digitalWrite(25, LOW);
 
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print ESP Local IP Address
  Serial.println(WiFi.localIP());

  initWebSocket();

  // Инициализируем SPIFFS:
  if (!SPIFFS.begin(true))
  {
  //   Serial.println("При монтировании SPIFFS произошла ошибка");
    return;
  }

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(SPIFFS, "/index.html", "text/html", false, processor); });

  // Start server
  server.begin();
}

void loop() {
  ws.cleanupClients();
  digitalWrite(25, ledState);
}

JS

  var gateway = `ws://${window.location.hostname}/ws`;
  var websocket;
  window.addEventListener('load', onLoad);
 
// Инициализация обработчика нажатий кнопок:  
  function onLoad(event) {
	initWebSocket();
	initButton(); 
  }
 
  function initWebSocket() {
    console.log('Попытка открыть соединение с веб-сокетом...');
    websocket = new WebSocket(gateway);
    websocket.onopen    = onOpen;
    websocket.onclose   = onClose;
    websocket.onmessage = onMessage;
  }
 
  function onOpen(event) {
    console.log('Соединение открыто');
  }
 
  function onClose(event) {
   console.log('Соединение закрыто');
   setTimeout(initWebSocket, 2000);
  }
 
  function onMessage(event) {
    switch(event.data)
    {
     case '0': document.getElementById("state1").innerHTML = "OFF"; break
     case '1': document.getElementById("state1").innerHTML = "ON"; break 
     }
  }
 
  function initButton() {
   document.getElementById('button').addEventListener('click', toggle1);
  }

  function toggle1() {
   websocket.send('toggle');
  }


HTML

<!DOCTYPE HTML><html>
<head>
  <title>ESP Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
 
<title>ESP Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
</head>
<body>
  <div class="topnav">
    <h1>ESP WebSocket сервер</h1>
  </div>
  <div class="content">
    <div class="card">
      <h2>Output - GPIO 2</h2>
      <p class="state">state: <span id="state">%STATE%</span></p>
      <p><button id="button" class="button">Toggle</button></p>
    </div>
  </div>
</body>
</html>

Эксперимент №1:

Сделать так, что бы кнопка срабатывала не только от нажатия, но и от показания датчика температуры. Скажем если температура превышает 25 гр. (будем считать что у нас универсальный датчик BME280, необходимые библиотеки типа установлены).

Заменить условие из строки 23 как указано ниже не проканает.

// if (strcmp((char *)data, "toggle") == 0) // заменить это
if (bme.readTemperature() > 25) == 0)       // на это

Хетелось бы понять как эта строка работает и как правильно совместить эти два условия?

 

 

 

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

Использовать логическое «или» (||), так в одном if оба условия объединишь.

Kakmyc
Offline
Зарегистрирован: 15.01.2018

Никак не работает. Во второй строке не хватает круглой скобки. А по первой, должно сравнивать пришедшую строку со строковым литералом "toogle", если есть совпадение, тогда выражение будет истинным, т.е ==0.

Если нужно заменить, тогда так должно быть: if (bme.readTemperature() > 25) Если совместить, то оба условия через || или or

Как то так

if (!strcmp((char *)data, "toggle") ||(bme.readTemperature() > 25) ) 

 

 

 

 

Adolf_Balalaykin
Offline
Зарегистрирован: 01.02.2021

Kakmyc пишет:

Никак не работает. Во второй строке не хватает круглой скобки. А по первой, должно сравнивать пришедшую строку со строковым литералом "toogle", если есть совпадение, тогда выражение будет истинным, т.е ==0.

Если нужно заменить, тогда так должно быть: if (bme.readTemperature() > 25) Если совместить, то оба условия через || или or

Как то так

if (!strcmp((char *)data, "toggle") ||(bme.readTemperature() > 25) ) 

Спасибо! С логическим оператором (или) всё ясно. Но до него даже очередь не дошла. У меня даже тупо замена не сработала.

    if (bme.readTemperature() > 25)
    {
      ledState6 = !ledState6;
      notifyClients6();
    }

Компилистя без ошибок, но условие не срабатывает. Может это связано с тем, что условие if (bme.readTemperature() > 25) спрятано внутри функции void handleWebSocketMessage?

Kakmyc
Offline
Зарегистрирован: 15.01.2018

bme.readTemperature() вообще никаким местом не связано с работой по сети.
Я его и в приведенном коде не наблюдаю

Возможно это связано с тем, что температура окружающей среды <25 ?

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

Скорее всего связано это с тем, что обсуждается код, который кроме ТС никто в глаза не видел )))

Я имею ввиду код с датчиком.

Adolf_Balalaykin
Offline
Зарегистрирован: 01.02.2021


BOOM пишет:

Скорее всего связано это с тем, что обсуждается код, который кроме ТС никто в глаза не видел )))

Я имею ввиду код с датчиком.

Конечно с ним! Исправляюсь, привожу код с датчиком.


#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Библиотеки для BME280
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

// Replace with your network credentials
const char* ssid = "ssid";
const char* password = "******";

bool ledState = 0;

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");

// BME280 - Универсальный датчик
#define SEALEVELPRESSURE_HPA (1013.25)
Adafruit_BME280 bme;
String getTemperature()
{
  float temp = bme.readTemperature();
   Serial.print("BME280- Температура: ");
   Serial.print(IN4);
   Serial.print(" °C ");
  return String(bme.readTemperature());
}

void notifyClients() {
  ws.textAll(String(ledState));
}

void handleWebSocketMessage(void *arg, uint8_t *data, size_t len) {
  AwsFrameInfo *info = (AwsFrameInfo*)arg;
  if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) {
    data[len] = 0;
    if (!strcmp((char *)data, "toggle") ||(bme.readTemperature() > 25) )
      {
      ledState = !ledState;
      notifyClients();
      }
    }
  }

void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
             void *arg, uint8_t *data, size_t len) {
  switch (type) {
    case WS_EVT_CONNECT:
      Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
      break;
    case WS_EVT_DISCONNECT:
      Serial.printf("WebSocket client #%u disconnected\n", client->id());
      break;
    case WS_EVT_DATA:
      handleWebSocketMessage(arg, data, len);
      break;
    case WS_EVT_PONG:
    case WS_EVT_ERROR:
      break;
  }
}

void initWebSocket() {
  ws.onEvent(onEvent);
  server.addHandler(&ws);
}

String processor(const String& var){
  Serial.println(var);
  if(var == "STATE"){
    if (ledState){
      return "ON";
    }
    else{
      return "OFF";
    }
  }
  return String();
}

void setup(){
  // Serial port for debugging purposes
  Serial.begin(115200);

  pinMode(25, OUTPUT);
  digitalWrite(25, LOW);
 
  // Подключение к Wi-Fi сети  
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  // Print ESP Local IP Address
  Serial.println(WiFi.localIP());

  initWebSocket();

  // Инициализируем SPIFFS:
  if (!SPIFFS.begin(true))
  {
  //   Serial.println("При монтировании SPIFFS произошла ошибка");
    return;
  }

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(SPIFFS, "/index.html", "text/html", false, processor); });

  // Start server
  server.begin();
}

void loop() {
  ws.cleanupClients();
  digitalWrite(25, ledState);
  
   // Инициализация датчика BME280
  if (!bme.begin(0x76))
  {
   Serial.println("Не обнаружен датчик BME280, проверить подключение!");
    while (1);
  }
  Serial.print(bme.readTemperature());
  Serial.println(" *C");
}

Из loop в последовательный монитор температура выводится. Услувие if (bme.readTemperature() > 23) не опрашивается.

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

Потому что handleWebSocketMessage() не вызывается. Значит нужно иначе, например в loop’е.

Adolf_Balalaykin
Offline
Зарегистрирован: 01.02.2021

BOOM пишет:

Потому что handleWebSocketMessage() не вызывается. Значит нужно иначе, например в loop’е.

А куда деватся? Пришлось в loop-е мудрить. Только код малость громоздкий получается для такой маленькой задачки. Да и некоторые команды приходится дублировать, которые уже есть выше. И всё из-за того что внутри handleWebSocketMessage() не вызывается, то что мне так необходимо.

   // Порог включения
 if ((bme.readTemperature() > 25) && !triggerActive1)
    {
     ws.textAll(String(!ledState)); // отправляем статус кнопки "ON"
     String message = String("Верхняя - t° выше порога. Текущая: ") + String (bme.readTemperature()) + String("C");
     Serial.println(message);
     triggerActive1 = true;
     digitalWrite(ledState, HIGH);
    }

    // Порог выключения
    if ((bme.readTemperature() < 25) && triggerActive1)
   {
     ws.textAll(String(ledState)); // отправляем статус кнопки "OFF"
     String message = String("Верхняя - t° ниже порога. Текущая: ") + String(bme.readTemperature()) + String("C");
     Serial.println(message);
     triggerActive1 = false;
     digitalWrite(ledState, LOW);
    }

Слушай Бум, а можно как нибудь извратится так, что бы bme.readTemperature() > 25 опросить в loop'е, а результат сравнения отправить на исполнение выше, а именно в handleWebSocketMessage() ? 

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

А смысл отправки этой? Все равно хэндлер только когда ты через вэб работаешь вызывается.

Adolf_Balalaykin
Offline
Зарегистрирован: 01.02.2021

BOOM пишет:

А смысл отправки этой? Все равно хэндлер только когда ты через вэб работаешь вызывается.

Смысл в том, что вместо всего этого нагромождения достаточно быбло бы в loop'e опросить это 

if (bme.readTemperature() > 25) 

При выполнении условия, отправить в хэндлер это

 ledState = !ledState;
 notifyClients();

Тогда не понадобится весь этот громоздкий код что я привел чуть выше. Ну да ладно. Переходим к следущему извращению :)

Эксперимент №2:

Теперь прикрутил к нему таймер на миллис.

ledState = false
uint32_t timer
String defTime3 = "8";   // Периодичность включения таймера по умолчанию (в часах)
String defTime3_1 = "4"; // Длительность работы таймера по умолчанию (в часах)

void loop() {
   if (millis() / 3600000L - timer >= (ledState ? defTime3_1.toFloat() : defTime3.toFloat()))
  {
ws.textAll(String(!ledState));      // отправляем статус кнопки "ON"
timer1 = millis() / 3600000L;       // сброс таймера раз в час
ledState = !ledState;
digitalWrite(ledPin, ledState); 
Serial.println(millis() / 1000L);
  }  
}

Таймер работает нормально. Может туплю, но очень хочется знать как подсчитать остаток времени до срабатывания таймера? Хочу этот остаток времени вывести в переменную и в дальнейшем отправить на вэб. панель или последовательный монитор например. Миллис, начинает отсчет с момента загрузки кода. Контроллер то знает, включили- пора считать. А мне как узнать когда его включили? :)

 

 

sadman41
Offline
Зарегистрирован: 19.10.2016

А как вы узнаёте, сколько яйцо варилось?

Logik
Offline
Зарегистрирован: 05.08.2014

sadman41 пишет:

А как вы узнаёте, сколько яйцо варилось?

Так видно ж - если покраснело, то мало варилось, а если больно - варилось долго.

Теперь тема действительно про извращения становится. 

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

Adolf_Balalaykin пишет:

BOOM пишет:

А смысл отправки этой? Все равно хэндлер только когда ты через вэб работаешь вызывается.

Смысл в том, что вместо всего этого нагромождения достаточно быбло бы в loop'e опросить это 

if (bme.readTemperature() > 25) 

При выполнении условия, отправить в хэндлер

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

Adolf_Balalaykin
Offline
Зарегистрирован: 01.02.2021

Logik пишет:

sadman41 пишет:

А как вы узнаёте, сколько яйцо варилось?

Так видно ж - если покраснело, то мало варилось, а если больно - варилось долго.

Теперь тема действительно про извращения становится. 

Блин Лоджик, ну ты как всегда в своем репертуаре. Опять заставляешь меня лишние буквы писать. :) Допустим у тебя таймер в режиме ожидания и тебе как пользователю важно знать когда он сработает! Будешь в голове держать время включения контроллера и высчитывать разницу? А так на вэб панели былобы видно, сколько времени осталось до включения. Это очень даже актуально, когда речь идет о больших промежутках времени.
 
sadman41
Offline
Зарегистрирован: 19.10.2016

Именно так это и реализуется - через вычисление разницы.

А вот ваши фантазии про какие-то режимы ожидания таймера на миллис, включение и выключения его и пр. в контексте Wiring не имеют ничего общего с реальностью.