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

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>)=====";
 
Сверху Снизу