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

Server Send Events на ESP

Melandr

Member
Добрый вечер!
Разбираюсь с технологией SSE. Сделал пример, в принципе работает. Но наблюдаются разрывы соединения буквально каждые 3-4 секунды. Потом соединение поднимается, но заметны фризы из-за обрывов соединения
Ниже код ESP и html
Код:
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

#include "OTA.h"
#include "index_html.h"

#include "ets_sys.h"
#include "gpio.h"
#include "user_interface.h"

#define INTC_EDGE_EN  (*(volatile uint32_t *)0x3FF00004)
#define TIMER_LOAD (*(volatile uint32_t *)0x60000600)
#define TIMER_COUNT (*(volatile uint32_t *)0x60000604)
#define TIMER_CTRL (*(volatile uint32_t *)0x60000608)
#define TIMER_INT  (*(volatile uint32_t *)0x6000060c)
#define GPIO_OUT_W1TS (*(volatile uint32_t *)0x60000304)
#define GPIO_OUT_W1TC (*(volatile uint32_t *)0x60000308)
#define GPIO_OUTP (*(volatile uint32_t *)0x60000300)
#define GPIO_STATUS (*(volatile uint32_t *)0x6000031C)
#define GPIO_PIN2_CFG (*(volatile uint32_t *)0x60000330)
#define GPIO_STATUS_W1TC (*(volatile uint32_t *)0x60000324)
#define WDEV_NOW() (*(volatile uint32_t *)0x3ff20c00)

#define TIME_PULSE 50
#define MAX_DIMMING_VALUE 100
#define MIN_DIMMING_VALUE 0

#define HOST_NAME "remotedebug"

const char* ssid = "ASUS";
const char* password = "gCU8YNZs";
//const char* ssid = "SamsungS9+";
//const char* password = "qwert789";

static const int GPIO_OUT = 2; // GPIO2
static const int GPIO_IN = 1; // GPIO1
static const int GPIO_IN_SENS = 3; // GPIO3

uint32_t volatile dimDelay = 50 * 5; // in 0.2 us (0x007fffff max), min 12*5
uint32_t volatile pulseDelay = TIME_PULSE * 5; // in 0.2 us (0x007fffff max), min 12*5

uint8_t volatile targetSpeed = 15;    //требуемое значение скорости в процентах
uint8_t volatile currentSpeed = 0;    //текущее значение скорости в процентах

volatile unsigned long countZeroCross = 0;
volatile unsigned long countPeriod = 0;
volatile unsigned long countSysTimer = 0;
volatile unsigned long period = 0;
unsigned long lastMillis = 0;
unsigned long lastPeriodTime = 0;
unsigned long uptime = 0;
unsigned long previousMillis = 0;     // храним крайнее обновление сенсоров
const long interval = 10000;          // считываем значение с датчиков каждые 10 секунд

typedef enum {
  OFF,
  ON
} eState;

eState motorState = ON;

String outputState() {
  if (motorState) {
    return "checked";
  }
  else {
    return "";
  }
  return "";
}

//переменные для асинхронного вэб-сервера
const char* PARAM_INPUT_1 = "state";
const char* PARAM_INPUT_2 = "value";

String dimmerSliderValue = String(targetSpeed);
String outputStateValue = outputState();

// создаем объект AsyncWebServer на 80-ом порту
AsyncWebServer server(80);
AsyncEventSource events("/events");

// Replaces placeholder with button section in your web page
String processor(const String& var) {
 
  if (var == "BUTTONPLACEHOLDER") {
    String buttons = "";
    outputStateValue = outputState();
    buttons += "outputStateValue";
    return buttons;
  }
  else if (var == "DIMMERVALUE") {
    return dimmerSliderValue;
  }
  else if (var == "UPTIME") {
    return String(uptime);
  }

  return String();
}

void ICACHE_RAM_ATTR GPIOs_intr_handler(void *arg) {
  (void)arg;
  uint32_t tmp = GPIO_STATUS;

  if (tmp & BIT(GPIO_IN)) {
    TIMER_LOAD = dimDelay;
    TIMER_CTRL = 4 | BIT(7); // TM_DIVDED_BY_16, NO TM_AUTO_RELOAD_CNT, TM_ENABLE_TIMER
    countZeroCross++;
  }
  if (tmp & BIT(GPIO_IN_SENS)) {
    period = millis() - lastPeriodTime;
    lastPeriodTime = millis();
    countPeriod++;
  }
  GPIO_STATUS_W1TC = tmp;
}

void ICACHE_RAM_ATTR TIMER_intr_cb(void *arg) {
  (void)arg;
  if (GPIO_OUTP & BIT(GPIO_OUT)) {      //если симистор включен
    GPIO_OUT_W1TC = BIT(GPIO_OUT);      //выключаем симистор
    TIMER_CTRL = 0; // stop timer
    //gpio_pin_intr_state_set(GPIO_IN, GPIO_PIN_INTR_POSEDGE);
    //gpio_pin_intr_state_set(GPIO_IN_SENS, GPIO_PIN_INTR_POSEDGE);
  } else {
    GPIO_OUT_W1TS = BIT(GPIO_OUT);    //если симистор выключен, включаем
    TIMER_LOAD = pulseDelay;      //загружаем в таймер значение времени импульса открытия
  }
  countSysTimer++;
}

void dimmer_stop() {
  ets_isr_mask(BIT(ETS_FRC_TIMER1_INUM) | BIT(ETS_GPIO_INUM)); // запретить прерывания GPIOs & Timer0
  GPIO_OUT_W1TC = BIT(GPIO_OUT);
  TIMER_CTRL = 0; // stop timer
}

void dimmer_start() {
  dimmer_stop();
  TIMER_COUNT = 0;
  /*  PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_GPIO1);
    PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2);
    PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_GPIO3);
    gpio_output_set(0, 0, BIT(GPIO_OUT), BIT(GPIO_IN));
    gpio_output_set(0, 0, 0, BIT(GPIO_IN_SENS));
    GPIO_PIN2_CFG &= ~BIT(2); // normal out (push-pull)
    ets_isr_attach(ETS_GPIO_INUM, GPIOs_intr_handler, NULL);
    ets_isr_attach(ETS_FRC_TIMER1_INUM, TIMER_intr_cb, NULL);
    gpio_pin_intr_state_set(GPIO_IN, GPIO_PIN_INTR_POSEDGE);
    gpio_pin_intr_state_set(GPIO_IN_SENS, GPIO_PIN_INTR_NEGEDGE);
    INTC_EDGE_EN |= BIT(1); // + timer0
    ets_isr_unmask(BIT(ETS_FRC_TIMER1_INUM) | BIT(ETS_GPIO_INUM)); // разрешить прерывания GPIOs & Timer0*/
}

void WiFi_init() {
  Serial.print(F("Connecting to "));
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.println(F("WiFi connected"));
  Serial.print(F("IP address: "));
  Serial.println(WiFi.localIP());
}

void setup() {
  Serial.begin(115200);
  Serial.println(F(""));
  gpio_init();
  Serial.println(F("GPIO init"));
  dimmer_start();
  Serial.println(F("dimmer start"));
  WiFi_init();
  OTA_init();

  // Путь к корневой / web странице
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    Serial.println(F("You called root page"));
    request->send_P(200, "text/html", index_html, processor);
  });

  // Отправляем GET запрос на <ESP_IP>/update?state=<inputMessage>
  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest * request) {
    String inputMessage;
    // GET input1 value on <ESP_IP>/update?state=<inputMessage>
    if (request->hasParam(PARAM_INPUT_1)) {
      inputMessage = request->getParam(PARAM_INPUT_1)->value();
      int tmpState = inputMessage.toInt();
      motorState = tmpState ? ON : OFF;
    }
    else {
      inputMessage = "No message sent";
    }
    //    Serial.println(inputMessage);
    Serial.print(F("Насос: "));
    Serial.println(motorState);
    request->send(200, "text/plain", "OK");
  });

  // Отправляем GET запрос на <ESP_IP>/slider?value=<inputMessage>
  server.on("/slider", HTTP_GET, [] (AsyncWebServerRequest * request) {
    String inputMessage;
    // GET input1 value on <ESP_IP>/slider?value=<inputMessage>
    if (request->hasParam(PARAM_INPUT_2)) {
      inputMessage = request->getParam(PARAM_INPUT_2)->value();
      dimmerSliderValue = inputMessage;
      targetSpeed = dimmerSliderValue.toInt();
    }
    else {
      inputMessage = "No message sent";
    }
    //    Serial.println(inputMessage);
    Serial.print(F("Скорость насоса: "));
    Serial.println(targetSpeed);
    request->send(200, "text/plain", "OK");
  });

  // Обработка событий веб-сервера
  events.onConnect([](AsyncEventSourceClient * client) {
    if (client->lastId()) {
      Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
    }
    // отправка события "hello!", id текущие миллисекунды
    // задержка повторного подключения 1 сек
    client->send("hello!", NULL, millis(), 500);
  });
  server.addHandler(&events);

  // Start server
  server.begin();
  Serial.println(F("HTTP server started"));
}

void loop() {
  ArduinoOTA.handle();

  static unsigned long lastEventTime = millis();
  static const unsigned long EVENT_INTERVAL_MS = 200;

  if (motorState == ON) {
    currentSpeed = targetSpeed;
    dimmer_start();
  }
  else {
    currentSpeed = MIN_DIMMING_VALUE;
    dimmer_stop();
  }

  if (currentSpeed == MIN_DIMMING_VALUE) {
    motorState = OFF;
  }
  else if (currentSpeed == MAX_DIMMING_VALUE) {
    motorState = ON;
  }

  dimDelay = 10 * 5 + (95 * (MAX_DIMMING_VALUE - currentSpeed)) * 5;
  uptime = millis() / 1000;

  // RemoteDebug handle
  Debug.handle();

  if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
    events.send(String(uptime).c_str(), "uptime", millis());
    events.send(String(currentSpeed).c_str(), "currentSpeed", millis());
    events.send(String(motorState).c_str(), "motorState", millis());   
    lastEventTime = millis();
  }

}
 

Melandr

Member
HTML
HTML:
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <link rel="icon" href="data:,"> 
  <title>Контроллер насоса котла</title>
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h2 {font-size: 2.4rem;}
    p {font-size: 2.2rem;}
    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
    .reading {font-size: 1.5rem;}
    .topnav {
      overflow: hidden;
      background-color: #50B8B4;
      color: white;
      font-size: 1rem;
     }
    .switch {
      position: relative;
      display: inline-block;
      width: 120px;
      height: 68px
     }
    .switch input {display: none}
    .slider {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background-color: #ccc;
      border-radius: 34px
     }
    .slider:before {
      position: absolute;
      content: "";
      height: 52px;
      width: 52px;
      left: 8px;
      bottom: 8px;
      background-color: #fff;
      -webkit-transition: .4s;
      transition: .4s;
      border-radius: 68px
     }
    input:checked+.slider {background-color: #2196F3}
    input:checked+.slider:before {
      -webkit-transform: translateX(52px);
      -ms-transform: translateX(52px);
      transform: translateX(52px)
     }
    .slider2 {
      -webkit-appearance: none;
      margin: 14px;
      width: 300px;
      height: 20px;
      background: #ccc;
      outline: none;
      -webkit-transition: .2s;
      transition: opacity .2s;
     }
    .slider2::-webkit-slider-thumb {
      -webkit-appearance: none;
      appearance: none;
      width: 30px;
      height: 30px;
      background: #2f4468;
      cursor: pointer;
     }
    .slider2::-moz-range-thumb { width: 30px; height: 30px; background: #2f4468; cursor: pointer; }
    .button1 {
      position: relative;
      left: 10px;
      right: 0;
      bottom: 6px;
    }
    .button2 {
      position: relative;
      left: 0;
      right: 20px;
      bottom: 6px;
    }   
  }
  </style>
</head>
<body>
  <div class="topnav">
  <h2>Контроллер насоса котла</h2>
  </div>
  <p><span id="dimmerValue">%DIMMERVALUE%</span> &percnt;</p>
  <p>
  <button class="button1" onclick="decreaseDimmerValue()">-</button>
  <input type="range" onchange="updateSliderDimmer(this)" id="dimmerSlider" min="0" max="100" value="%DIMMERVALUE%" step="1" class="slider2">
  <button class="button2" onclick="increaseDimmerValue()">+</button>
  </p>
  <p><label class="switch"><input type="checkbox" onchange="toggleCheckbox(this)" id="output" %BUTTONPLACEHOLDER%><span class="slider"></span></label></p>
  <p class="reading">Время работы: <span id="uptime">%UPTIME%</span> сек</p>
<script>
function decreaseDimmerValue() {
  var sliderValue = document.getElementById("dimmerSlider").value;
  sliderValue--;
  document.getElementById("dimmerSlider").value = sliderValue;
  document.getElementById("dimmerValue").innerHTML = sliderValue;
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/slider?value="+sliderValue, true);
  xhr.send(); 
}
function increaseDimmerValue() {
  var sliderValue = document.getElementById("dimmerSlider").value;
  sliderValue++;
  document.getElementById("dimmerSlider").value = sliderValue;
  document.getElementById("dimmerValue").innerHTML = sliderValue;
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/slider?value="+sliderValue, true);
  xhr.send(); 
}
function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/update?state=1", true); console.log("button - нажата");}
  else { xhr.open("GET", "/update?state=0", true);console.log("button - отжата");}
  xhr.send();
}
function updateSliderDimmer(element) {
  var sliderValue = document.getElementById("dimmerSlider").value;
  document.getElementById("dimmerValue").innerHTML = sliderValue;
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/slider?value="+sliderValue, true);
  xhr.send();
  console.log("Скорость: " + sliderValue);
}
if (!!window.EventSource) {
 var source = new EventSource('/events');
 source.addEventListener('open', function(e) {
  console.log("Events Connected");
 }, false);
 source.addEventListener('error', function(e) {
  if (e.target.readyState != EventSource.OPEN) {
    console.log("Events Disconnected");
  }
 }, false);
 source.addEventListener('message', function(e) {
  console.log("message", e.data);
 }, false);
 source.addEventListener('uptime', function(e) {
  console.log("uptime", e.data);
  document.getElementById("uptime").innerHTML = e.data;
 }, false);
 source.addEventListener('currentSpeed', function(e) {
  console.log("currentSpeed", e.data);
  //sliderValue = e.data;
  document.getElementById("dimmerSlider").value = e.data;
  document.getElementById("dimmerValue").innerHTML = e.data;
 }, false);
  source.addEventListener('motorState', function(e) {
    console.log("motorState", e.data);
    var inputChecked;
    if( e.data == 1){ inputChecked = true; }
    else { inputChecked = false; }
    document.getElementById("output").checked = inputChecked;
  }, false);
}
</script>
</body>
</html>
)rawliteral";
 

Melandr

Member
Добрый вечер!
Хотелось бы все-таки понять, в чем может быть проблема, почему разрывается соединение sse, попробовал пример https://github.com/IU5HKU/ESP8266-ServerSentEvents
Соединение держится пока не нажмешь кнопку обновить в браузере, для остановки обновления.
 

Melandr

Member
Доброй ночи!
Запустил следующий код на ESP
Код:
/*
  Server-Sent Events / EventSource DEMO
  forked from Claudius Coenen repository
  based on Web Server example by David A. Mellis and Tom Igoe
  Adapted to the new ESP8266 SDK 2.4.2 by Marco Campinoti

  Circuit:
   Analog input attached to pins A0 (optional)
   Digital input attached to pins 5 or 6 (optional)

  This is free software. Use, modify and tinker with it however you like!
  LICENSED UNDER CC-BY-4.0 http://creativecommons.org/licenses/by/4.0/
*/

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>

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

//declare the webserver
ESP8266WebServer server(80);

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(115200);
  while (!Serial) {
    ; // wait for serial port to connect
  }

  if (WiFi.status() != WL_CONNECTED)
  {
    Serial.print(F("Connecting"));

    WiFi.persistent(false);       // WiFi config isn't saved in flash
    WiFi.mode(WIFI_STA);          // use WIFI_AP_STA if you want an AP
    WiFi.hostname("ESP8266");    // must be called before wifi.begin()
    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(F("."));
    }
  }

  Serial.println();
  Serial.print(F("IP address: "));
  Serial.println(WiFi.localIP());

  //start the webserver
  server.begin();
  //Server Sent Events will be handled from this URI
  server.on("/ssedata", handleSSEdata);
}

void loop() {
  // listen for incoming clients
  server.handleClient();
}

void handleSSEdata() {
  WiFiClient client = server.client();

  if (client) {
    Serial.println("new client");
    serverSentEventHeader(client);
    while (client.connected()) {
      serverSentEvent(client);
      delay(16); // round about 60 messages per second
    }

    // give the web browser time to receive the data
    delay(1);
    // close the connection:
    client.stop();
    Serial.println("client disconnected");
  }
}

void serverSentEventHeader(WiFiClient client) {
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/event-stream;charset=UTF-8");
  client.println("Connection: close");  // the connection will be closed after completion of the response
  client.println("Access-Control-Allow-Origin: *");  // allow any connection. We don't want Arduino to host all of the website ;-)
  client.println("Cache-Control: no-cache");  // refresh the page automatically every 5 sec
  client.println();
  client.flush();
}

void serverSentEvent(WiFiClient client) {
  client.println("event: esp8266"); // this name could be anything, really.
  client.print("data: {");
  client.print("\"A0\": ");
  client.print(1.0 * analogRead(0) / 1024.0);
  client.print(", \"in5\": ");
  client.print(digitalRead(5));
  client.print(", \"in6\": ");
  client.print(digitalRead(6));
  client.print(", \"uptime\": ");
  client.print(millis()); 
  client.print(", \"text\": ESP8266");    // added just to show how you can add your own parameters
  client.println("}");
  client.println();
  client.flush();
}
Когда нажимаешь кнопку обновить на странице браузера, страница начинает заполняться данными
Значит код работает. Но как отобразить в реальном времени эти данные на html-странице?
В https://github.com/IU5HKU/ESP8266-ServerSentEvents есть html и php
Как его запустить?
 

Melandr

Member
Самое интересное, проверка работы вэб-сервера и php проходит на ура, а с ESP работать не хочет.
 

CodeNameHawk

Moderator
Команда форума
Отвечу в вашем стиле, последний скетч работает.
Ну как и у вас, много чего написано, но нету ни что, ни как.
Зашел по адресу и идет отображение информации.
 

Melandr

Member
Добрый день, попробую объяснить в чем проблема.
Последний скетч работает, данные идут постоянно на страницу.
Но этот скетч реализован на синхронном вэб-сервере.
Я сделал пример на асинхронном вэб-сервере.
Вот код:
Код:
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

#include "OTA.h"
#include "index_html.h"

#include "ets_sys.h"
#include "gpio.h"
#include "user_interface.h"


#define INTC_EDGE_EN  (*(volatile uint32_t *)0x3FF00004)
#define TIMER_LOAD (*(volatile uint32_t *)0x60000600)
#define TIMER_COUNT (*(volatile uint32_t *)0x60000604)
#define TIMER_CTRL (*(volatile uint32_t *)0x60000608)
#define TIMER_INT  (*(volatile uint32_t *)0x6000060c)
#define GPIO_OUT_W1TS (*(volatile uint32_t *)0x60000304)
#define GPIO_OUT_W1TC (*(volatile uint32_t *)0x60000308)
#define GPIO_OUTP (*(volatile uint32_t *)0x60000300)
#define GPIO_STATUS (*(volatile uint32_t *)0x6000031C)
#define GPIO_PIN2_CFG (*(volatile uint32_t *)0x60000330)
#define GPIO_STATUS_W1TC (*(volatile uint32_t *)0x60000324)
#define WDEV_NOW() (*(volatile uint32_t *)0x3ff20c00)

#define TIME_PULSE 50
#define MAX_DIMMING_VALUE 100
#define MIN_DIMMING_VALUE 0

const char* ssid = "ASUS";
const char* password = "gCU8YNZs";
//const char* ssid = "TP-LINK_F54920";
//const char* password = "54168398";
//const char* ssid = "SamsungS9+";
//const char* password = "qwert789";

static const int GPIO_OUT = 2; // GPIO2
static const int GPIO_IN = 1; // GPIO1
static const int GPIO_IN_SENS = 3; // GPIO3

uint32_t volatile dimDelay = 50 * 5; // in 0.2 us (0x007fffff max), min 12*5
uint32_t volatile pulseDelay = TIME_PULSE * 5; // in 0.2 us (0x007fffff max), min 12*5

uint8_t volatile targetSpeed = 15;    //требуемое значение скорости в процентах
uint8_t volatile currentSpeed = 0;    //текущее значение скорости в процентах

volatile unsigned long countZeroCross = 0;
volatile unsigned long countPeriod = 0;
volatile unsigned long countSysTimer = 0;
volatile unsigned long period = 0;
unsigned long lastMillis = 0;
unsigned long lastPeriodTime = 0;
unsigned long uptime = 0;
unsigned long previousMillis = 0;     // храним крайнее обновление сенсоров
const long interval = 10000;          // считываем значение с датчиков каждые 10 секунд

typedef enum {
  OFF,
  ON
} eState;

eState motorState = ON;

String outputState() {
  if (motorState) {
    return "checked";
  }
  else {
    return "";
  }
  return "";
}

//переменные для асинхронного вэб-сервера
const char* PARAM_INPUT_1 = "state";
const char* PARAM_INPUT_2 = "value";

String dimmerSliderValue = String(targetSpeed);
String outputStateValue = outputState();

// создаем объект AsyncWebServer на 80-ом порту
AsyncWebServer server(80);
AsyncEventSource events("/events");

// Replaces placeholder with button section in your web page
String processor(const String& var) {

  if (var == "BUTTONPLACEHOLDER") {
    String buttons = "";
    outputStateValue = outputState();
    buttons += "outputStateValue";
    return buttons;
  }
  else if (var == "DIMMERVALUE") {
    return dimmerSliderValue;
  }
  else if (var == "UPTIME") {
    return String(uptime);
  }
  else if (var == "PERIOD") {
    return String(period);
  }

  return String();
}

void ICACHE_RAM_ATTR GPIOs_intr_handler(void *arg) {
  (void)arg;
  uint32_t tmp = GPIO_STATUS;

  if (tmp & BIT(GPIO_IN)) {
    TIMER_LOAD = dimDelay;
    TIMER_CTRL = 4 | BIT(7); // TM_DIVDED_BY_16, NO TM_AUTO_RELOAD_CNT, TM_ENABLE_TIMER
    countZeroCross++;
  }
  if (tmp & BIT(GPIO_IN_SENS)) {
    period = millis() - lastPeriodTime;
    lastPeriodTime = millis();
    countPeriod++;
  }
  GPIO_STATUS_W1TC = tmp;
}

void ICACHE_RAM_ATTR TIMER_intr_cb(void *arg) {
  (void)arg;
  if (GPIO_OUTP & BIT(GPIO_OUT)) {      //если симистор включен
    GPIO_OUT_W1TC = BIT(GPIO_OUT);      //выключаем симистор
    TIMER_CTRL = 0; // stop timer
    //gpio_pin_intr_state_set(GPIO_IN, GPIO_PIN_INTR_POSEDGE);
    //gpio_pin_intr_state_set(GPIO_IN_SENS, GPIO_PIN_INTR_POSEDGE);
  } else {
    GPIO_OUT_W1TS = BIT(GPIO_OUT);    //если симистор выключен, включаем
    TIMER_LOAD = pulseDelay;      //загружаем в таймер значение времени импульса открытия
  }
  countSysTimer++;
}

void dimmer_stop() {
  ets_isr_mask(BIT(ETS_FRC_TIMER1_INUM) | BIT(ETS_GPIO_INUM)); // запретить прерывания GPIOs & Timer0
  GPIO_OUT_W1TC = BIT(GPIO_OUT);
  TIMER_CTRL = 0; // stop timer
}

void dimmer_start() {
  dimmer_stop();
  TIMER_COUNT = 0;
/*  PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_GPIO1);
  PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2);
  PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_GPIO3);
  gpio_output_set(0, 0, BIT(GPIO_OUT), BIT(GPIO_IN));
  gpio_output_set(0, 0, 0, BIT(GPIO_IN_SENS));
  GPIO_PIN2_CFG &= ~BIT(2); // normal out (push-pull)
  ets_isr_attach(ETS_GPIO_INUM, GPIOs_intr_handler, NULL);
  ets_isr_attach(ETS_FRC_TIMER1_INUM, TIMER_intr_cb, NULL);
  gpio_pin_intr_state_set(GPIO_IN, GPIO_PIN_INTR_POSEDGE);
  gpio_pin_intr_state_set(GPIO_IN_SENS, GPIO_PIN_INTR_NEGEDGE);*/
  INTC_EDGE_EN |= BIT(1); // + timer0
  ets_isr_unmask(BIT(ETS_FRC_TIMER1_INUM) | BIT(ETS_GPIO_INUM)); // разрешить прерывания GPIOs & Timer0
}

void WiFi_init() {
  Serial.print(F("Connecting to "));
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.println(F("WiFi connected"));
  Serial.print(F("IP address: "));
  Serial.println(WiFi.localIP());
}

void setup() {
  Serial.begin(115200);
  Serial.println(F(""));
  gpio_init();
  Serial.println(F("GPIO init"));
  dimmer_start();
  Serial.println(F("dimmer start"));
  WiFi_init();
  OTA_init();

  // Путь к корневой / web странице
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    Serial.println(F("You called root page"));
    request->send_P(200, "text/html", index_html, processor);
  });

  // Отправляем GET запрос на <ESP_IP>/update?state=<inputMessage>
  server.on("/update", HTTP_GET, [] (AsyncWebServerRequest * request) {
    String inputMessage;
    // GET input1 value on <ESP_IP>/update?state=<inputMessage>
    if (request->hasParam(PARAM_INPUT_1)) {
      inputMessage = request->getParam(PARAM_INPUT_1)->value();
      int tmpState = inputMessage.toInt();
      motorState = tmpState ? ON : OFF;
    }
    else {
      inputMessage = "No message sent";
    }
    //    Serial.println(inputMessage);
    Serial.print(F("Насос: "));
    Serial.println(motorState);
    request->send(200, "text/plain", "OK");
  });

  // Отправляем GET запрос на <ESP_IP>/slider?value=<inputMessage>
  server.on("/slider", HTTP_GET, [] (AsyncWebServerRequest * request) {
    String inputMessage;
    // GET input1 value on <ESP_IP>/slider?value=<inputMessage>
    if (request->hasParam(PARAM_INPUT_2)) {
      inputMessage = request->getParam(PARAM_INPUT_2)->value();
      dimmerSliderValue = inputMessage;
      targetSpeed = dimmerSliderValue.toInt();
    }
    else {
      inputMessage = "No message sent";
    }
    //    Serial.println(inputMessage);
    Serial.print(F("Скорость насоса: "));
    Serial.println(targetSpeed);
    request->send(200, "text/plain", "OK");
  });

  // Обработка событий веб-сервера
  events.onConnect([](AsyncEventSourceClient * client) {
    if (client->lastId()) {
      Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
    }
    // отправка события "hello!", id текущие миллисекунды
    // задержка повторного подключения 1 сек
    client->send("hello!", NULL, millis(), 100);
  });
  server.addHandler(&events);

  // Start server
  server.begin();
  Serial.println(F("HTTP server started"));
}

void loop() {
  ArduinoOTA.handle();

  static unsigned long lastEventTime = millis();
  static const unsigned long EVENT_INTERVAL_MS = 3000;

  if (motorState == ON) {
    currentSpeed = targetSpeed;
    dimmer_start();
  }
  else {
    currentSpeed = MIN_DIMMING_VALUE;
    dimmer_stop();
  }

  if (currentSpeed == MIN_DIMMING_VALUE) {
    motorState = OFF;
  }
  else if (currentSpeed == MAX_DIMMING_VALUE) {
    motorState = ON;
  }

  dimDelay = 10 * 5 + (95 * (MAX_DIMMING_VALUE - currentSpeed)) * 5;
  uptime = millis() / 1000;

  if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
    Serial.println(F("send data"));
    events.send(String(uptime).c_str(), "uptime", millis());
    events.send(String(currentSpeed).c_str(), "currentSpeed", millis());
    events.send(String(motorState).c_str(), "motorState", millis());
    events.send(String(period).c_str(), "period", millis());
    lastEventTime = millis();
  }

}
Насколько я понял, если открыть страницу http://ESP_adress/event
то должны падать данные на страницу в json каждые 3 секунды
А по факту проходят только 2 посылки с данными
2021-03-08_122657.jpg
А через последовательный порт идут постоянные реконекты
2021-03-08_122803.jpg
А на странице это отображается как фризы в отображении данных. Не могу понять, из-за чего идут эти подтормаживания.
 

CodeNameHawk

Moderator
Команда форума
Сократите скетч до минимальной длины, когда еще не работает.
 

EvgeniyS

Member
А этот асинхронный events не на базе websocket сделан?
Вероятно, нет т.к. в библиотеке это отдельный плагин в библотеке, и он не требует создания экземпляра websocket. К тому же, судя по документации, events source - это односторонняя связь сервер->клиент, а вебсокет подразумевает постоянно открытое соединение и друстороннюю связь сервера с клиентом.
 

Melandr

Member
Пример кода взят с сайта Руи Сантоса
А вот с гита выдержка
Async Event Source Plugin
The server includes EventSource (Server-Sent Events) plugin which can be used to send short text events to the browser. Difference between EventSource and WebSockets is that EventSource is single direction, text-only protocol.
Setup Event Source on the server
Код:
AsyncWebServer server(80);
AsyncEventSource events("/events");

void setup(){
  // setup ......
  events.onConnect([](AsyncEventSourceClient *client){
    if(client->lastId()){
      Serial.printf("Client reconnected! Last message ID that it gat is: %u\n", client->lastId());
    }
    //send event with message "hello!", id current millis
    // and set reconnect delay to 1 second
    client->send("hello!",NULL,millis(),1000);
  });
  //HTTP Basic authentication
  events.setAuthentication("user", "pass");
  server.addHandler(&events);
  // setup ......
}

void loop(){
  if(eventTriggered){ // your logic here
    //send event "myevent"
    events.send("my event content","myevent",millis());
  }
}
Setup Event Source in the browser
Код:
if (!!window.EventSource) {
  var source = new EventSource('/events');

  source.addEventListener('open', function(e) {
    console.log("Events Connected");
  }, false);

  source.addEventListener('error', function(e) {
    if (e.target.readyState != EventSource.OPEN) {
      console.log("Events Disconnected");
    }
  }, false);

  source.addEventListener('message', function(e) {
    console.log("message", e.data);
  }, false);

  source.addEventListener('myevent', function(e) {
    console.log("myevent", e.data);
  }, false);
}
Сейчас сделаю код, который будет бросать на страницу данные. Посмотрю, почему обрывает соединение. Хотя вроде бы код реализован также как и на гихабе пример.
 

Melandr

Member
Добрый день! Повторил пример server sent event с данного сайта http://wei48221.blogspot.com/2019/04/first-experiment-with-sse-server-sent.html Но не могу понять, в чем может быть проблема. При прошивке ESP в браузере по адресу http://ESP_IP/ssedata наблюдаю постоянное обновление данных. Далее сделал файл source.php и забросил его в каталог www на роутере, на котором поднят lighthhtpd - данные не приходят. Потом установил Денвер, забросил в его каталог этот же файл, все нормально. Как можно проверить работоспособность кода на php? При этом на роутере создаю пример проверки работы php, страница отображается корректно.
 

Melandr

Member
начал разбираться с реализацией server sent event на синхронном сервере и увидел, что при создании соединения по SSE программа попадает в обработчик SSE и не выходит из него, пока активно соединение. Соответственно весь код, который находится в основном цикле не выполняется. Не знаю, зачем нужна такая реализация обмена данными, если основной функционал выполняться не будет.
Ниже эти примеры:
Код:
/*
  Server-Sent Events / EventSource DEMO
  forked from Claudius Coenen repository
  based on Web Server example by David A. Mellis and Tom Igoe
  Adapted to the new ESP8266 SDK 2.4.2 by Marco Campinoti

  Circuit:
   Analog input attached to pins A0 (optional)
   Digital input attached to pins 5 or 6 (optional)

  This is free software. Use, modify and tinker with it however you like!
  LICENSED UNDER CC-BY-4.0 http://creativecommons.org/licenses/by/4.0/
*/

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>

//const char* ssid = "ASUS";
//const char* password = "gCU8YNZs";
const char* ssid = "SamsungS9+";
const char* password = "qwert789";

//declare the webserver
ESP8266WebServer server(80);

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(115200);

  if (WiFi.status() != WL_CONNECTED)
  {
    Serial.print(F("Connecting"));

    WiFi.persistent(false);       // WiFi config isn't saved in flash
    WiFi.mode(WIFI_STA);          // use WIFI_AP_STA if you want an AP
    WiFi.hostname("ESP8266");    // must be called before wifi.begin()
    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(F("."));
    }
  }

  Serial.println();
  Serial.print(F("IP address: "));
  Serial.println(WiFi.localIP());

  //start the webserver
  server.begin();
  //Server Sent Events will be handled from this URI
  server.on("/ssedata", handleSSEdata);
}

void loop() {
  // listen for incoming clients
  server.handleClient();
  static long int count;
  delay(500);
  count++;
  Serial.print("Loop - ");
  Serial.println(count);
}

void handleSSEdata() {
  WiFiClient client = server.client();

  if (client) {
    Serial.println("new client");
    serverSentEventHeader(client);
    while (client.connected()) {
      serverSentEvent(client);
      delay(16); // round about 60 messages per second
    }

    // give the web browser time to receive the data
    delay(1);
    // close the connection:
    client.stop();
    Serial.println("client disconnected");
  }
}

void serverSentEventHeader(WiFiClient client) {
  client.println("HTTP/1.1 200 OK");
  client.println("Content-Type: text/event-stream;charset=UTF-8");
  client.println("Connection: close");  // the connection will be closed after completion of the response
  client.println("Access-Control-Allow-Origin: *");  // allow any connection. We don't want Arduino to host all of the website ;-)
  client.println("Cache-Control: no-cache");  // refresh the page automatically every 5 sec
  client.println();
  client.flush();
}

void serverSentEvent(WiFiClient client) {
  client.println("event: esp8266"); // this name could be anything, really.
  client.print("data: {");
  client.print("\"A0\": ");
  client.print(1.0 * analogRead(0) / 1024.0);
  client.print(", \"in5\": ");
  client.print(digitalRead(5));
  client.print(", \"in6\": ");
  client.print(digitalRead(6));
  client.print(", \"text\": ESP8266");    // added just to show how you can add your own parameters
  client.println("}");
  client.println();
  client.flush();
}
Походу нужно разбираться в вэб-сокетами.
Такой вопрос, если интересует период обновления значений переменных на вэб-странице порядка 500-1000 мс, какую технологию лучше использовать?
 

EvgeniyS

Member
Такой вопрос, если интересует период обновления значений переменных на вэб-странице порядка 500-1000 мс, какую технологию лучше использовать?
Веб сокеты подойдут. Но, есть особенность: в библиотеке ESPAsyncWebServer есть ограничение на количество одновременно подключенных клиентов (около 10, точно не помню). Обусловленно это тем, что каждый подключенный клиент - это открытое соединение, что требует выделения памяти. Плюс данной технологии в том, что при каждой отправке данных не нужно открывать и закрывать соединение, что очень затратно. И все же я бы сделал проверку в коде, изменились ли переменные, и если да, то только в этом случае отправлять клиенту обновленные данные, чтобы не гонять по сети лишние пакеты.
 

Melandr

Member
А насчет server sent event, не подскажите. А то внятного описания не нашел. Допустим на ESP во флеше есть html страница. Она открыта в браузере, при нажатии кнопки на странице GET запросом передается значение на ESP. Также есть переменные, которые нужно отобразить на html странице. Период обновления 1 сек. Если использовать механизм sse, то мы открываем соединение и шлем данные клиенту на html-страницу. Нужно ли закрывать каждый раз соединение, для выполнения кода в основном цикле?
 

Melandr

Member
Убрал прерывания и работу с портами, оставил только взаимодействие с html-страницей

Не могу понять, почему идет переподключение соединения, и при этом
static const unsigned long EVENT_INTERVAL_MS = 1000;
и при этом эта переменная не влияет на период обновления данных
2021-03-11_205045.jpg
2021-03-11_205602.jpg
2021-03-11_205029.jpg
 

Melandr

Member
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "index.h"

#define MAX_DIMMING_VALUE 100
#define MIN_DIMMING_VALUE 0

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

uint8_t volatile targetSpeed = 15; //требуемое значение скорости в процентах
uint8_t volatile currentSpeed = 0; //текущее значение скорости в процентах
unsigned long uptime = 0;

typedef enum {
OFF,
ON
} eState;

eState motorState = ON;

String outputState() {
if (motorState) {
return "checked";
}
else {
return "";
}
return "";
}

//переменные для асинхронного вэб-сервера
const char* PARAM_INPUT_1 = "state";
const char* PARAM_INPUT_2 = "value";

String dimmerSliderValue = String(targetSpeed);
String outputStateValue = outputState();

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
AsyncEventSource events("/events");

// Replaces placeholder with button section in your web page
String processor(const String& var) {

if (var == "BUTTONPLACEHOLDER") {
String buttons = "";
outputStateValue = outputState();
buttons += "outputStateValue";
return buttons;
}
else if (var == "DIMMERVALUE") {
return dimmerSliderValue;
}
else if (var == "UPTIME") {
return String(uptime);
}

return String();
}

void setup() {
Serial.begin(115200);

if (WiFi.status() != WL_CONNECTED)
{
Serial.print(F("Connecting"));

WiFi.persistent(false); // WiFi config isn't saved in flash
WiFi.mode(WIFI_STA); // use WIFI_AP_STA if you want an AP
WiFi.hostname("ESP8266"); // must be called before wifi.begin()
WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(F("."));
}
}

Serial.println();
Serial.print(F("IP address: "));
Serial.println(WiFi.localIP());

// Путь к корневой / web странице
server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
Serial.println(F("You called root page"));
request->send_P(200, "text/html", index_html, processor);
});

// Отправляем GET запрос на <ESP_IP>/update?state=<inputMessage>
server.on("/update", HTTP_GET, [] (AsyncWebServerRequest * request) {
String inputMessage;
// GET input1 value on <ESP_IP>/update?state=<inputMessage>
if (request->hasParam(PARAM_INPUT_1)) {
inputMessage = request->getParam(PARAM_INPUT_1)->value();
int tmpState = inputMessage.toInt();
motorState = tmpState ? ON : OFF;
}
else {
inputMessage = "No message sent";
}
// Serial.println(inputMessage);
Serial.print(F("Насос: "));
Serial.println(motorState);
request->send(200, "text/plain", "OK");
});

// Отправляем GET запрос на <ESP_IP>/slider?value=<inputMessage>
server.on("/slider", HTTP_GET, [] (AsyncWebServerRequest * request) {
String inputMessage;
// GET input1 value on <ESP_IP>/slider?value=<inputMessage>
if (request->hasParam(PARAM_INPUT_2)) {
inputMessage = request->getParam(PARAM_INPUT_2)->value();
dimmerSliderValue = inputMessage;
targetSpeed = dimmerSliderValue.toInt();
}
else {
inputMessage = "No message sent";
}
// Serial.println(inputMessage);
Serial.print(F("Скорость насоса: "));
Serial.println(targetSpeed);
request->send(200, "text/plain", "OK");
});

// Обработка событий веб-сервера
events.onConnect([](AsyncEventSourceClient * client) {
if (client->lastId()) {
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
// отправка события "hello!", id текущие миллисекунды
// задержка повторного подключения 1 сек
client->send("hello!", NULL, millis(), 1000);
});
server.addHandler(&events);

// Start server
server.begin();
Serial.println(F("HTTP server started"));
}

void loop() {

static unsigned long lastEventTime = millis();
static const unsigned long EVENT_INTERVAL_MS = 1000;
static long int count;

if (motorState == ON) {
currentSpeed = targetSpeed;
}
else {
currentSpeed = MIN_DIMMING_VALUE;
}

if (currentSpeed == MIN_DIMMING_VALUE) {
motorState = OFF;
}
else if (currentSpeed == MAX_DIMMING_VALUE) {
motorState = ON;
}

uptime = millis() / 1000;

if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
Serial.println(F("send data"));
events.send(String(uptime).c_str(), "uptime", millis());
events.send(String(currentSpeed).c_str(), "currentSpeed", millis());
events.send(String(motorState).c_str(), "motorState", millis());
lastEventTime = millis();
}

delay(500);
count++;
Serial.print("Loop - ");
Serial.println(count);
}
 
Сверху Снизу