Как стать автором
Обновить

Очередной умный дом (или как потерять 2 месяца из-за одной глупой ошибки)

Время на прочтение43 мин
Количество просмотров19K

Предыстория

я, не знающий своей глупости и наивности
я, не знающий своей глупости и наивности

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

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

Данный пост - мой переделанный диплом, да и вообще первая попытка в написании таких вещей )

План

Для успешной и продуктивной работы нужен план, для себя я составил его примерно таким:

  1. Идея и идеология умного дома.

  2. Железо для построения умного дома.

  3. Код для каждой из частей умного дома.

В принципе, для начала это более чем достаточно. Теперь можно разобрать каждый пункт отдельно.

Идея и идеология умного дома

Посидев несколько дней и обдумав все идеи, в моей голове всплыла утопическая идея такой системы, которая не нуждалась бы в базовом модуле, а каждый такой модуль будет полностью автономен. У системы такого типа, в отличии от той, что использует в своей основе базовый модуль (примером может быть умный дом xiaomi), имеется несколько плюсов и минусов.
Начнем с плюсов. Главный плюс - отказоустойчивость, даже если один из управляющих модулей выйдет из строя, датчики все равно будут отправлять данные пользователю. Второй же плюс - возможность связи модулей на больших расстояниях.

А теперь про минусы: самый значительный минус - автономности, ведь на то, чтобы проснуться, подключиться к интернету, отправить данные и уснуть тратится больше энергии, чем на то, чтобы передать данные по радиоканалу. Следующий минус состоит в необходимости наличия интернета для работы каждого датчика, иначе он просто не сможет отправить данные.
В итоге я составил список функционала, который умный дом должен реализовывать в ИДЕАЛЕ:

  1. Автономные модули.

  2. Взаимозаменяемость модулей.

  3. Масштабируемость системы.

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

И да, забыл сказать, модуль - единичное устройство системы умного дома, будь то wi-fi розетка или датчик температуры.

Для приемо-передачи данных я выбрал MQTT, довольно известный и удобный протокол передачи.

Микроконтроллер

Микроконтроллером, который является основой для каждого модуля умного дома, я выбрал всем хорошо знакомый, даже немного заезженный ESP-12F . Он хорош своими возможностями, в которые входит функция WI-FI (как соединение, так и точка доступа) и отличная производительность, которой с лихвой хватило на все мои задумки, а цена за штуку не превышает 100 рублей в Китае, если брать сразу 10 штук.

распиновка и внутреннее устройство esp12F
распиновка и внутреннее устройство esp12F

Вообще есть несколько версий esp12, самая первая - esp12Е, она по всем фронтам уступает более новому esp12F. Основные их отличия в форме антенны, у F-версии она сделана более удачно, и в размере Flash памяти: у E-версии в большинстве случаев она составляет 1МБ, в то время как у F-версии она уже 4МБ. Также, вроде как различия в внутренней компоновки компонентов, которая удачнее в F-версии. Еще можно вспомнить самую новую версию - esp12S, которая практически идентична (если я правильно разобрался) своей начинкой, но сделана в более компактном и удобном корпусе для smd пайки.

три версии esp12 рядом
три версии esp12 рядом

Устройство WI-FI розетки

Теперь можно рассмотреть каждый модуль, всего их будет 3.

Что из себя вообще представляет умная (wifi) розетка - устройство, включаемый в обычную розетку и которое может управлять (как автоматически, так и в ручном режиме) нагрузкой, включенную в это устройство.

Есть два основных способа реализовать это:

1 способ - использовать реле, вместе с транзисторным ключом (реле 5В, а микроконтроллер питается от 3.3В).
2 способ - использовать симистор.

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

симистор и реле
симистор и реле

Я решил все же использовать реле, ведь оно было уже куплено и схема была отработана, а с симистор пришлось бы разбираться.

принципиальная схема
принципиальная схема

Как видно на схеме, для правильной работы реле, ему нужен транзисторный ключ, ведь esp12F питается от 3.3В, а реле на 5В, да и такую нагрузку лучше не включать прямо в пин микроконтроллера, может сгореть.

Как AC/DC преобразователь может использоваться любой блок питания без корпуса, я использовал готовый для таких целей блок питания с алика, с 220В переменного на 5В постоянного и 1А на выходе, что даже избыточно, ведь вся схема в работе потребляет не более 0,3-0,2А. Для транзисторного ключа идеально подошел кт315, который уже хорошо известен многим радиолюбителям.

Как было сказано раньше, esp12F питается от 3.3В, а блок питания выдает все 5В, для того, чтобы МК не сгорел, для его питания, сразу после блока питания стоит понижающий линейный преобразователь AMS1117.

печатная плата для wi-fi розетки
печатная плата для wi-fi розетки

Для корпуса были куплены обычная розетка и вилка, которые впоследствии были уничтожены (разобраны на части), а основной корпус был напечатан на 3д принтере.

корпус в реальности и корпус в fusion360
корпус в реальности и корпус в fusion360
внутреннее строение модуля
внутреннее строение модуля

Печатная плата сделана ЛУТом, по рецепту Гайвера, только под утюгом лучше держать около 10 минут, а то краска некорректно перейдет на стеклотекстолит.

плата с стороны компонентов
плата с стороны компонентов
неудачные платы
неудачные платы

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

Устройство модулей датчиков

Оба модулей датчиков имеют практически идентичное внутреннее устройство, различия только в самих датчиках, в одном это DHT11, а в другом DS18B20 и их обвязках.

Модули выполняют очень простую функцию: отправлять показания с датчиков по MQTTT.

принципиальная схема модуля для датчика DS18B20(для DHT11 отличие будет только в резисторе, который будет не 4.7К,а 10К)
принципиальная схема модуля для датчика DS18B20(для DHT11 отличие будет только в резисторе, который будет не 4.7К,а 10К)
печатная плата для модуля датчика(зеленое на плате - проводки на другой стороне)
печатная плата для модуля датчика(зеленое на плате - проводки на другой стороне)

В этот раз корпус был полностью напечатан на 3д принтере, ведь даже так, себестоимость корпусов дешевле, чем покупать уже готовые коробочки, которые придется дорабатывать!

корпус в реале и корпус в fusion 360
корпус в реале и корпус в fusion 360
корпус без крышки
корпус без крышки

Питание модуля идет от 3 батареек типа АА.

Прошивка модулей

Наверное самый интересный и самый долгий пункт....

Сначала я хотел использовать RTOS-SDK от ESP, но понял что уйдет много времени чтобы разобраться в нем, поэтому я остановился на Arduino, хоть и от FreeRTOS пришлось отказаться.

Но, в принципе, для моего решения RTOS не нужна.

Для прошивки можно использовать любой программатор, совместимый с esp, но главное не перепутать перемычки (если они есть конечно) логического уровня и не поставить их на 5в, иначе esp12 может попросту сгореть.

Подключал я по этой схеме... и вот как раз таки та загвоздка, из-за которой я и потратил 2 месяца в пустую...забыл минус микроконтроллера к минусу программатора соединить! Оказывается, не дурак эту схему начертил, а дурак её пытался повторить). Пока искал где проблема, переделал много плат, заказал новые микроконтроллеры, подумал что с ними проблема, новый программатор взял, перерыл форумы с этой проблемой...

Ну да ладно, главное что все заработало.

MQTT

Как я и говорил раньше, решил использовать MQTT для своих целей. Сам протокол состоит из нескольких сущностей:

  • Брокер - сервер, который управляет передачей данных, создает топики.

  • Топик - канал передачи данных, на который может подписываться устройство "подписчик" и в который может публиковать данные устройство "издатель".

  • Подписчик - устройство, подписывающееся на топик и получаемое из этого топика все данные, публикующиеся в него.

  • Издатель - устройство, публикующее в топик данные .

Примерная схема передачи данных по протоколу MQTT. Разные цвета - разные топики.
Примерная схема передачи данных по протоколу MQTT. Разные цвета - разные топики.

Одно и тоже устройство может быть как подписчиком, так и издателем одновременно, причем в разных топиках.

Изначально была идея использовать брокер от yandex iot, но он оказался менее удобным для моего проекта, хотя можно сразу прикрепить сервер обработки и хранения данных, но это стоило бы довольно дорого, поэтому выбрал wqtt, который стоит всего 300р в год и можно добавить поддержку Яндекс Алисы).

Настройка модулей

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

Давайте разберем мою реализацию режима настройки.

Для начала, нам нужно подключить нужные библиотеки:

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

Следом идут определение константан для точки доступа самого модуля, wifi к которому она подключается и к MQTT:

#define RELEPIN 4 // пин МК с реле

const char *ssid_ap = "Rozetka_Setup";  //имя точки доступа модуля
const char *password_ap = "12345678";  //пароль точки доступа модуля
const char *ID = "rele_1";// типовое, статичное ID модуля

String ssid = "";  //имя wifi
String password = "";  //пароль wifi
String device_id; // пользовательское ID модуля
bool setup_mode; // true - первичная настройка модуля, false - основная работа модуля
bool rele;// true - реле включено, false - реле выключено

const char *mqtt_server = "M5.WQTT.RU"; // Имя сервера MQTT
const int mqtt_port = 2602; // Порт для подключения к серверу MQTT
const char *mqtt_user = "user"; // Логин от серверa
const char *mqtt_pass = "pass"; // Пароль от сервера

IPAddress local_ip(192, 168, 1, 1);//IP для точки доступа модуля
IPAddress gateway(192, 168, 1, 1);//гейт для точки доступа модуля
IPAddress subnet(255, 255, 255, 0);//маска для точки доступа модуля

IPAddress local_ip_2(192, 168, 0, 250);//IP для WIFI
IPAddress gateway_2(192, 168, 0, 1);//гейт для WIFI
IPAddress subnet_2(255, 255, 255, 0);//маска для WIFI

ESP8266WebServer server(80);// server для настройки

WiFiClient wclient;
PubSubClient client(wclient, mqtt_server, mqtt_port);

Setup блок программы:

pinMode(RELEPIN, OUTPUT);
Serial.begin(115200);
EEPROM.begin(256);//подключаем EEPROM
setup_mode = EEPROM.read(45);// читаем из EERPOM текущий режим
rele = EEPROM.read(60);// читаем из EERPOM текущее состояние реле
EEPROM.end();//отключаем EEPROM
if (rele) digitalWrite(RELEPIN, HIGH); else digitalWrite(RELEPIN, LOW);

delay(1000);
WiFi.softAP(ssid_ap, password_ap);//настраиваем точку доступа(название и пароль)
WiFi.softAPConfig(local_ip, gateway, subnet);//настраиваем точку доступа(ip для подключения)
delay(100);
server.on("/", handle_OnConnect);//handle для первой начальной страницы
server.on("/end_setup", handle_EndSetup);//handle для окончания настройки
server.on("/action_page", handleForm);//handle для выбора продолжить или закончить настройку
server.onNotFound(handle_NotFound);
server.begin();//запуск сервера
Serial.println("HTTP server started");

Почему я использую подключение по через браузер по типу "http://192.168.1.1/", я попросту не нашел альтернативы . Конечно, есть технология Mdns, которая позволяет обращаться через браузер просто по имени "http://esp8266/", но её не поддерживают android устройства ! Я не понимаю почему, но именно так, гугл не может добавить поддержку Mdns, хотя у эпл она давно уже есть...

Как работают hadle ? Изначально мы переходим по адресу http://192.168.1.1/, а последняя "/" и является своеобразным "указателем" на определенный handle, а сам по себе любой handle является программой, который мы настраиваем. Например адрес "http://192.168.1.1/end_setup" указывает на handle, который завершает настройку модуля.

Рассмотрим сами handle:

данный Handle передает пользователю веб-страничку для настройки модуля-розетки, для модулей -датчиков данная страничка будет немного иная -

void handle_OnConnect() {// handle, который отправляет начальную страницу
  server.send(200, "text/html", SendHTML());
}

String SendHTML() {//отправка HTML страницы для настройки датчика- розетки
  String ptr = "<!DOCTYPE html><html lang='ru-RU'><head><meta charset='UTF-8'/> <html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>WIFI Control</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr += ".button {display: block;width: 80px;background-color: #1abc9c;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr += ".button-on {background-color: #1abc9c;}\n";
  ptr += ".button-on:active {background-color: #16a085;}\n";
  ptr += ".button-off {background-color: #34495e;}\n";
  ptr += ".button-off:active {background-color: #2c3e50;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<h1>Настройка WIFI</h1>\n";
  ptr += "<h3>Укажите название и пароль от нужной wifi сети</h3>\n";
  ptr += "<form action=\"/action_page\">";
  ptr += "Название:<br>";
  ptr += "<input type=\"text\" name=\"WIFI_NAME\" value=\"тест_название\">";
  ptr += "<br>";
  ptr += "Пароль:<br>";
  ptr += "<input type=\"text\" name=\"WIFI_password\" value=\"123456789\">";
  ptr += "<br>";
  ptr += "Название модуля:<br>";
  ptr += "<input type=\"text\" maxlength=\"14\" name=\"DEVICE_ID\" value=\"розетка\"><br>";
  ptr += "Local_ip:<br>";
  ptr += "<input type=\"text\" name=\"LOCAL_IP\" value=\"192.168.0.250\">";
  ptr += "<br>";
  ptr += "Gateway:<br>";
  ptr += "<input type=\"text\" name=\"GATEWAY\" value=\"192.168.0.1\">";
  ptr += "<br>";
  ptr += "<br><br>";
  ptr += "<input type=\"submit\" value=\"Закончить\">";
  ptr += "</form>";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}

String SendHTML() {//Отправка HTML страницы для настройки модуля - датчика
  String ptr = "<!DOCTYPE html><html lang='ru-RU'><head><meta charset='UTF-8'/> <html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>WIFI Control</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr += ".button {display: block;width: 80px;background-color: #1abc9c;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr += ".button-on {background-color: #1abc9c;}\n";
  ptr += ".button-on:active {background-color: #16a085;}\n";
  ptr += ".button-off {background-color: #34495e;}\n";
  ptr += ".button-off:active {background-color: #2c3e50;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<h1>Настройка WIFI</h1>\n";
  ptr += "<h3>Укажите название и пароль от нужной wifi сети</h3>\n";
  ptr += "<form action=\"/action_page\">";
  ptr += "Название:<br>";
  ptr += "<input type=\"text\" name=\"WIFI_NAME\" value=\"тест_название\">";
  ptr += "<br>";
  ptr += "Пароль:<br>";
  ptr += "<input type=\"text\" name=\"WIFI_password\" value=\"123456789\">";
  ptr += "<br>";
  ptr += "Тайминг отправки(в секундах):<br>";
  ptr += "<input type=\"text\" name=\"TIMING\" value=\"1\">";
  ptr += "<br>";
  ptr += "Название модуля:<br>";
  ptr += "<input type=\"text\" maxlength=\"10\" name=\"DEVICE_ID\" value=\"datc_temp\"><br>";
  ptr += "<br><br>";
  ptr += "<input type=\"submit\" value=\"Закончить\">";
  ptr += "</form>";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}
Меню для настройки: слева - для модуля-датчика, справа - для модуля-розетки
Меню для настройки: слева - для модуля-датчика, справа - для модуля-розетки

По нажатии кнопки "закончить" в действие вступает следующий handle -

void handleForm() {//версия для модуля - розетки
  ssid = server.arg("WIFI_NAME");
  password = server.arg("WIFI_password");
  device_id = server.arg("DEVICE_ID");
  local_ip_2.fromString(server.arg("LOCAL_IP"));
  gateway_2.fromString(server.arg("GATEWAY"));
  write_string_EEPROM(200, server.arg("LOCAL_IP"));
  write_string_EEPROM(220, server.arg("GATEWAY"));
  Serial.print("WIFI: ");
  Serial.println(ssid);

  Serial.print("password: ");
  Serial.println(password);

  server.send(200, "text/html", SendEndHTML()); //Send web page
}

void handleForm() {//версия для модуля - датчика
  ssid = server.arg("WIFI_NAME");
  password = server.arg("WIFI_password");
  device_id =  server.arg("DEVICE_ID");
  tmi = server.arg("TIMING").toInt();
  Serial.print("WIFI: ");
  Serial.println(ssid);

  Serial.print("password: ");
  Serial.println(password);

  server.send(200, "text/html", SendEndHTML()); //Send web page
}

String SendEndHTML() {
  String ptr = "<!DOCTYPE html><html lang='ru-RU'><head><meta charset='UTF-8'/><html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>WIFI Control</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<a href='/'><h1>Вернуться к настройки</h1></a>\n";
  ptr += "<a href='/end_setup'><h1>Продолжить</h1></a>\n";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}
форма выбора
форма выбора

При нажатии на кнопку "Вернуться к настройки" пользователь вернется на первую страницу, а при нажатии на кнопку "продолжить" сработает следующий handle, который перезапишет в EEPROM ssid и пароль от WIFI, переведет модуль в режим нормальной работы -

void handle_EndSetup() {
  write_string_EEPROM(0, ssid);
  write_string_EEPROM(20, password);
  EEPROM.begin(256);
  EEPROM.write(45, false);
  setup_mode = false;
  EEPROM.commit();
  EEPROM.end();
  WiFi.softAPdisconnect(true);
  ESP.reset();
}

Немного про саму запись в EEPROM. Для записи и чтения строк в него используются две функции -

void write_string_EEPROM (int Addr, String Str) {//Запись строки в EEPROM
//Addr - начальный байт, str - строка. Строка не может быть больше 15 символов
  byte lng = Str.length();
  EEPROM.begin (256);
  EEPROM.write(Addr , lng);
  unsigned char* buf = new unsigned char[15];
  Str.getBytes(buf, lng + 1);
  Addr++;
  for (byte i = 0; i < lng; i++) {
    EEPROM.write(Addr + i, buf[i]);
    delay(10);
  }
  EEPROM.commit();
  EEPROM.end();
}

char read_string_EEPROM (int Addr) {// Чтение строки из EEPROM
//Addr - начальный байт
  EEPROM.begin(256);
  byte lng = EEPROM.read(Addr);
  char buf = new char[15];
  Addr++;
  for (byte i = 0; i < lng && i < 15; i++) buf[i] = char(EEPROM.read(i + Addr));
  buf[lng] = '\x0';
  EEPROM.end();
  return buf;
}

Вообще, хорошо бы вместо EEPROM использовать FS.h (файловую систему),но тогда до меня это не дошло.

Режим нормально работы (модуль - розетка)

Теперь, когда наши модули настроены, можно рассмотреть их основные функции.

Рассмотрим SETUP, который запускается при старте модуля в данном режиме:

pinMode(RELEPIN, OUTPUT);
Serial.begin(115200);
EEPROM.begin(256);//подключаем EEPROM
setup_mode = EEPROM.read(45);// читаем из EERPOM текущий режим
rele = EEPROM.read(60);// читаем из EERPOM текущее состояние реле
EEPROM.end();//отключаем EEPROM
if (rele) digitalWrite(RELEPIN, HIGH); else digitalWrite(RELEPIN, LOW);

local_ip_2.fromString(String(read_string_EEPROM(200)));//читаем IP из eeprom 
    gateway_2.fromString(String(read_string_EEPROM(220)));//читаем гейт из eeprom 
    WiFi.begin(read_string_EEPROM(0), read_string_EEPROM(20));
    int sm = 0;
    while (WiFi.status() != WL_CONNECTED) {
      delay(500); sm++;
      Serial.print(".");
      if (sm > 120) {
        handle_ReturnSetup();
      }
    }
    WiFi.config(local_ip_2, gateway_2, subnet_2);
    Serial.println("WiFi connected");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    read_auto();
    server.on("/", handle_OnConnect_2);
    server.on("/select_auto", handle_SelectAuto);
    server.on("/rele_auto", handle_ReleAuto);
    server.on("/delete_page", handle_Delete);
    server.on("/return_setup", handle_ReturnSetup);
    server.on("/control_menu", handle_ControlMenu);
    server.on("/rele_off", handle_ReleOff);
    server.on("/rele_on", handle_ReleOn);
    server.on("/auto_off", handle_AutoOff);
    server.on("/auto_on", handle_AutoOn);
    server.onNotFound(handle_NotFound);
    server.begin();
    Serial.println("HTTP server started");

    if (client.connect(MQTT::Connect(ID).set_auth(mqtt_user, mqtt_pass))) {
      Serial.println("Connected to MQTT server");
      client.subscribe("/datk"); // подписываемся на топик с данными датчиков
      //client.subscribe("/cmd"); // подписываемся на топик с командами
      client.set_callback(callback);
    } else {
      Serial.println("Could not connect to MQTT server");
    }

Обработчик MQTT:


void perek(bool per) { //per = true = прямой режим, per = true = обратный режим
  EEPROM.begin(256);
  if (auto_mode) {
    if (per) {
      rele = 0;
      digitalWrite(RELEPIN, LOW);
    }
    else {
      rele = 1;
      digitalWrite(RELEPIN, HIGH);
    }
  }
  else {
    if (per) {
      rele = 1;
      digitalWrite(RELEPIN, HIGH);
    }
    else {
      rele = 0;
      digitalWrite(RELEPIN, LOW);
    }
  }
  EEPROM.write(60, rele);
  EEPROM.commit();
  EEPROM.end();
}

void callback(const MQTT::Publish& pub)
{
  Serial.print(pub.topic()); // выводим в сериал порт название топика
  Serial.print(" => ");
  Serial.print(pub.payload_string()); // выводим в сериал порт значение полученных данных
  String payload = pub.payload_string();
  if (String(pub.topic()) == "/datk") // проверяем из нужного ли нам топика пришли данные
  {
    String tmpstr = "", namest, znachs;
    int datat;
    int mod = 0;
    bool tr = false;
    for (int i = 0 ; i < payload.length(); i++) {
      if (payload[i] != '#') tmpstr += payload[i]; else {
        switch (mod) {
          case 0:
            namest = tmpstr;
            break;
          case 1:
            datat = tmpstr.toInt();
            break;
          case 2:
            znachs = tmpstr;
            break;
        }
        tmpstr = "";
        mod++;
      }
    }
    for (int i = 0; i < lng && !tr; i++) {
      if (names[i] == namest) {
        data[i] = datat;
        tr = true;
      }
    }
    if (!tr) {
      lng++;
      names[lng - 1] = namest;
      data[lng - 1] = datat;
      znach[lng - 1] = znachs;
    }
    if (auto_stat && namest == auto_name) {
      switch (auto_oper) {
        case 0:
          if (auto_data > datat) perek(true);
          if (auto_data < datat) perek(false);
          break;
        case 1:
          if (auto_data < datat) perek(true);
          if (auto_data > datat) perek(false);
          break;
        case 2:
          if (auto_data = datat) perek(true);
          if (auto_data != datat) perek(false);
          break;
      }
    }
  }

Функция "Perek" используется для правильного переключения реле.

В блоке с подключением к WIFI, если модуль минуту не может подключиться к WIFI, то он переходит в режим настройки.

Первый же handle:

void handle_OnConnect_2() {
  server.send(200, "text/html", SendMenuHTML());
}

String SendMenuHTML() {
  String ptr = "<!DOCTYPE html> <html lang='ru-RU'><head><meta charset='UTF-8'/><html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>WIFI Control</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr += ".button {display: block;width: 80px;background-color: #1abc9c;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr += ".button-on {background-color: #1abc9c;}\n";
  ptr += ".button-on:active {background-color: #16a085;}\n";
  ptr += ".button-off {background-color: #34495e;}\n";
  ptr += ".button-off:active {background-color: #2c3e50;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<h1>WIFI меню</h1>\n";
  ptr += "<h3>Выберете пункт меню:</h3>\n";
  ptr += "<br><input type=\"button\" value=\"Вернуться в режим настройки\" onClick=\"document.location = '/return_setup'\" /><br>";
  ptr += "<br><input type=\"button\" value=\"Панель управления\" onClick=\"document.location = '/control_menu'\" /><br>";
  ptr += "<br><input type=\"button\" value=\"Автоматическое управление розеткой\" onClick=\"document.location = '/rele_auto'\" /><br>";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}

Само меню выглядит так:

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

Возвращение к меню управления происходит через handle:

void handle_ReturnSetup() {
  EEPROM.begin(256);
  EEPROM.write(45, true);
  setup_mode = true;
  EEPROM.commit();
  EEPROM.end();
  ESP.reset();
}

Для перехода к меню управления используется handle (Больше handle богу handle!):

void handle_ControlMenu() {
  server.send(200, "text/html", SendControlMenuHTML());
}

String SendControlMenuHTML() {
  String ptr = "<!DOCTYPE html><html lang='ru-RU'><head><meta charset='UTF-8'/> <html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>WIFI Control</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px; } h3 {color: #444444;margin-bottom: 50px;} h4 {color: #444444;margin-bottom: 20px;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<h1>Панель управления</h1>\n";
  ptr += "<br><input type=\"button\" value=\"Вернуться в меню\" onClick=\"document.location = '/'\" /><br>";
  if (rele) {
    ptr += "<br><input type=\"button\" value=\"Включить розетку\" onClick=\"document.location = '/rele_off'\" /><br>";
  }
  if (!rele) {
    ptr += "<br><input type=\"button\" value=\"Выключить розетку\" onClick=\"document.location = '/rele_on'\" /><br>";
  }
  ptr += "<h3>Датчики:</h3>\n";
  //ptr += "<h4>Датчики:</h4>\n";
  for (int i = 0 ; i < lng; i++) {
    ptr += "<h4>" + names[i] + " : " + data[i] + " " + znach[i] + "</h4>";
  }
  ptr += "<form action=\"/delete_page\">";
  ptr += "Название:  ";
  ptr += "<input type=\"text\" name=\"DELETE_NAME\" value=\"модуль\">   ";
  //ptr += "<br><br>";
  ptr += "<input type=\"submit\" value=\"удалить\">";
  ptr += "</form>";
  ptr += "<br><input type=\"button\" value=\"обновить\" onClick=\"document.location = '/control_menu'\" /><br>";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}
Панель управления
Панель управления

Рассмотрим элементы управления сверху-вниз. В самом верху находится кнопка возвращения в первоначальное меню, следом идет управления реле/розеткой, меняющее свое значение в зависимости от состояние реле, и в зависимости от него используются один из этих handle:

void handle_ReleOff() {//выключание реле
  rele = 0;
  digitalWrite(RELEPIN, LOW);
  EEPROM.begin(256);
  EEPROM.write(60, 0);
  EEPROM.commit();
  EEPROM.end();
  server.send(200, "text/html", SendControlMenuHTML());
}

void handle_ReleOn() {//включение реле
  rele = 1;
  digitalWrite(RELEPIN, HIGH  );
  EEPROM.begin(256);
  EEPROM.write(60, 1);
  EEPROM.commit();
  EEPROM.end();
  server.send(200, "text/html", SendControlMenuHTML());
}

Далее список датчиков и их показаний, которые можно удалить в следующей форме. Список датчиков обновляется по мере поступления данных, обновление страницы идет с помощью кнопки "обновить".

Удаление датчиков идет с помощью handle:

void handle_Delete() {
  String delname = server.arg("DELETE_NAME");
  bool tr = false;
  for (int i = 0 ; i < lng; i++) {
    if (tr) {
      names[i - 1] = names[i];
      data[i - 1] = data[i];
      znach[i - 1] = znach[i];
    }
    else if (names[i] == delname) {
      tr = true;
    }
  }
  if (tr) lng--;
  server.send(200, "text/html", SendControlMenuHTML());
}

Программа ищет имя данного датчика и удаляет его, перемещая список на 1 пункт ниже, после найденного элемента.

Поднимемся на уровень выше и перейдем к настройки автоматического управления розеткой.
Сам handle, отвечающий за отправку данной веб-страницы пользователю выглядит так:

void handle_ReleAuto() {
  server.send(200, "text/html", SendControlReleAutoHTML());
}

String SendControlReleAutoHTML() {
  String ptr = "<!DOCTYPE html><html lang='ru-RU'><head><meta charset='UTF-8'/> <html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>WIFI Control</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px; } h3 {color: #444444;margin-bottom: 50px;} h4 {color: #444444;margin-bottom: 20px;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<h1>Настройка автоматического управления розетки </h1>";
  if (auto_stat) {
    ptr += "<h3>Автоматическое управления включено</h3>";
  }
  else {
    ptr += "<h3>Автоматическое управления выключено</h3>";
  }
  ptr += "<h3>Текущее условие:" + auto_name + " ";
  switch (auto_oper) {
    case 0 : ptr += "> ";
      break;
    case 1 : ptr += "< ";
      break;
    case 2 : ptr += "= ";
      break;
  }
  if (auto_mode)
    ptr += String(auto_data) + " выключать </h3>";
  else
    ptr += String(auto_data) + " включать </h3>";
  ptr += "<h3>Выберете датчик и параметр для него:</h3>\n";
  ptr += "<form action=\"/select_auto\">";
  ptr += "<select size=\"1\" name=\"DAT_NAME\">";
  for (int i = 0 ; i < lng; i++) {
    ptr += "<option value=\"" + String(i) + "\">" + names[i] + "</option>";
  }
  ptr += "</select>  ";
  ptr += "<select size=\"1\" name=\"OPER\">";
  ptr += "<option value=0>></option>";
  ptr += "<option value=1><</option>";
  ptr += "<option value=2>=</option>";
  ptr += "</select>  ";
  ptr += "</select>  ";
  ptr += "<input type=\"text\" name=\"DATA_P\" value=\"10\">";
  ptr += "<select size=\"1\" name=\"ON_OFF\">";
  ptr += "<option value=0>Включать</option>";
  ptr += "<option value=1>Выключать</option>";
  ptr += "</select>  ";
  //ptr += "<br><br>";
  ptr += "<input type=\"submit\" value=\"настроить\">";
  ptr += "</form>";
  ptr += "<br><input type=\"button\" value=\"обновить\" onClick=\"document.location = '/rele_auto'\" /><br>";
  if (auto_stat) {
    ptr += "<br><input type=\"button\" value=\"выключить автопереключение\" onClick=\"document.location = '/auto_off'\" /><br>";
  }
  else {
    ptr += "<br><input type=\"button\" value=\"включить автопереключение\" onClick=\"document.location = '/auto_on'\" /><br>";
  }
  ptr += "<br><input type=\"button\" value=\"Вернуться в меню\" onClick=\"document.location = '/'\" /><br>";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}

В начало программы добавляются константы:

String auto_name = "example";
int auto_data = 10;
int auto_oper = 0;
bool auto_stat = false;// false - не работает, true - работает
bool auto_mode = false; //false - включать, true - выключать

String names[10];// массив имен модулей
String znach[10];// массив едениц измерения модулей
int data[10];// массив данных модулей
int lng = 0;//используемая длина
Меню автоматического управления
Меню автоматического управления

Рассмотрим также сверху-вниз, пропуская название сверху. Сначала нам дается информация о том, включено или нет авто управление. Ниже показывается текущее условие. Чтобы перенастроить модуль надо выбрать в форме ниже модуль, условие, значение и действия, а по окончанию нажать кнопку "настроить". Кнопка "обновить" служит для обновления списка датчиков.

Настройка происходит также через handle:

void handle_SelectAuto() {
  auto_name = names[server.arg("DAT_NAME").toInt()];
  auto_oper = server.arg("OPER").toInt();
  auto_data = server.arg("DATA_P").toInt();
  auto_mode = server.arg("ON_OFF").toInt();
  write_string_EEPROM(70, auto_name);
  write_string_EEPROM(110, server.arg("OPER"));
  write_string_EEPROM(90, server.arg("DATA_P"));
  EEPROM.begin(256);
  EEPROM.write(130, auto_mode);
  EEPROM.commit();
  EEPROM.end();
  server.send(200, "text/html", SendControlReleAutoHTML());
}

Включение/выключение авто управление происходит по типу выключения/выключения реле:

void handle_AutoOff() {//отключение авто управления
  auto_stat = 0;
  server.send(200, "text/html", SendControlReleAutoHTML());
}

void handle_AutoOn() {//включение авто управления
  auto_stat = 1;
  server.send(200, "text/html", SendControlReleAutoHTML());
}

Ну и последняя кнопка возвращает обратно в меню.

Режим нормально работы (модуль - датчик)

Для обоих датчиков смысл данного режима одинаков, различия только самих датчиках и в способе обращения к ним.

После перехода в режим настройки работы, модуль должен проснуться, подключиться к WIFI и отправить данные по MQTT.

Вся программа содержится в блоке SETUP, рассмотрим сначала датчик на DHT11:

#include "DHT.h"//библиотека DHT

#define DHTTYPE DHT11
uint8_t DHTPin = 5; 
int tempC = 0;
int humC = 0;
int tmi = 10;// тайминг отправки данных
DHT dht(DHTPin, DHTTYPE); 

void setup() {
  pinMode(DHTPin, INPUT);
  Serial.begin(115200);
  EEPROM.begin(256);
  setup_mode = EEPROM.read(85);
  EEPROM.end();
  device_id = read_string_EEPROM(40);
  if (!setup_mode) {//режим нормально работы
    dht.begin();
    tmi = String(read_string_EEPROM(200)).toInt(); //считываем тайминг отправки данных
    local_ip.fromString(String(read_string_EEPROM(200)));
    gateway.fromString(String(read_string_EEPROM(220)));
    WiFi.begin(read_string_EEPROM(0), read_string_EEPROM(20));
    int sm = 0;
    while (WiFi.status() != WL_CONNECTED) {
      delay(500); sm++;
      Serial.print(".");
      if (sm > 120) {
        ReturnSetup();
      }
    }
    
    Serial.println("WiFi connected");
    tempC = (int)dht.readTemperature(); //считывание температуры в цельсиях
  humC = (int)dht.readHumidity(); //считывание влажности воздуха
    if (client.connect(MQTT::Connect(ID).set_auth(mqtt_user, mqtt_pass))) {
      Serial.println("Connected to MQTT server");
    } else {
      Serial.println("Could not connect to MQTT server");
    }
    client.publish("/datk", device_id+"_t" + "#" + String(tempC) + "#градусов#");
    client.publish("/datk", device_id+"_hm" + "#" + String(humC) + "#влажности#");
    delay(100);
    ESP.deepSleep(tmi * 1000000);//модуль засыпает на определенное время
  }
  else {//режим настройки
    delay(1000);
    WiFi.softAP(ssid_ap, password_ap);
    WiFi.softAPConfig(local_ip, gateway, subnet);
    delay(100);
    server.on("/", handle_OnConnect);
    server.on("/end_setup", handle_EndSetup);
    server.on("/action_page", handleForm);
    server.onNotFound(handle_NotFound);
    server.begin();
    Serial.println("HTTP server started");
  }
}

Теперь SETUP для модуля с ds18b20:

#include <DallasTemperature.h>//библиотека ds18b20

#define ONE_WIRE_BUS 5
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature DS18B20(&oneWire);
char temperatureCString[7];
int tempC = 0;
int tmi = 10;//тайминг отправки данных

void getTemperature()//считывание температуры с датчика
{
  do {
    DS18B20.requestTemperatures();
    tempC = DS18B20.getTempCByIndex(0);
    dtostrf(tempC, 2, 2, temperatureCString);
    delay(100);
  } while (tempC == 85.0 || tempC == (-127.0));
}

void setup() {
	Serial.begin(115200);
  EEPROM.begin(256);
  setup_mode = EEPROM.read(85);
  EEPROM.end();
  device_id = read_string_EEPROM(40);
  if (!setup_mode) {//режим нормально работы
    DS18B20.begin();
    tmi = String(read_string_EEPROM(200)).toInt(); //считываем тайминг отправки данных
    local_ip.fromString(String(read_string_EEPROM(200)));
    gateway.fromString(String(read_string_EEPROM(220)));
    WiFi.begin(read_string_EEPROM(0), read_string_EEPROM(20));
    int sm = 0;
    while (WiFi.status() != WL_CONNECTED) {
      delay(500); sm++;
      Serial.print(".");
      if (sm > 120) {
        ReturnSetup();
      }
    }
    getTemperature();//считываем температуру
    Serial.println("WiFi connected");
    if (client.connect(MQTT::Connect(ID).set_auth(mqtt_user, mqtt_pass))) {
      Serial.println("Connected to MQTT server");
    } else {
      Serial.println("Could not connect to MQTT server");
    }
    client.publish("/datk", device_id + "#" + String(tempC) + "#градусов#");м;
    delay(100);
    ESP.deepSleep(tmi * 1000000);//модуль засыпает на определенное время
  }
  else {//режим настройки
    delay(1000);
    WiFi.softAP(ssid_ap, password_ap);
    WiFi.softAPConfig(local_ip, gateway, subnet);
    delay(100);
    server.on("/", handle_OnConnect);
    server.on("/end_setup", handle_EndSetup);
    server.on("/action_page", handleForm);
    server.onNotFound(handle_NotFound);
    server.begin();
    Serial.println("HTTP server started");
  }
}

Тут также, как и с датчиком - розеткой, если модуль не может подключиться к WIFI более 1 минуты, то модуль переходит в режим настройки. В момент "сна" ESP практически полностью выключена, работает только RTC таймер, а просыпается от того, что пин, подключенный к RTC таймеру и к пину "Reset", подаёт положительный сигнал, перезагружая МК .

Видео с работой системы

Заключение

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

Листинги и файлы

Листинг программы для модуля - розетки
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>
#include <PubSubClient.h>

#define RELEPIN 4

const char *ssid_ap = "Rozetka_Setup";  //имя точки доступа модуля
const char *password_ap = "12345678";  //пароль точки доступа модуля
const char *ID = "rele_1";

String ssid = "";  //имя wifi
String password = "";  //пароль wifi
String device_id; // ID модуля
bool setup_mode; // true - первичная настройка модуля, false - основная работа модуля
bool rele;

String auto_name = "example";
int auto_data = 10;
int auto_oper = 0;
bool auto_stat = false;// false - не работает, true - работает
bool auto_mode = false; //false - включать, true - выключать

const char *mqtt_server = "M5.WQTT.RU"; // Имя сервера MQTT
const int mqtt_port = 2602; // Порт для подключения к серверу MQTT
const char *mqtt_user = "u_RSELYN"; // Логин от серверa
const char *mqtt_pass = "bhbtIJue"; // Пароль от сервера

String names[10];// массив имен модулей
String znach[10];// массив единиц измерения модулей
int data[10];// массив данных модулей
int lng = 0;//используемая длина


IPAddress local_ip(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

IPAddress local_ip_2(192, 168, 0, 250);
IPAddress gateway_2(192, 168, 0, 1);
IPAddress subnet_2(255, 255, 255, 0);

ESP8266WebServer server(80);// server для настройки

WiFiClient wclient;
PubSubClient client(wclient, mqtt_server, mqtt_port);

void write_string_EEPROM (int Addr, String Str) {
  byte lng = Str.length();
  EEPROM.begin (256);
  EEPROM.write(Addr , lng);
  unsigned char* buf = new unsigned char[15];
  Str.getBytes(buf, lng + 1);
  Addr++;
  for (byte i = 0; i < lng; i++) {
    EEPROM.write(Addr + i, buf[i]);
    delay(10);
  }
  EEPROM.commit();
  EEPROM.end();
}

char *read_string_EEPROM (int Addr) {
  EEPROM.begin(256);
  byte lng = EEPROM.read(Addr);
  char* buf = new char[15];
  Addr++;
  for (byte i = 0; i < lng && i < 15; i++) buf[i] = char(EEPROM.read(i + Addr));
  buf[lng] = '\x0';
  EEPROM.end();
  return buf;
}



void setup() {
  pinMode(RELEPIN, OUTPUT);
  Serial.begin(115200);
  EEPROM.begin(256);
  setup_mode = EEPROM.read(45);// читаем из EERPOM текущий режим
  rele = EEPROM.read(60);// читаем из EERPOM текущее состояние реле
  EEPROM.end();
  if (rele) digitalWrite(RELEPIN, HIGH); else digitalWrite(RELEPIN, LOW);
  if (!setup_mode) { //если модуль в режиме нормальной работы
    
    local_ip_2.fromString(String(read_string_EEPROM(200)));//читаем IP из eeprom 
    gateway_2.fromString(String(read_string_EEPROM(220)));//читаем гейт из eeprom 

    //Serial.println(read_string_EEPROM(0));
    //Serial.println(read_string_EEPROM(20));
    WiFi.begin(read_string_EEPROM(0), read_string_EEPROM(20));
    int sm = 0;
    while (WiFi.status() != WL_CONNECTED) {
      delay(500); sm++;
      Serial.print(".");
      if (sm > 120) {
        handle_ReturnSetup();
      }
    }
    WiFi.config(local_ip_2, gateway_2, subnet_2);
    Serial.println("WiFi connected");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    read_auto();
    server.on("/", handle_OnConnect_2);
    server.on("/select_auto", handle_SelectAuto);
    server.on("/rele_auto", handle_ReleAuto);
    server.on("/delete_page", handle_Delete);
    server.on("/return_setup", handle_ReturnSetup);
    server.on("/control_menu", handle_ControlMenu);
    server.on("/rele_off", handle_ReleOff);
    server.on("/rele_on", handle_ReleOn);
    server.on("/auto_off", handle_AutoOff);
    server.on("/auto_on", handle_AutoOn);
    server.onNotFound(handle_NotFound);
    server.begin();
    Serial.println("HTTP server started");

    if (client.connect(MQTT::Connect(ID).set_auth(mqtt_user, mqtt_pass))) {
      Serial.println("Connected to MQTT server");
      client.subscribe("/datk"); // подписывааемся на топик с данными датчиков
      //client.subscribe("/cmd"); // подписывааемся на топик с командами
      client.set_callback(callback);
    } else {
      Serial.println("Could not connect to MQTT server");
    }
  }
  else {//если модуль в режиме настройки
    delay(1000);
    WiFi.softAP(ssid_ap, password_ap);
    WiFi.softAPConfig(local_ip, gateway, subnet);
    delay(100);
    server.on("/", handle_OnConnect);
    server.on("/end_setup", handle_EndSetup);
    server.on("/action_page", handleForm);
    server.onNotFound(handle_NotFound);
    server.begin();
    Serial.println("HTTP server started");
  }
}

void read_auto() {
  EEPROM.begin(256);
  auto_mode = EEPROM.read(130);
  EEPROM.end();
  auto_data = String(read_string_EEPROM(90)).toInt();
  Serial.println(read_string_EEPROM(90));
  auto_oper = String(read_string_EEPROM(110)).toInt();
  auto_name = read_string_EEPROM(70);
}

void perek(bool per) { //per = true = прямой режим, per = true = обратный режим
  EEPROM.begin(256);
  if (auto_mode) {
    if (per) {
      rele = 0;
      digitalWrite(RELEPIN, LOW);
    }
    else {
      rele = 1;
      digitalWrite(RELEPIN, HIGH);
    }
  }
  else {
    if (per) {
      rele = 1;
      digitalWrite(RELEPIN, HIGH);
    }
    else {
      rele = 0;
      digitalWrite(RELEPIN, LOW);
    }
  }
  EEPROM.write(60, rele);
  EEPROM.commit();
  EEPROM.end();
}

void loop() {
  server.handleClient();
  if (client.connected()) {
    client.loop();
  }
}

void callback(const MQTT::Publish& pub)
{
  Serial.print(pub.topic()); // выводим в сериал порт название топика
  Serial.print(" => ");
  Serial.print(pub.payload_string()); // выводим в сериал порт значение полученных данных
  String payload = pub.payload_string();
  if (String(pub.topic()) == "/datk") // проверяем из нужного ли нам топика пришли данные
  {
    String tmpstr = "", namest, znachs;
    int datat;
    int mod = 0;
    bool tr = false;
    for (int i = 0 ; i < payload.length(); i++) {
      if (payload[i] != '#') tmpstr += payload[i]; else {
        switch (mod) {
          case 0:
            namest = tmpstr;
            break;
          case 1:
            datat = tmpstr.toInt();
            break;
          case 2:
            znachs = tmpstr;
            break;
        }
        tmpstr = "";
        mod++;
      }
    }
    for (int i = 0; i < lng && !tr; i++) {
      if (names[i] == namest) {
        data[i] = datat;
        tr = true;
      }
    }
    if (!tr) {
      lng++;
      names[lng - 1] = namest;
      data[lng - 1] = datat;
      znach[lng - 1] = znachs;
    }
    if (auto_stat && namest == auto_name) {
      switch (auto_oper) {
        case 0:
          if (auto_data > datat) perek(true);
          if (auto_data < datat) perek(false);
          break;
        case 1:
          if (auto_data < datat) perek(true);
          if (auto_data > datat) perek(false);
          break;
        case 2:
          if (auto_data = datat) perek(true);
          if (auto_data != datat) perek(false);
          break;
      }
    }
  }

  /*if (String(pub.topic()) == "/сmd"){
    if(pub.payload_string()=="ON")
    {
      rele = 1;
    digitalWrite(RELEPIN, HIGH  );
    EEPROM.begin(256);
    EEPROM.write(60, 1);
    EEPROM.commit();
    EEPROM.end();
    }
    else
    {
      rele = 0;
    digitalWrite(RELEPIN, LOW  );
    EEPROM.begin(256);
    EEPROM.write(60, 0);
    EEPROM.commit();
    EEPROM.end();
    }
    }*/
}


void handle_ReturnSetup() {
  EEPROM.begin(256);
  EEPROM.write(45, true);
  setup_mode = true;
  EEPROM.commit();
  EEPROM.end();
  ESP.reset();
}

void handle_Delete() {
  String delname = server.arg("DELETE_NAME");
  bool tr = false;
  for (int i = 0 ; i < lng; i++) {
    if (tr) {
      names[i - 1] = names[i];
      data[i - 1] = data[i];
      znach[i - 1] = znach[i];
    }
    else if (names[i] == delname) {
      tr = true;
    }
  }
  if (tr) lng--;
  server.send(200, "text/html", SendControlMenuHTML());
}

void handle_ReleAuto() {
  server.send(200, "text/html", SendControlReleAutoHTML());
}


void handle_SelectAuto() {
  auto_name = names[server.arg("DAT_NAME").toInt()];
  auto_oper = server.arg("OPER").toInt();
  auto_data = server.arg("DATA_P").toInt();
  auto_mode = server.arg("ON_OFF").toInt();
  Serial.println(auto_name);
  Serial.println(auto_oper);
  Serial.println(auto_data);
  Serial.println(auto_mode);
  write_string_EEPROM(70, auto_name);
  write_string_EEPROM(110, server.arg("OPER"));
  write_string_EEPROM(90, server.arg("DATA_P"));
  EEPROM.begin(256);
  EEPROM.write(130, auto_mode);
  EEPROM.commit();
  EEPROM.end();
  server.send(200, "text/html", SendControlReleAutoHTML());
}

void handle_AutoOff() {
  auto_stat = 0;
  server.send(200, "text/html", SendControlReleAutoHTML());
}

void handle_AutoOn() {
  auto_stat = 1;
  server.send(200, "text/html", SendControlReleAutoHTML());
}

void handle_ReleOff() {
  rele = 0;
  digitalWrite(RELEPIN, LOW);
  EEPROM.begin(256);
  EEPROM.write(60, 0);
  EEPROM.commit();
  EEPROM.end();
  server.send(200, "text/html", SendControlMenuHTML());
}

void handle_ReleOn() {
  rele = 1;
  digitalWrite(RELEPIN, HIGH  );
  EEPROM.begin(256);
  EEPROM.write(60, 1);
  EEPROM.commit();
  EEPROM.end();
  server.send(200, "text/html", SendControlMenuHTML());
}

void handle_OnConnect() {
  server.send(200, "text/html", SendHTML());
}

void handle_OnConnect_2() {
  server.send(200, "text/html", SendMenuHTML());
}

void handleForm() {
  ssid = server.arg("WIFI_NAME");
  password = server.arg("WIFI_password");
  device_id = server.arg("DEVICE_ID");
  local_ip_2.fromString(server.arg("LOCAL_IP"));
  gateway_2.fromString(server.arg("GATEWAY"));
  write_string_EEPROM(200, server.arg("LOCAL_IP"));
  write_string_EEPROM(220, server.arg("GATEWAY"));
  Serial.print("WIFI: ");
  Serial.println(ssid);

  Serial.print("password: ");
  Serial.println(password);

  server.send(200, "text/html", SendEndHTML()); //Send web page
}

void handle_EndSetup() {
  write_string_EEPROM(0, ssid);
  write_string_EEPROM(20, password);
  EEPROM.begin(256);
  EEPROM.write(45, false);
  setup_mode = false;
  EEPROM.commit();
  EEPROM.end();
  WiFi.softAPdisconnect(true);
  ESP.reset();
}

void handle_ControlMenu() {
  server.send(200, "text/html", SendControlMenuHTML());
}

void handle_NotFound() {
  server.send(404, "text/plain", "Not found");
}

String SendEndHTML() {
  String ptr = "<!DOCTYPE html><html lang='ru-RU'><head><meta charset='UTF-8'/><html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>WIFI Control</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<a href='/'><h1>Вернуться к настройки</h1></a>\n";
  ptr += "<a href='/end_setup'><h1>Продолжить</h1></a>\n";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}

String SendHTML() {
  String ptr = "<!DOCTYPE html><html lang='ru-RU'><head><meta charset='UTF-8'/> <html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>WIFI Control</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr += ".button {display: block;width: 80px;background-color: #1abc9c;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr += ".button-on {background-color: #1abc9c;}\n";
  ptr += ".button-on:active {background-color: #16a085;}\n";
  ptr += ".button-off {background-color: #34495e;}\n";
  ptr += ".button-off:active {background-color: #2c3e50;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<h1>Настройка WIFI</h1>\n";
  ptr += "<h3>Укажите название и пароль от нужной wifi сети</h3>\n";
  ptr += "<form action=\"/action_page\">";
  ptr += "Название:<br>";
  ptr += "<input type=\"text\" name=\"WIFI_NAME\" value=\"тест_название\">";
  ptr += "<br>";
  ptr += "Пароль:<br>";
  ptr += "<input type=\"text\" name=\"WIFI_password\" value=\"123456789\">";
  ptr += "<br>";
  ptr += "Название модуля:<br>";
  ptr += "<input type=\"text\" maxlength=\"14\" name=\"DEVICE_ID\" value=\"розетка\"><br>";
  ptr += "Local_ip:<br>";
  ptr += "<input type=\"text\" name=\"LOCAL_IP\" value=\"192.168.0.250\">";
  ptr += "<br>";
  ptr += "Gateway:<br>";
  ptr += "<input type=\"text\" name=\"GATEWAY\" value=\"192.168.0.1\">";
  ptr += "<br>";
  ptr += "<br><br>";
  ptr += "<input type=\"submit\" value=\"Закончить\">";
  ptr += "</form>";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}

String SendMenuHTML() {
  String ptr = "<!DOCTYPE html> <html lang='ru-RU'><head><meta charset='UTF-8'/><html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>WIFI Control</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr += ".button {display: block;width: 80px;background-color: #1abc9c;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr += ".button-on {background-color: #1abc9c;}\n";
  ptr += ".button-on:active {background-color: #16a085;}\n";
  ptr += ".button-off {background-color: #34495e;}\n";
  ptr += ".button-off:active {background-color: #2c3e50;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<h1>WIFI меню</h1>\n";
  ptr += "<h3>Выберете пункт меню:</h3>\n";
  ptr += "<br><input type=\"button\" value=\"Вернуться в режим настройки\" onClick=\"document.location = '/return_setup'\" /><br>";
  ptr += "<br><input type=\"button\" value=\"Панель управления\" onClick=\"document.location = '/control_menu'\" /><br>";
  ptr += "<br><input type=\"button\" value=\"Автоматическое управление розеткой\" onClick=\"document.location = '/rele_auto'\" /><br>";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}

String SendControlMenuHTML() {
  String ptr = "<!DOCTYPE html><html lang='ru-RU'><head><meta charset='UTF-8'/> <html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>WIFI Control</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px; } h3 {color: #444444;margin-bottom: 50px;} h4 {color: #444444;margin-bottom: 20px;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<h1>Панель управления</h1>\n";
  ptr += "<br><input type=\"button\" value=\"Вернуться в меню\" onClick=\"document.location = '/'\" /><br>";
  if (rele) {
    ptr += "<br><input type=\"button\" value=\"Включить розетку\" onClick=\"document.location = '/rele_off'\" /><br>";
  }
  if (!rele) {
    ptr += "<br><input type=\"button\" value=\"Выключить розетку\" onClick=\"document.location = '/rele_on'\" /><br>";
  }
  ptr += "<h3>Датчики:</h3>\n";
  //ptr += "<h4>Датчики:</h4>\n";
  for (int i = 0 ; i < lng; i++) {
    ptr += "<h4>" + names[i] + " : " + data[i] + " " + znach[i] + "</h4>";
  }
  ptr += "<form action=\"/delete_page\">";
  ptr += "Название:  ";
  ptr += "<input type=\"text\" name=\"DELETE_NAME\" value=\"модуль\">   ";
  //ptr += "<br><br>";
  ptr += "<input type=\"submit\" value=\"удалить\">";
  ptr += "</form>";
  ptr += "<br><input type=\"button\" value=\"обновить\" onClick=\"document.location = '/control_menu'\" /><br>";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}

String SendControlReleAutoHTML() {
  String ptr = "<!DOCTYPE html><html lang='ru-RU'><head><meta charset='UTF-8'/> <html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>WIFI Control</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px; } h3 {color: #444444;margin-bottom: 50px;} h4 {color: #444444;margin-bottom: 20px;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<h1>Настройка автоматического управления розетки </h1>";
  if (auto_stat) {
    ptr += "<h3>Автоматическое управления включено</h3>";
  }
  else {
    ptr += "<h3>Автоматическое управления выключено</h3>";
  }
  ptr += "<h3>Текущее условие:" + auto_name + " ";
  switch (auto_oper) {
    case 0 : ptr += "> ";
      break;
    case 1 : ptr += "< ";
      break;
    case 2 : ptr += "= ";
      break;
  }
  if (auto_mode)
    ptr += String(auto_data) + " выключать </h3>";
  else
    ptr += String(auto_data) + " включать </h3>";
  ptr += "<h3>Выберете датчик и параметр для него:</h3>\n";
  ptr += "<form action=\"/select_auto\">";
  ptr += "<select size=\"1\" name=\"DAT_NAME\">";
  for (int i = 0 ; i < lng; i++) {
    ptr += "<option value=\"" + String(i) + "\">" + names[i] + "</option>";
  }
  ptr += "</select>  ";
  ptr += "<select size=\"1\" name=\"OPER\">";
  ptr += "<option value=0>></option>";
  ptr += "<option value=1><</option>";
  ptr += "<option value=2>=</option>";
  ptr += "</select>  ";
  ptr += "</select>  ";
  ptr += "<input type=\"text\" name=\"DATA_P\" value=\"10\">";
  ptr += "<select size=\"1\" name=\"ON_OFF\">";
  ptr += "<option value=0>Включать</option>";
  ptr += "<option value=1>Выключать</option>";
  ptr += "</select>  ";
  //ptr += "<br><br>";
  ptr += "<input type=\"submit\" value=\"настроить\">";
  ptr += "</form>";
  ptr += "<br><input type=\"button\" value=\"обновить\" onClick=\"document.location = '/rele_auto'\" /><br>";
  if (auto_stat) {
    ptr += "<br><input type=\"button\" value=\"выключить автопереключение\" onClick=\"document.location = '/auto_off'\" /><br>";
  }
  else {
    ptr += "<br><input type=\"button\" value=\"включить автопереключение\" onClick=\"document.location = '/auto_on'\" /><br>";
  }
  ptr += "<br><input type=\"button\" value=\"Вернуться в меню\" onClick=\"document.location = '/'\" /><br>";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}
Листинг программы для модуля - датчика DHT11
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>
#include <PubSubClient.h>
#include "DHT.h"

#define DHTTYPE DHT11
uint8_t DHTPin = 5; 
int tempC = 0;
int humC = 0;
int tmi = 10;
DHT dht(DHTPin, DHTTYPE); 

const char *ssid_ap = "Datchik_Setup";  //имя точки доступа модуля
const char *password_ap = "12345678";  //пароль точки доступа модуля
const char *ID = "temp_1";

String ssid = "";  //имя wifi
String password = "";  //пароль wifi
String device_id; // ID модуля
bool setup_mode ; // true - первичная настройка модуля, false - основная работа модуля

const char *mqtt_server = "M5.WQTT.RU"; // Имя сервера MQTT
const int mqtt_port = 2602; // Порт для подключения к серверу MQTT
const char *mqtt_user = "u_RSELYN"; // Логин от серверa
const char *mqtt_pass = "bhbtIJue"; // Пароль от сервера

ESP8266WebServer server(80);// server для настройки

WiFiClient wclient;
PubSubClient client(wclient, mqtt_server, mqtt_port);

IPAddress local_ip(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

void write_string_EEPROM (int Addr, String Str) {
  byte lng = Str.length();
  EEPROM.begin (256);
  EEPROM.write(Addr , lng);
  unsigned char* buf = new unsigned char[15];
  Str.getBytes(buf, lng + 1);
  Addr++;
  for (byte i = 0; i < lng; i++) {
    EEPROM.write(Addr + i, buf[i]);
    delay(10);
  }
  EEPROM.commit();
  EEPROM.end();
}

char *read_string_EEPROM (int Addr) {
  EEPROM.begin(256);
  byte lng = EEPROM.read(Addr);
  char* buf = new char[15];
  Addr++;
  for (byte i = 0; i < lng && i < 15; i++) buf[i] = char(EEPROM.read(i + Addr));
  buf[lng] = '\x0';
  EEPROM.end();
  return buf;
}

void setup() {
  pinMode(DHTPin, INPUT);
  Serial.begin(115200);
  EEPROM.begin(256);
  setup_mode = EEPROM.read(85);
  EEPROM.end();
  device_id = read_string_EEPROM(40);
  if (!setup_mode) {
    dht.begin();
    tmi = String(read_string_EEPROM(200)).toInt();
    local_ip.fromString(String(read_string_EEPROM(200)));
    gateway.fromString(String(read_string_EEPROM(220)));
    Serial.println(read_string_EEPROM(0));
    Serial.println(read_string_EEPROM(20));
    WiFi.begin(read_string_EEPROM(0), read_string_EEPROM(20));
    int sm = 0;
    while (WiFi.status() != WL_CONNECTED) {
      delay(500); sm++;
      Serial.print(".");
      if (sm > 120) {
        ReturnSetup();
      }
    }
    
    Serial.println("WiFi connected");
    tempC = (int)dht.readTemperature(); 
  humC = (int)dht.readHumidity();
    if (client.connect(MQTT::Connect(ID).set_auth(mqtt_user, mqtt_pass))) {
      Serial.println("Connected to MQTT server");
    } else {
      Serial.println("Could not connect to MQTT server");
    }
    client.publish("/datk", device_id+"_t" + "#" + String(tempC) + "#градусов#");
    client.publish("/datk", device_id+"_hm" + "#" + String(humC) + "#влажности#");
    delay(100);
    ESP.deepSleep(tmi * 1000000);
  }
  else {
    delay(1000);
    WiFi.softAP(ssid_ap, password_ap);
    WiFi.softAPConfig(local_ip, gateway, subnet);
    delay(100);
    server.on("/", handle_OnConnect);
    server.on("/end_setup", handle_EndSetup);
    server.on("/action_page", handleForm);
    server.onNotFound(handle_NotFound);
    server.begin();
    Serial.println("HTTP server started");
  }
}

void loop() {
  if (setup_mode)
    server.handleClient();
}

void handle_OnConnect() {
  server.send(200, "text/html", SendHTML());
}

void ReturnSetup() {
  EEPROM.begin(256);
  EEPROM.write(85, true);
  setup_mode = true;
  EEPROM.commit();
  EEPROM.end();
  ESP.reset();
}


void handleForm() {
  ssid = server.arg("WIFI_NAME");
  password = server.arg("WIFI_password");
  device_id =  server.arg("DEVICE_ID");
  tmi = server.arg("TIMING").toInt();
  Serial.print("WIFI: ");
  Serial.println(ssid);

  Serial.print("password: ");
  Serial.println(password);

  server.send(200, "text/html", SendEndHTML()); //Send web page
}

void handle_EndSetup() {
  write_string_EEPROM(0, ssid);
  write_string_EEPROM(20, password);
  write_string_EEPROM(40, device_id);
  write_string_EEPROM(200, String(tmi));
  EEPROM.begin(256);
  EEPROM.write(85, false);
  setup_mode = false;
  EEPROM.commit();
  EEPROM.end();
  WiFi.softAPdisconnect(true);
  ESP.reset();
}

void handle_NotFound() {
  server.send(404, "text/plain", "Not found");
}

String SendEndHTML() {
  String ptr = "<!DOCTYPE html><html lang='ru-RU'><head><meta charset='UTF-8'/><html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>WIFI Control</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<a href='/'><h1>Вернуться к настройки</h1></a>\n";
  ptr += "<a href='/end_setup'><h1>Продолжить</h1></a>\n";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}

String SendHTML() {
  String ptr = "<!DOCTYPE html><html lang='ru-RU'><head><meta charset='UTF-8'/> <html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>WIFI Control</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr += ".button {display: block;width: 80px;background-color: #1abc9c;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr += ".button-on {background-color: #1abc9c;}\n";
  ptr += ".button-on:active {background-color: #16a085;}\n";
  ptr += ".button-off {background-color: #34495e;}\n";
  ptr += ".button-off:active {background-color: #2c3e50;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<h1>Настройка WIFI</h1>\n";
  ptr += "<h3>Укажите название и пароль от нужной wifi сети</h3>\n";
  ptr += "<form action=\"/action_page\">";
  ptr += "Название:<br>";
  ptr += "<input type=\"text\" name=\"WIFI_NAME\" value=\"тест_название\">";
  ptr += "<br>";
  ptr += "Пароль:<br>";
  ptr += "<input type=\"text\" name=\"WIFI_password\" value=\"123456789\">";
  ptr += "<br>";
  ptr += "Тайминг отправки(в секундах):<br>";
  ptr += "<input type=\"text\" name=\"TIMING\" value=\"1\">";
  ptr += "<br>";
  ptr += "Название модуля:<br>";
  ptr += "<input type=\"text\" maxlength=\"10\" name=\"DEVICE_ID\" value=\"datc_temp\"><br>";
  ptr += "<br><br>";
  ptr += "<input type=\"submit\" value=\"Закончить\">";
  ptr += "</form>";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}
Листинг программы для модуля - датчика ds18b20
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>
#include <PubSubClient.h>
#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS 5
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature DS18B20(&oneWire);
char temperatureCString[7];
int tempC = 0;
int tmi = 10;

const char *ssid_ap = "Datchik_Setup";  //имя точки доступа модуля
const char *password_ap = "12345678";  //пароль точки доступа модуля
const char *ID = "temp_1";

String ssid = "";  //имя wifi
String password = "";  //пароль wifi
String device_id; // ID модуля
bool setup_mode ; // true - первичная настройка модуля, false - основная работа модуля

const char *mqtt_server = "M5.WQTT.RU"; // Имя сервера MQTT
const int mqtt_port = 2602; // Порт для подключения к серверу MQTT
const char *mqtt_user = "u_RSELYN"; // Логин от серверa
const char *mqtt_pass = "bhbtIJue"; // Пароль от сервера

ESP8266WebServer server(80);// server для настройки

WiFiClient wclient;
PubSubClient client(wclient, mqtt_server, mqtt_port);

IPAddress local_ip(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

void write_string_EEPROM (int Addr, String Str) {
  byte lng = Str.length();
  EEPROM.begin (256);
  EEPROM.write(Addr , lng);
  unsigned char* buf = new unsigned char[15];
  Str.getBytes(buf, lng + 1);
  Addr++;
  for (byte i = 0; i < lng; i++) {
    EEPROM.write(Addr + i, buf[i]);
    delay(10);
  }
  EEPROM.commit();
  EEPROM.end();
}

char *read_string_EEPROM (int Addr) {
  EEPROM.begin(256);
  byte lng = EEPROM.read(Addr);
  char* buf = new char[15];
  Addr++;
  for (byte i = 0; i < lng && i < 15; i++) buf[i] = char(EEPROM.read(i + Addr));
  buf[lng] = '\x0';
  EEPROM.end();
  return buf;
}

void getTemperature()
{
  do {
    DS18B20.requestTemperatures();
    tempC = DS18B20.getTempCByIndex(0);
    dtostrf(tempC, 2, 2, temperatureCString);
    delay(100);
  } while (tempC == 85.0 || tempC == (-127.0));
}

void setup() {
  Serial.begin(115200);
  EEPROM.begin(256);
  setup_mode = EEPROM.read(85);
  EEPROM.end();
  device_id = read_string_EEPROM(40);
  if (!setup_mode) {
    tmi = String(read_string_EEPROM(200)).toInt();
    local_ip.fromString(String(read_string_EEPROM(200)));
    gateway.fromString(String(read_string_EEPROM(220)));
    DS18B20.begin();
    Serial.println(read_string_EEPROM(0));
    Serial.println(read_string_EEPROM(20));
    WiFi.begin(read_string_EEPROM(0), read_string_EEPROM(20));
    int sm = 0;
    while (WiFi.status() != WL_CONNECTED) {
      delay(500); sm++;
      Serial.print(".");
      if (sm > 120) {
        ReturnSetup();
      }
    }
    getTemperature();
    Serial.println("WiFi connected");
    if (client.connect(MQTT::Connect(ID).set_auth(mqtt_user, mqtt_pass))) {
      Serial.println("Connected to MQTT server");
    } else {
      Serial.println("Could not connect to MQTT server");
    }
    client.publish("/datk", device_id + "#" + String(tempC) + "#градусов#");
    delay(100);
    ESP.deepSleep(tmi * 1000000);
  }
  else {
    delay(1000);
    WiFi.softAP(ssid_ap, password_ap);
    WiFi.softAPConfig(local_ip, gateway, subnet);
    delay(100);
    server.on("/", handle_OnConnect);
    server.on("/end_setup", handle_EndSetup);
    server.on("/action_page", handleForm);
    server.onNotFound(handle_NotFound);
    server.begin();
    Serial.println("HTTP server started");
  }
}

void loop() {
  if (setup_mode)
    server.handleClient();
}

void handle_OnConnect() {
  server.send(200, "text/html", SendHTML());
}

void ReturnSetup() {
  EEPROM.begin(256);
  EEPROM.write(85, true);
  setup_mode = true;
  EEPROM.commit();
  EEPROM.end();
  ESP.reset();
}


void handleForm() {
  ssid = server.arg("WIFI_NAME");
  password = server.arg("WIFI_password");
  device_id =  server.arg("DEVICE_ID");
  tmi = server.arg("TIMING").toInt();
  Serial.print("WIFI: ");
  Serial.println(ssid);

  Serial.print("password: ");
  Serial.println(password);

  server.send(200, "text/html", SendEndHTML()); //Send web page
}

void handle_EndSetup() {
  write_string_EEPROM(0, ssid);
  write_string_EEPROM(20, password);
  write_string_EEPROM(40, device_id);
  write_string_EEPROM(200, String(tmi));
  EEPROM.begin(256);
  EEPROM.write(85, false);
  setup_mode = false;
  EEPROM.commit();
  EEPROM.end();
  WiFi.softAPdisconnect(true);
  ESP.reset();
}

void handle_NotFound() {
  server.send(404, "text/plain", "Not found");
}

String SendEndHTML() {
  String ptr = "<!DOCTYPE html><html lang='ru-RU'><head><meta charset='UTF-8'/><html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>WIFI Control</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<a href='/'><h1>Вернуться к настройки</h1></a>\n";
  ptr += "<a href='/end_setup'><h1>Продолжить</h1></a>\n";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}

String SendHTML() {
  String ptr = "<!DOCTYPE html><html lang='ru-RU'><head><meta charset='UTF-8'/> <html>\n";
  ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
  ptr += "<title>WIFI Control</title>\n";
  ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
  ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}\n";
  ptr += ".button {display: block;width: 80px;background-color: #1abc9c;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
  ptr += ".button-on {background-color: #1abc9c;}\n";
  ptr += ".button-on:active {background-color: #16a085;}\n";
  ptr += ".button-off {background-color: #34495e;}\n";
  ptr += ".button-off:active {background-color: #2c3e50;}\n";
  ptr += "p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
  ptr += "</style>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<h1>Настройка WIFI</h1>\n";
  ptr += "<h3>Укажите название и пароль от нужной wifi сети</h3>\n";
  ptr += "<form action=\"/action_page\">";
  ptr += "Название:<br>";
  ptr += "<input type=\"text\" name=\"WIFI_NAME\" value=\"тест_название\">";
  ptr += "<br>";
  ptr += "Пароль:<br>";
  ptr += "<input type=\"text\" name=\"WIFI_password\" value=\"123456789\">";
  ptr += "<br>";
  ptr += "Тайминг отправки(в секундах):<br>";
  ptr += "<input type=\"text\" name=\"TIMING\" value=\"1\">";
  ptr += "<br>";
  ptr += "Название модуля:<br>";
  ptr += "<input type=\"text\" maxlength=\"14\" name=\"DEVICE_ID\" value=\"datc_temp\"><br>";
  ptr += "<br><br>";
  ptr += "<input type=\"submit\" value=\"Закончить\">";
  ptr += "</form>";
  ptr += "</body>\n";
  ptr += "</html>\n";
  return ptr;
}
Теги:
Хабы:
Всего голосов 21: ↑20 и ↓1+27
Комментарии35

Публикации

Истории

Ближайшие события