• Уважаемые посетители сайта esp8266.ru!
    Мы отказались от размещения рекламы на страницах форума для большего комфорта пользователей.
    Вы можете оказать посильную поддержку администрации форума. Данные средства пойдут на оплату услуг облачных провайдеров для сайта esp8266.ru
  • Система автоматизации с открытым исходным кодом на базе 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);
}
 
Сверху Снизу