откатные ворота на ардуино НАНО

nik182
Offline
Зарегистрирован: 04.05.2015

Когда бензопилу к воротам пристраивать будешь пилящую цепь поменять на цепь от велосипеда попробуй. Безопасней будет.

Kakmyc
Offline
Зарегистрирован: 15.01.2018

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

Общие правила:
1. Концевых выключателей должно быть ДВА(один тоже можно, но два правильнее)
2.При непонятном положении ворот, при запуске, ворота должны открыватся
3. Должен быть датчик тока(что бы не спалить к херам двигатель)
4. Должно быть ограничение работы по таймеру(опять же что бы не спалить двигатель, когда механика умерла)
5.Датчик препятствия обязателен

примерная таблица состояний
1.ворота закрыты, в режиме ожидания
2.ворота открыты в режиме ожидания
3.ворота открываются
4.ворота закрываются
5.ворота в состоянии Z , в режиме ожидания

далее требуется описать переходы из одного состояния в другое,
согласно набору условий.
Примеры:
1.при состоянии 5 , при нажатии кнопки включения,
если нет помех на пути, состояние 5 меняется на состояние 3
2. из состояния 3 возможны переходы в состояния:
5-при нажатии кнопки стоп, появления препятствия или превышение тока
2-при полном открытии ворот

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

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

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

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Замечательно сформулировано!

Таблица состояний, правда, это не список состояний, а как раз таблица, в которой записаны условия переходов из одних состояний в другие, но, принимая во внимание целевую аудиторию, это и не важно.

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

Kakmyc пишет:
Я в принципе не понимаю, зачем человек который не в состоянии описать алгоритм работы того, что ему требуется, берется за это дело. Но мне не жалко: Общие правила:
1. Концевых выключателей должно быть ДВА(один тоже можно, но два правильнее)
2.При непонятном положении ворот, при запуске, ворота должны открыватся

...

У меня какое-то дежавю, я ж это уже где-то читал... о_0

zipper31
Offline
Зарегистрирован: 25.01.2016

Тема не моя,я тут паралельно.

Откатные ворота на базе шуруповёрта.Логика стандартная для такой автоматики.

Код не полный,в разработке.Не могу сообразить как реализовать остановку по средине(стоп) и обратное движение.

На макетке всё работает то что написано,в качестве двигателя 13 пин(светодиод).2 концевика постоянно разомкнутые.

// Номера пинов МКонтроллера:
#define led      13     // индикатор
#define button   2      // кнопка step-by-step
#define gerkonOP 3      // концевик открыто
#define gerkonCL 4      // концевик закрыто
//#define rev      5      // Реле реверса мотора
//#define motor    9      // мотор вкл
//#define lamp     6      // сигнальная лампа
//#define ir       7      // фото элементы безопасности

bool flag = false;
uint32_t btnTimer = 0;
byte pos = 1;            //позиция ворот.0-закрыты,1-открываются,
                         //              2-открыты,3-закрываются,

void setup()
{
  pinMode(led, OUTPUT);
  pinMode(button, INPUT_PULLUP);
  pinMode(gerkonCL, INPUT_PULLUP);
  pinMode(gerkonOP, INPUT_PULLUP);
  Serial.begin(9600);
}
void loop() {
  //кнопка или пульт
  bool btnState = !digitalRead(2);
  if (btnState && !flag && millis() - btnTimer > 100) {
    flag = true;
    btnTimer = millis();
    Serial.println("press");
  }
  if (!btnState && flag && millis() - btnTimer > 100) {
    flag = false;
    btnTimer = millis();
    //Serial.println("release");
  } //Закрыты,открываем...
  if (flag == true && digitalRead(gerkonCL) == !true) {
    digitalWrite(led, HIGH);
    pos = 1;
    Serial.println("open...");
  }  //Открылись,стоп
  if (pos == 1 && digitalRead(gerkonOP) == !true) {
    digitalWrite(led, LOW);
    pos = 2;
    Serial.println("opened");
  }  //открыты,закрываем...
  if (flag == true && digitalRead(gerkonOP) == !true) {
    digitalWrite(led, HIGH);
    pos = 3;
    Serial.println("close");
  }  //закрылись,стоп
  if (pos == 3 && digitalRead(gerkonCL) == !true) {
    digitalWrite(led, LOW);
    pos = 0;
    Serial.println("closed");
  }  //стоп при открытии
  /* if (flag == true && pos == 1) {
     digitalWrite(led, LOW);
     Serial.println("open stop");
    }  //стоп при закрытии
    if (flag == true && pos == 3) {
     digitalWrite(led, LOW);
     Serial.println("close stop");
    } */

}

 

Kakmyc
Offline
Зарегистрирован: 15.01.2018

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

zipper31
Offline
Зарегистрирован: 25.01.2016

Kakmyc пишет:
За такую логику работы ворот , нужно этот шруповерт в одно место засунуть , включить и не доставать, пока просветление не снизойдёт.

Ну и для чего надо было это писать в разделе для новичков?Тем более без аргументирования своей позиции.

 

 

Kakmyc
Offline
Зарегистрирован: 15.01.2018

zipper31 пишет:

Kakmyc пишет:
За такую логику работы ворот , нужно этот шруповерт в одно место засунуть , включить и не доставать, пока просветление не снизойдёт.

Ну и для чего надо было это писать в разделе для новичков?Тем более без аргументирования своей позиции.

 

 

Для того что бы новичок задумался над тем, что он делает не так.
Или ты чего ждал ?
Что тебе скажут:"твой код охуенен, пиши ещё" ?
Я ж тебе не мама, за все хвалить , даже за то, что сына молодец какой покакал и даже не на ковер.
В таком виде в работу запускать железяки нельзя и даже делится таким "рабочим кодом" не стоит.
Точнее можно, но лучше это делать в другом месте, скажем на форуме гивера. Там таких писателей больше чем людей.

Kakmyc
Offline
Зарегистрирован: 15.01.2018

Что то типа этого нужно писать, что бы было понятно и самому через пару лет и окружающим




enum STATE{Z_STATE,OPENED,CLOSED,OPENING,CLOSING,ALARM};

STATE currentState=Z_STATE;

void setup(){
    //initialization
}

void Z_state(){
    if(true/*нажатаКнопка&&нетПомехи&&нетПерегрузки*/)currentState=CLOSING;
}

void Closing(){
//флагНаВключениеДвигателяНазад=true;
    if(true/*нажатаКнопка*/)currentState=Z_STATE;
    if(true/*нажатКонцевикЗакрытияВорот*/)currentState=CLOSED;
    if(true/*перегрузкаДвигателя*/)currentState=ALARM;
    //остальные переходы опционально
}

void loop(){
    //опрос датчиков
    
    //в зависимости от состояния вызываем ту или иную функцию
    switch(currentState){
    case Z_STATE:
        Z_state();
    break;
    
    case OPENED:
    break;
    
    case CLOSED:
    break;
    
    case OPENING:
    break;
    
    case CLOSING:
    break;
    
    case ALARM:
    break;
        }
    
    //изменение выходных сигналов
    if(true/*флагНаВключениеДвигателяНазад*/){//включаем двигатель назад
    }else //выключаем двигатель назад
    ;
    
    if(true/*флагНаВключениеДвигателяВперед*/){//включаем двигатель вперед
   }  else //выключаем двигатель вперед
    ;
    
    //и тд.
    
    
}

 

zipper31
Offline
Зарегистрирован: 25.01.2016

поизучаю.

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

Kakmyc пишет:


Что то типа этого нужно писать, что бы было понятно и самому через пару лет и окружающим




enum STATE{Z_STATE,OPENED,CLOSED,OPENING,CLOSING,ALARM};

STATE currentState=Z_STATE;

void setup(){
    //initialization
}

void Z_state(){
    if(true/*нажатаКнопка&&нетПомехи&&нетПерегрузки*/)currentState=CLOSING;
}

void Closing(){
//флагНаВключениеДвигателяНазад=true;
    if(true/*нажатаКнопка*/)currentState=Z_STATE;
    if(true/*нажатКонцевикЗакрытияВорот*/)currentState=CLOSED;
    if(true/*перегрузкаДвигателя*/)currentState=ALARM;
    //остальные переходы опционально
}

void loop(){
    //опрос датчиков
    
    //в зависимости от состояния вызываем ту или иную функцию
    switch(currentState){
    case Z_STATE:
        Z_state();
    break;
    
    case OPENED:
    break;
    
    case CLOSED:
    break;
    
    case OPENING:
    break;
    
    case CLOSING:
    break;
    
    case ALARM:
    break;
        }
    
    //изменение выходных сигналов
    if(true/*флагНаВключениеДвигателяНазад*/){//включаем двигатель назад
    }else //выключаем двигатель назад
    ;
    
    if(true/*флагНаВключениеДвигателяВперед*/){//включаем двигатель вперед
   }  else //выключаем двигатель вперед
    ;
    
    //и тд.
    
    
}

 

Тоже о enum подумал сразу же как первоначальный код увидел))

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016
enum STATE {Z_STATE, OPENED, CLOSED, OPENING, CLOSING, ALARM};
STATE currentState;

void stand(STATE s);// функция перехода
void gate_run();// функция отслеживания состояний

void setup() {
  stand(Z_STATE);
}
void loop() {
  gate_run();
}
//----------------------------------------------------------------
void stand(STATE s) { // функция перехода
  currentState = s;
  switch (currentState) {
    case Z_STATE:
      break;
    case OPENED:
      /*выключить мотор*/
      break;
    case CLOSED:
      /*выключить мотор*/
      break;
    case OPENING:
      /*включить мотор на открытие*/
      break;
    case CLOSING:
      /*включить мотор на закрытие*/
      break;
    case ALARM:
      /*выключить мотор и включить сирену*/
      break;
  }
}
void gate_run() {

  switch (currentState) {
    case Z_STATE:
      if (true /*команда открыть*/)stand(OPENING);
      else if (true /*команда закрыть*/)stand(CLOSING);
      break;
    case OPENED:
      if (true /*команда закрыть*/)stand(CLOSING);
      break;
    case CLOSED:
      if (true /*команда открыть*/)stand(OPENING);
      break;
    case OPENING:
      if (true /*концевие открыто*/)stand(OPENED);
      else if (true /*команда закрыть*/)stand(CLOSING);
      else if (true /*тело в воротах*/)stand(ALARM);
      break;
    case CLOSING:
      if (true /*концевие закрыто*/)stand(CLOSED);
      else if (true /*команда открыть*/)stand(OPENING);
      else if (true /*тело в воротах*/)stand(ALARM);
      break;
    case ALARM:
      if (true /*тело в воротах нет*/) {
        if (true /*команда открыть*/)stand(OPENING);
        else if (true /*команда закрыть*/)stand(CLOSING);
      }
      break;
  }
}

 

BOOM
BOOM аватар
Offline
Зарегистрирован: 14.11.2018

qwone пишет:

enum STATE {Z_STATE, OPENED, CLOSED, OPENING, CLOSING, ALARM};
STATE currentState;

void stand(STATE s);// функция перехода
void gate_run();// функция отслеживания состояний

void setup() {
  stand(Z_STATE);
}
void loop() {
  gate_run();
}
//----------------------------------------------------------------
void stand(STATE s) { // функция перехода
  currentState = s;
  switch (currentState) {
    case Z_STATE:
      break;
    case OPENED:
      /*выключить мотор*/
      break;
    case CLOSED:
      /*выключить мотор*/
      break;
    case OPENING:
      /*включить мотор на открытие*/
      break;
    case CLOSING:
      /*включить мотор на закрытие*/
      break;
    case ALARM:
      /*выключить мотор и включить сирену*/
      break;
  }
}
void gate_run() {

  switch (currentState) {
    case Z_STATE:
      if (true /*команда открыть*/)stand(OPENING);
      else if (true /*команда закрыть*/)stand(CLOSING);
      break;
    case OPENED:
      if (true /*команда закрыть*/)stand(CLOSING);
      break;
    case CLOSED:
      if (true /*команда открыть*/)stand(OPENING);
      break;
    case OPENING:
      if (true /*концевие открыто*/)stand(OPENED);
      else if (true /*команда закрыть*/)stand(CLOSING);
      else if (true /*тело в воротах*/)stand(ALARM);
      break;
    case CLOSING:
      if (true /*концевие закрыто*/)stand(CLOSED);
      else if (true /*команда открыть*/)stand(OPENING);
      else if (true /*тело в воротах*/)stand(ALARM);
      break;
    case ALARM:
      if (true /*тело в воротах нет*/) {
        if (true /*команда открыть*/)stand(OPENING);
        else if (true /*команда закрыть*/)stand(CLOSING);
      }
      break;
  }
}

 

да ну нет, ну куда так?  

zipper31
Offline
Зарегистрирован: 25.01.2016

Вот кое что получилось.Основа кода к сожалению не моя так как я нахожусь на низшей ступени эволюции Ардуино.

Поменян код кнопки

Поменян код сигнальной лампы.

Добавлены фотоэлементы безопасности на закрытие(делают стоп).В идеале для них добавить авто реверс и таймер(чтоб двигатель не рвало моментальным реверсом).Легко,но мне это не нужно.

Концевики постоянно разомкнуты.Фотоэлементы постоянно разомкнуты.Легко можно обратить в постоянно замкнутые.

Код полностью работоспособен может кому пригодиться в жизни.

#define LED_FLASH       13    // сигнальная лампа
#define MOTOR_OPEN      8     // выход на драйвер управления мотором
#define MOTOR_CLOSE     7     // выход на драйвер управления мотором
#define BUTTON          2     // кнопка или сигнал с пульта
#define SWITCH_OPENED   12    // концевик полностью открытых ворот N.O
#define SWITCH_CLOSED   10    // концевик полностью закрытых ворот N.O
#define Photo           11    // фотоэлементы безопасности на закрытие N.O
#define DEBOUNCE        50    // задержка для антидребезга кнопки
enum {STOP, OPEN, CLOSE};     // возможные состояния системы
byte state;                   // текущее состояние системы
byte lastDir;                 // предыдущее направление открытия ворот
uint32_t ms, ms1 = 0;         // таймер сигнальной лампы
bool ledState = true;         // переменная сигнальной лампы
bool buttonWasUp = true;      // переменная кнопки


void checkSwitches(void);
void ledsManager(void);
void motor(void);
void readButton(void);

void setup()
{
  pinMode(LED_FLASH, OUTPUT);
  pinMode(MOTOR_OPEN, OUTPUT);
  pinMode(MOTOR_CLOSE, OUTPUT);
  pinMode(BUTTON, INPUT_PULLUP);
  pinMode(SWITCH_OPENED, INPUT_PULLUP);
  pinMode(SWITCH_CLOSED, INPUT_PULLUP);
  pinMode(Photo, INPUT_PULLUP);
  state = STOP;                       // исходное состояние после включения питания
  lastDir = CLOSE;
  Serial.begin(9600);
}

void loop()
{
  readButton();       // проверка кнопок
  checkSwitches();    // проверка концевиков, при коллизиях подавляет команду от кнопки
  ledsManager();      // сигнальная лампа
  motor();            // управление двигателем
}
//___________________________________________________________
// работа кнопки.
void readButton()
{
  boolean buttonIsUp = digitalRead(BUTTON);

  if (buttonWasUp && !buttonIsUp) {
    delay(100);
    buttonIsUp = digitalRead(BUTTON);
    if (!buttonIsUp) {
      Serial.println("press");

      if (state == STOP && lastDir == CLOSE) // даем команду на открытите
      {
        // lastDir = state;
        state = OPEN;
        Serial.println("push_open");
      }
      else if (state == STOP && lastDir == OPEN) // даем команду на закрытите
      {
        // lastDir = state;
        state = CLOSE;
        Serial.println("push_close");
      }
      else if (state == OPEN || state == CLOSE) // даем команду на остановку
      {
        lastDir = state;
        state = STOP;
      }
    }
  }
  buttonWasUp = buttonIsUp;
}
//___________________________________________________________
// защита двигателя от неправельного движения и
// фотоэлементы безопасности на закрытие
void checkSwitches()
{
  if (!digitalRead(SWITCH_OPENED) && state == OPEN)         // отмена команды Открыть при полностью открытой двери
  {
    lastDir = state;
    state = STOP;
  }
  else if (!digitalRead(SWITCH_CLOSED) && state == CLOSE)   // отмена команды Закрыть при полностью закрытой двери
  {
    lastDir = state;
    state = STOP;
  }
  if (!digitalRead(Photo) && state == CLOSE)   // фотоэлементы сработали останавливаем
  {
    lastDir = state;
    state = STOP;
    Serial.println("object warning");
  }
}

//___________________________________________________________
void motor()// управление мотором
{
  switch (state)
  {
    case STOP:
      digitalWrite(MOTOR_OPEN, LOW);
      digitalWrite(MOTOR_CLOSE, LOW);
      Serial.println("stop");
      break;

    case OPEN:
      digitalWrite(MOTOR_OPEN, HIGH);
      digitalWrite(MOTOR_CLOSE, LOW);
      Serial.println("open");
      break;

    case CLOSE:
      digitalWrite(MOTOR_OPEN, LOW);
      digitalWrite(MOTOR_CLOSE, HIGH);
      Serial.println("close");
      break;
  }
  delay(DEBOUNCE); // простейший антидребезг кнопки
}

//___________________________________________________________
//сигнальная лампа.
void ledsManager()
{
  if (!state == STOP)
  {
    ms = millis();
    if ( ( ms - ms1 ) > 500 || ms < ms1 ) {   // включаем каждые 500 мс
      ms1 = ms;
      digitalWrite(LED_FLASH, ledState);      // инвертируем сигнал,выключаем
      ledState = !ledState;
    }
  }
  if (state == STOP)       //защита от залипания во вкл состоянии
  {
    digitalWrite(LED_FLASH, LOW);
  }
}

 

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

zipper31 пишет:

Добавлены фотоэлементы безопасности на закрытие(делают стоп).В идеале для них добавить авто реверс и таймер(чтоб двигатель не рвало моментальным реверсом).Легко,но мне это не нужно.

В идеале туда-бы добавить проверку тока на двигателях, чтобы их не сжечь.

Fenicksnet
Offline
Зарегистрирован: 25.03.2019

zipper31 пишет:

Концевики постоянно разомкнуты.Фотоэлементы постоянно разомкнуты.Легко можно обратить в постоянно замкнутые.

тоже попробую донести свою мысль.

Элементы безопасности , в нормальном состоянии должны быть в замкнутом состоянии, дабы исключить обрыв линии и неисправность фотоэлементов. Это я не касательно конкретного этого скетча, а в принципе. может я не заменил, но добавил бы проверку элементов безопасности перед закрытием. строки 60++. возможно что-то типа такого.

      else if (state == STOP && lastDir == OPEN) // даем команду на закрытите
      {
        if (digitalRead(Photo)) {
          state = CLOSE;
          Serial.println("push_close");
        }
      }

ну и ограничить время работы двигателей. типа для защиты от неисправности концевиков или застревание.

zipper31
Offline
Зарегистрирован: 25.01.2016

На самом деле это не нужно.Все заводские фотоэлементы безопасности в рабочем состоянии замкнуты.

То есть без питания они разомкнутся и ворота не поедут.Вся автоматика так работает.

Данный код так написан для удобства макетирования и кто хоть немного понимает с лёгкостью уберёт ! знак и всё заработает должным образом.

zipper31
Offline
Зарегистрирован: 25.01.2016

mykaida пишет:

zipper31 пишет:

В идеале туда-бы добавить проверку тока на двигателях, чтобы их не сжечь.

У меня откатные,там один двигатель.

Так как это самоделка то двигатели разные.У меня будет шуруповёрт с собственной защитой от перегрузки.

Может быть 12,24,220v мотор.Тепловое реле можно поставить.Тут всё сугубо индивидуально.

У меня просто 2 контактных реле на 12v с алика.

Есть ещё мысль управлять оборотами двигателя мосфетом стандартной кнопки шуруповёрта.Замедлять к примеру перед концевыми положениями ворот.

P.S:Посмотрим,на улице тает всё сырость брр.Подсохнет во дворе,начну эксперименты на проф пригодность шуруповёрта и всё остальное.Готовый результат выложу.

кутузов
Offline
Зарегистрирован: 22.06.2016

Привет всем! Наткнулся на эту тему пару недель назад, когда озадачился установкой мотора на свои ворота. Не я первый, не я последний. Для тех кто, кто так же наткнется на эту тему в поисках готового решения, выкладываю свой код. Использована дешевенькая плата с WiFi на борту - ESP8266  NodeMcu V3 .  Датчики открытого и закрытого положения ворот применены A3144. По подключению: D1 - открыть, D2 - закрыть, D5,D6- вход для датчиков крайнего положения. Управление воротами с любого устройства через WiFi (телефон, планшет, и т.п.) после подключения в браузере вводим адрес : 192.168.4.1 и радуемся. Интернет не нужен, вайфай  подключается напрямую к плате. Может код далек от идеала (кривой косой, все в кучу и т.д.), взят за основу пример мигания светодиодами и переделан под управление воротами, но главное - все работает, бери и пользуйся! Для ярых сторонников безопасности можете световой барьер подключить вместе с датчиком закрытого положения, а перед открытием и закрытием можно бибикать три раза.  

/*********
  Подключение по умолчанию 192.168.4.1
  Добавлены концевые выключатели,
  Сделана программная блокировка одновременного включения выходов.
  Добавлено изменение цвета кнопки в зависимости от состояния.
*********/
 
// Загрузить библиотеку Wi-Fi
#include <ESP8266WiFi.h>
 
// Замените вашими сетевыми учетными данными
const char* ssid     = "Nazvanie";
const char* password = "Parol";
 
// Установите номер порта веб-сервера равным 80
WiFiServer server(80);
 
//  Переменная для хранения HTTP-запроса
String header;
 
// Вспомогательные переменные для сохранения текущего выходного состояния
String outputD1State = "off";
String outputD2State = "off";
 
// Назначьте выходные переменные контактам GPIO, GPIO 4-D1. GPIO 5-D2
const int outputD1 = 4; // Выход D1 открыть
const int outputD2 = 5; // Выход D2 закрыть
const int inputD5 = D5; // Вход D5  датчик открытого положения А3144 - китайский датчик холла
const int inputD6 = D6; // Вход D6  датчик закрытого положения А3144

//Переменные для хранения состояния концевиков
bool openEnd = 0;
bool closeEnd = 0;

void setup() {
        //  Serial.begin(115200);
  // Инициализация выходных переменных как outputs
  pinMode(outputD1, OUTPUT);
  pinMode(outputD2, OUTPUT);
  pinMode(inputD5, INPUT);     // Вход датчика крайнего ОТКРЫТОГО положения, низкий уровень при срабатывании
  pinMode(inputD6, INPUT);     // Вход датчика крайнего ЗАКРЫТОГО положения, низкий уровень при срабатывании
  // Установите выходы на низкий уровень
  digitalWrite(outputD1, LOW);
  digitalWrite(outputD2, LOW);
 
  // Подключитесь к сети Wi-Fi с помощью SSID и пароля
  //        Serial.print("Setting AP (Access Point)…");
  // Удалите параметр пароля, если вы хотите, чтобы точка доступа была открыта
  WiFi.softAP(ssid, password);
 
  IPAddress IP = WiFi.softAPIP();
        //Serial.print("AP IP address: ");
        //Serial.println(IP);
  
  server.begin();
}
 
void loop(){

openEnd = digitalRead(inputD5); // Читаем остояние на входе
closeEnd = digitalRead(inputD6); // Читаем остояние на входе
// Отключение при достижении крайних положений
if (openEnd==0) {
   outputD1State = "off";
  digitalWrite(outputD1, LOW);
            }
if (closeEnd==0) {
  outputD2State = "off";
  digitalWrite(outputD2, LOW);
            }
  WiFiClient client = server.available();   // Слушаем входящих клиентов
 
  if (client) {                             // Если подключается новый клиент,
    //Serial.println("New Client.");          // вывести сообщение в последовательный порт
    String currentLine = "";                // создаем строку для хранения входящих данных от клиента
    while (client.connected()) {                   // цикл, пока клиент подключен

      openEnd = digitalRead(inputD5); // Читаем остояние на входе от датчика открытого положения
      closeEnd = digitalRead(inputD6); // Читаем остояние на входе от датчика закрытого положения
      if (openEnd==0) {
               outputD1State = "off";
               digitalWrite(outputD1, LOW);
            }
      if (closeEnd==0) {
              outputD2State = "off";
              digitalWrite(outputD2, LOW);
            }

      if (client.available()) {             // если есть байты для чтения от клиента,
        char c = client.read();             // прочитать байт, затем
       // Serial.write(c);                    // вывести его на серийный монитор
        header += c;
        if (c == '\n') {                    // если байт является символом новой строки
          // если текущая строка пуста, вы получили два символа новой строки подряд.
          // это конец клиентского HTTP-запроса, поэтому отправьте ответ:
          if (currentLine.length() == 0) {
            // Заголовки HTTP всегда начинаются с кода ответа (например, HTTP/1.1 200 OK)
            // и тип контента, чтобы клиент знал, что придет, затем пустая строка:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();
            
            
            // включает и выключает GPIO
            if (header.indexOf("GET /26/on") >= 0 & openEnd==1 & outputD2State == "off") {
              //Serial.println("GPIO 26 on");
              outputD1State = "on";
              digitalWrite(outputD1  , HIGH);
            } else if (header.indexOf("GET /26/off") >= 0) {
              //Serial.println("GPIO 26 off");
              outputD1State = "off";
              digitalWrite(outputD1, LOW);
            } else if (header.indexOf("GET /27/on") >= 0 & closeEnd==1 & outputD1State == "off") {
              //Serial.println("GPIO 27 on");
              outputD2State = "on";
              digitalWrite(outputD2, HIGH);
            } else if (header.indexOf("GET /27/off") >= 0) {
              //Serial.println("GPIO 27 off");
              outputD2State = "off";
              digitalWrite(outputD2, LOW);
            }
            

            // Отображение веб-страницы в формате HTML
            client.println("<!DOCTYPE html><html>"); 
            //   <meta charset=\"utf-8\">     Добавляет кирилицу     
            client.println("<head> <meta charset=\"utf-8\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"); 
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // CSS для оформления кнопок включения/выключения 
            //Не стесняйтесь изменять атрибуты background-color и font-size в соответствии с вашими предпочтениями
            client.println("<style>html { font-family: utf-8; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { background-color: #4CAF50; border: none; color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button1 {background-color: #555555;}"); // Состояние крайнего положение
            client.println(".button2 {background-color: #8b0000;}</style></head>");  //color: #555555 - Серый цвет, #8b0000 - Красный
            
            // Заголовок веб-страницы
            client.println("<body><h1>управление воротами</h1>");
            
            // Отображение текущего состояния и кнопки ВКЛ/ВЫКЛ для GPIO 26 
            client.println("<p>GPIO D1 - State " + outputD1State + "</p>");
            
           // Если outputDState выключен, отображается кнопка ON            
            if (outputD1State=="off" & openEnd ==1 & outputD2State == "off") {
              client.println("<p><a href=\"/26/on\"><button class=button >открыть</button></a></p>" ); 
              }
              else if (outputD1State=="off" & openEnd == 0 || outputD2State == "on") {
              client.println("<p><a href=\"/26/on\"><button class=\"button button1\" >открыть</button></a></p>" ); 
              }
             else {
              client.println("<p><a href=\"/26/off\"><button class=\"button button2\"> &nbsp; &nbsp; стоп &nbsp; &nbsp;  </button></a></p>");
                    } 
               
            // Отображение текущего состояния и кнопки ВКЛ/ВЫКЛ для GPIO 27
            client.println("<p>GPIO D2 - State " + outputD2State + "</p>");
            // Если outputD2State выключен, отображается кнопка ON    
            if (outputD2State=="off" & closeEnd ==1 & outputD1State == "off") {
              client.println("<p><a href=\"/27/on\"><button class=\"button\">закрыть</button></a></p>");
            }
            else if (outputD2State=="off" & closeEnd == 0 || outputD1State == "on") {
              client.println("<p><a href=\"/27/on\"><button class=\"button button1\">закрыть</button></a></p>");
            }
             else {
              client.println("<p><a href=\"/27/off\"><button class=\"button button2\"> &nbsp; &nbsp; стоп &nbsp; &nbsp; </button></a></p>");
            }
            client.println("</body></html>");
            
            // Ответ HTTP заканчивается еще одной пустой строкой
            client.println();
            // Выход из цикла while
            break;
          } else { // если вы получили новую строку, то очищаем currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // если вы получили что-то еще, кроме символа возврата каретки,
          currentLine += c;      // добавляем его в конец currentLine
        }
      }
    }
    // Очистить переменную заголовка
    header = "";
    // Закрыть соединение
    client.stop();
    //Serial.println("Client disconnected.");
    //Serial.println("");
  }
}