• Уважаемые посетители сайта esp8266.ru!
    Мы отказались от размещения рекламы на страницах форума для большего комфорта пользователей.
    Вы можете оказать посильную поддержку администрации форума. Данные средства пойдут на оплату услуг облачных провайдеров для сайта esp8266.ru
  • Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Еще раз о выключателе

anthony3d

New member
Прошу прощения за ламерский вопрос, который уже, наверное, все давно решили… Но
Делаю по сути первый приличный проект на ESP8266.
Настенный выключатель с Веб-функцией.
Т.е. он должен и аппаратно включаться-выключаться, и через веб.
Взял за основу ESP8266-01 Relay module, к которому повесил кнопку на GPIO2.
И затормозил на софте.
Задача стоит так:
1. ESP должен стать сервером со своим IP и крошечной веб-страничкой, на которой всего один орган управления — чекбокс, управляющий реле.
(Я потом сделаю супер-сервер, в котором будет нарисован план дома и все эти ESP-чекбоксы будут выведены «быстро-грязно» в iframe.)
Эта задача решается готовым примером. Правда, хочу этот чекбокс обновлять AJAX'ом, без перезагрузки страницы.
2. Реле должно переключаться также и от нажатия кнопки на GPIO2. При этом веб-чекбокс тоже должен перевестись в другое положение. (То же должно происходить когда веб-чекбокс переключили с другого браузера.)

Нет никаких проблем сделать просто аппаратную кнопку — переключатель реле, без веб.
Также нормально получается включать-выключать реле веб-чекбоксом. И АЯКС там тоже нормально работает.
Но вот объединить эти две функции не получается.
Самое главное — я не знаю как обычно делается передача значения переменной из скетча на веб-страничку. Я видел программу, где по таймеру javascript считывает XML, а сервер ему этот XML передает. Скажем так, не самая красивая конструкция. Хотя бы потому, что есть задержка пока передастся блок, да и я вообще против постоянно исполняющейся процедуры на довольно слабеньком устройстве. И еще это не решает синхронизацию чекбокса с разных браузеров.

Не сталкивался ли кто-нибудь с этой проблемой? Не изобретаю ли я давно придуманный велосипед?
 

CodeNameHawk

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

Я делаю так:

Код:
char html[1000];
#define SETUPPAGE_LENGTH sizeof(html)

    snprintf (html, SETUPPAGE_LENGTH - 1, "\
<!DOCTYPE html>\
<html>\
    <head>\
        <meta name=\"viewport\" charset=\"utf-8\" content=\"width=device-width, initial-scale=1.0\"/>\
    </head>\
    <body>\
        <form action=\"/cCS\" method=\"POST\">\
            <fieldset>\
                <legend>%s CONNECT SETUP</legend>\
                <table>\
                    <tr>\
                            <td>SSID 0</td><td><input type=\"text\" name=\"0\" value=\"%s\"></td>\
                    </tr>\
                    <tr>\
                            <td>PASS 0</td><td><input type=\"text\" name=\"1\" value=\"%s\"></td>\
                    </tr>\
                </table>\
            </fieldset>\
            <br>\
            <button type=\"submit\" title=\"Save and Exit\" value=\"Submit\">Confirm</button>\
            <input type=\"reset\">\
            <button type=\"submit\" title=\"Exit without saving\" name=\"ESC\" value=\"1\">ESC</button>\
        </form> \
        <br><small>\
        SSID: %s\
        <br>\
        Local IP: %s\
                                                    ",
                                                    FlashData.tmSet.ESP_ssid, // <legend>%s CONNECT SETUP</legend>
                                                    FlashData.tmSet.ssid[0],    // <td>SSID 0</td><td><input type=\"text\" name=\"0\" value=\"%s\"></td>
                                                    FlashData.tmSet.pass[0],    // <td>PASS 0</td><td><input type=\"text\" name=\"1\" value=\"%s\"></td>
                                                    curr_ssid,                                // SSID: %s
                                                    curr_ip );                                // Local IP: %s

    strncat(html, "\
        </body>\
</html>", SETUPPAGE_LENGTH - strlen(html) - 1);

    Serial.printf("6 setupPage(%d)= %d\n", SETUPPAGE_LENGTH, strlen(html));

    server.send ( 200, "text/html", html );
 

CodeNameHawk

Moderator
Команда форума
И при помощи какой технологии это делать выбор автора.
Есп может посылать данные в сеть, через отрытый канал, а программа на принимающей стороне может постоянно его слушать и оперативно выводить на экран.
Может ли это все делать веб страница я не в курсе.
 
имхо запрос данных идет от браузера к есп,
т.е если есп и имеет новые данные, то сама (есп) не сможет их обновить на открытой странице, без запроса со стороны браузера.
Именно так:
автообновление HTML страницы каждые 10 сек:
HTML:
<meta charset="utf-8" http-equiv="refresh" content="10">
автообновление JS:
JavaScript:
setInterval(getTemp, 10000);
или еще как-то...
 

CodeNameHawk

Moderator
Команда форума
Именно так:
автообновление HTML страницы каждые 10 сек:
Так как сделал автор, при помощи XML, намного приятней пользоваться, страница не мелькает, при обновлении.
Он хочет моментальное обновление данных на страницax. И малую нагрузку на проц.
 

anthony3d

New member
Безобразие с этим XML.
Круглые сутки выключатель стоит и ждет нажатие кнопки на включение. И происходит это раз 10 в день, если мы дома и не происходит вообще если мы уехали на месяц.
А каждую секунду исполняется JavaScript процедура, которая считывает XML, который "по свистку" собирает код скетча.
Я понимаю, что это все происходит внутри одного кристалла, что если этого не делать, то кристалл все равно будет тупить бесконечный цикл ожидания, и еще непонятно что из этого лучше.
Короче, XML буду делать если другое ничего не получится.

По Websocket.
Код, указанный уважаемым Encrypt довольно сложный и делает очень много чего еще. Его явно писал специалист, делающий не первое устройство на ESP. Я попробовал его упростить для понимания, но вызовы серверных функций в нем так раскиданы по тексту, что я бросил это дело.

И, кажется, я нашел инструмент, которым можно это все сделать. ESPAsyncWebServer

Постараюсь отписаться по результатам.
 
А каждую секунду исполняется JavaScript процедура,
JS исполняется только в браузере компьютера, только пока открыта страница вашего выключателя. Вы собираетесь целый день сидеть и смотреть на страничку своего выключателя в браузере?
Как я понимаю, вы откроете страничку, увидите состояние выключателя, включите или выключите его, если нужно, закроете страничку. Все.
 

Сергей_Ф

Moderator
Команда форума
JS исполняется только в браузере компьютера, только пока открыта страница вашего выключателя. Вы собираетесь целый день сидеть и смотреть на страничку своего выключателя в браузере?
Не будет ничего исполняться, если сидеть и смотреть на страничку. Js работает по событиям, которые в нем описаны. Если кнопки не нажимать, то ничего и не работает. Если там не втулили отслеживание курсора мыши, конечно. Тогда будет реагировать на мышку.

Круглые сутки выключатель стоит и ждет нажатие кнопки на включение.
Я вам больше скажу, даже если сделать на прерываниях, то процессор тоже будет целые сутки ждать это прерывание. Его конечно, можно загнать в сон, но кроме проблем вы никакого выигрыша не получите, строго говоря. Ну, кроме, морального удовлетворения. Если у вас не батарейное питание, где имеет смысл экономить каждый мВт|ч то не заморачивайтесь. Оно того не стоит.
 
Последнее редактирование:

anthony3d

New member
В идеале в доме должно быть размещено несколько экранчиков с тачскрином (raspberry pi), на которых будет постоянно транслироваться этот веб-интерфейс с планом дома и выключателями. Поэтому, думаю, Javascript будет действительно выполняться постоянно, более того, еще и не внутри кристалла, а с реально летающими по WiFi пакетами каждую секунду.
Но, видимо, такова его судьба.
Напишу что получилось. :)
 
летающими по WiFi пакетами каждую секунду. :)
Не вижу в этом ничего плохого. Посмотрите на свой домашний роутер - лампочки мигают постоянно, даже если вы не ведете никакой активности в сети - значит идет постоянный обмен пакетами. Одним больше, одним меньше :)
 

Сергей_Ф

Moderator
Команда форума
@anthony3d не будет ничего летать, если правильно написать js. Вы наверное, пропустили мой ответ. Js - это один большой callback. Там работа только по событиям. Загрузит один раз страницу и будет ждать события.
Нажатия кнопки на экране или изменения статуса со стороны esp. Событие отработает и опять будет ждать следующего. Если ничего меняться не будет - то ничего и летать не будет.
 

Сергей_Ф

Moderator
Команда форума
@lookingooder отошлет запрос и получит ответ. Один раз. Дальше действовать то будет js, а он по событию. Зачем каждую секунду грузить? Даже без веб- сокета можно обойтись.
Хотя "при умении" можно такого накрутить, что и i7 не справится.
 

Encrypt

Member
По Websocket.
Код, указанный уважаемым Encrypt довольно сложный и делает очень много чего еще. Его явно писал специалист, делающий не первое устройство на ESP. Я попробовал его упростить для понимания, но вызовы серверных функций в нем так раскиданы по тексту, что я бросил это дело.
---
В идеале в доме должно быть размещено несколько экранчиков с тачскрином (raspberry pi), на которых будет постоянно транслироваться этот веб-интерфейс с планом дома и выключателями.
На будущее:
Еще, я бы порекомендовал вам обратить внимание на MQTT протокол, как альтернативу веб-сокетам.
В качестве интерфейса и управления -- древний смартфон или планшет и MQTT клиент (например, MQTT Dash, Lazy MQTT...)
С практической точки зрения немного проще и дешевле построить панель управления с готового устройства.
Главное чтобы работал экран(тач), фронтальная камера(об этом ниже) , вайфай.

Но если в планах научится веб-программированию, то лучше начинать с веб-интерфейсов.
К тому же в обеих выше упомянутых программах используется JavaScript, который очень расширяет функционал.

Мой базовый пример у входной двери:
Приклеен стационарно на шкафу смартфон на андроиде 4.0 (2012г.) переживший 2-х владельцев.
Запитан напрямую от БП через dc-dc преобразователь(4.0 вольт) на контакты батареи, то есть батареи там нет.
Экран включается по движению!

Из спец софта установлен MQTT Dash, собственно интерфейс и управление.
Tasker -- автозагрузка всего необходимого и включение экрана по движению.
Motion Detector -- который фиксирует движение через фронтальную камеру и передает событие в таскер.

MQTT сервер - Mosquitto крутится на роутере (Xiaomi Router 3G + прошивка от padavan), но для начала можно воспользоваться и облачными сервисами. тотже cloudmqtt.com
В глубине всей этой автоматизации только ESP8266 12-e 4 mb flash. Мощных железок не использую так как пока не вижу смысла.
 

anthony3d

New member
Вот, что вышло.
Я не стал использовать никаких модных библиотек серверов.
Только подобрал библиотеку для кнопочки, чтобы сама с дребезгом итд. разбиралась.
Еще пока нет сетевого имени, но это уже просто.
Что есть:
1. Стандартная релейная платка для ESP8266 с управлением по GPIO0.
2. На GPIO2 припаян резистор 3к3 на плюс и кнопочка на землю.
3. LED на GPIO1.

Что делает:
1. Раз в секунду синхронизирует переменную switchState между JS и скетчем.
2. При нажатии на кнопочку меняет switchState.
3. При нажатии на чекбокс в веб-интерфейсе меняет switchState.
4. Синхронный switchState сразу отражается на состоянии реле.
5. LED показывает сначала процесс подключения, а в дальнейшем сетевую активность ESP.

Для проверки я подключил все компьютеры и телефоны в доме и наслаждался синхронным переключением движков чекбоксов на них.

Код:
#include <ESP8266WiFi.h>
const char* ssid = "Home-WiFi";
const char* password = "12345678";
WiFiServer server(80);//Service Port

const int __ON = LOW;
const int __OFF = HIGH;
#define LED 1
#define RELAY 0
int switchState = __OFF;
String request;

#include <JC_Button.h>
#define BUTTON 2
Button button(BUTTON);
#include "index.h"

void setup() {
  pinMode(RELAY, OUTPUT); digitalWrite(RELAY, switchState); // Setup start
  pinMode(LED, OUTPUT); digitalWrite(LED, __ON);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { delay(100); }
  server.begin();
  button.begin();
  digitalWrite(LED, __OFF); // Setup Ok
}

void loop() {
  button.read();
  if (button.wasPressed()) { switchState = !switchState; }
  digitalWrite(RELAY, switchState);
  WiFiClient client = server.available(); // Check if a client has connected
  if (client) {
    digitalWrite(LED, __ON); // Client start
    //delay(400);
    request = client.readStringUntil('\r');
    if ( request.indexOf("LEDON") > 0 )  {
      switchState = __ON;  digitalWrite(RELAY, switchState);
      client.print(F("HTTP/1.1 200 OK\r\n"));
    } else if ( request.indexOf("LEDOFF") > 0 ) {
      switchState = __OFF; digitalWrite(RELAY, switchState);
      client.print(F("HTTP/1.1 200 OK\r\n"));
    } else if ( request.indexOf("STATE") > 0 ) {
      client.print(F("HTTP/1.1 200 OK\r\nContent-Type: text/plane\r\n\r\n"));
      client.print((switchState) ? F("LEDOFF") : F("LEDON"));
    } else {
      client.print( header );
      client.print( html );
      delay(5);
    }
    client.flush();
    digitalWrite(LED, __OFF); // Client ok
  }
}
Код:
// -- index.h --
String header = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
String html = R"=====(
<!DOCTYPE html>
<html>
<head>
  <meta name='viewport' content='width=device-width, initial-scale=1.0'/>
  <meta charset='utf-8'>
  <style>
    #main {display: table; margin: auto;  padding: 0 10px 0 10px; }

.checkbox {
  position: absolute;
  z-index: -1;
  opacity: 0;
  margin: 10px 0 0 20px;
}
.checkbox + label {
  position: relative;
  padding: 0 0 0 60px;
  cursor: pointer;
}
.checkbox + label:before {
  content: '';
  position: absolute;
  top: -4px;
  left: 0;
  width: 50px;
  height: 26px;
  border-radius: 13px;
  background: #CDD1DA;
  box-shadow: inset 0 2px 3px rgba(0,0,0,.2);
  transition: .2s;
}
.checkbox + label:after {
  content: '';
  position: absolute;
  top: -2px;
  left: 2px;
  width: 22px;
  height: 22px;
  border-radius: 10px;
  background: #FFF;
  box-shadow: 0 2px 5px rgba(0,0,0,.3);
  transition: .2s;
}
.checkbox:checked + label:before {
  background: #9FD468;
}
.checkbox:checked + label:after {
  left: 26px;
}
.checkbox:focus + label:before {
  box-shadow: inset 0 2px 3px rgba(0,0,0,.2), 0 0 0 3px rgba(255,255,0,.7);
}

  </style>
  <script>
    var switchState = false;
    var iCan = true;
    window.setInterval(function(){
      if (iCan) ajaxLoad('STATE');
    }, 1000);
    function switchLED(chk) {
      iCan = false;
      if (chk.checked) {
        ajaxLoad('LEDON');
        switchState = true;
      }
      else {
        ajaxLoad('LEDOFF');
        switchState = false;
      }
      //console.log(switchState);
      iCan = true;
    }
    var ajaxRequest = null;
    if (window.XMLHttpRequest)  { ajaxRequest =new XMLHttpRequest(); }
    else                        { ajaxRequest =new ActiveXObject("Microsoft.XMLHTTP"); }

    function ajaxLoad(ajaxURL) {
      if(!ajaxRequest){ alert("AJAX is not supported."); return; }
      //console.log('ajaxLoad');
      ajaxRequest.open("GET",ajaxURL,true);
      ajaxRequest.onreadystatechange = function() {
        if(ajaxRequest.readyState == 4 && ajaxRequest.status==200) {
          var ajaxResult = ajaxRequest.responseText;
          //console.log(ajaxResult);
          if ( ajaxResult.indexOf('LEDON') !== -1 ) {
            //console.log('LEDON received');
            document.getElementById('checkbox').checked = true;
          } else if ( ajaxResult.indexOf('LEDOFF') !== -1 ) {
            //console.log('LEDOFF received');
            document.getElementById('checkbox').checked = false;
          }
        }
      }
      ajaxRequest.send();
    }
  </script>
  <title>Smart Home</title>
</head>
<body>
  <div id='main'>
    <input type="checkbox" class="checkbox" id="checkbox" onclick="switchLED(this)" />
    <label for="checkbox"></label>
  </div>
</body>
</html>)=====";
 
Сверху Снизу