Уровень воды

Гриша
Offline
Зарегистрирован: 27.04.2014

есть такая портия - Rosemount  cерии 3100 - партия датчиков уровня :))))))))) если правильно помню, примено 1к2 баксов за штуку... не нравится этот, можно у сименсов поискать - но они немного хуже...

dmitron1036
Offline
Зарегистрирован: 10.01.2016

подпишусь.

А может даже сделаю себе на основе датчика давления.

dmitry63
dmitry63 аватар
Offline
Зарегистрирован: 09.03.2019

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

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

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

Работает все это под управлением arduino uno + w5100 + датчики Y25 T12V + клапана EBOWAN. Все заказано на aliexpress.

Последняя версия всегда на гитхабе: https://github.com/yashindmitry/ReservationWaterFromWell

#include <SPI.h>
#include <Ethernet.h>
byte mac[] = {
  0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD // MAC адрес веб сервера
};
IPAddress ip(192, 168, 0, 15); // IP адрес веб сервера
EthernetServer server(80); // Порт веб сервера
const int RelayWell = 5; // Реле управление соленоидным клапаном на колодце
const int RelayBarrelOut = 6; // Реле управление соленоидным клапаном забора воды из бочки
const int RelayBarrelIn = 7; // Реле управление соленоидным клапаном пополнения бочки
boolean RelayWellIsOpen = true; // Признак того, что клапан колодца открыт
boolean RelayBarrelIsOpen = false; // Признак того, что клапан бочки открыт
boolean AutomaticControl = true; // Включено автоматическое управление клапанами
boolean ReserveAutomaticControl = true; // Включено автоматическое наполнение резервной бочки из колодца
boolean RelayBarrelInIsOpen = false; // Признак того, что клапан наполнения бочки открыт

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(RelayWell, OUTPUT);
  pinMode(RelayBarrelOut, OUTPUT);
  pinMode(RelayBarrelIn, OUTPUT);
  Ethernet.begin(mac, ip);
  server.begin();
}

unsigned long lastCheckMillis = 0; // Время последней проверки уровня воды в колодце
unsigned long checkInterval = 1000; // Через сколько осуществить следующую проверку урвня воды в колодце
unsigned long ReserveLastCheckMillis = 0; // Время последней проверки необходимости пополнения резервной емкости
unsigned long checkIntervalReserve = 1000; // Через сколько осуществить следующую проверку необходимости пополнения резервной емкости

void loop() {

  unsigned long currentMillis = millis();

  // Проверим не пришло ли время проверять уровень воды в колодце
  long differenceValues = currentMillis - lastCheckMillis;
  if((differenceValues > checkInterval) || (differenceValues < 0)) {
    lastCheckMillis = currentMillis;
    int IsHighWaterLevel = digitalRead(A0); // Читаем данные с датчика уровня воды
    digitalWrite(LED_BUILTIN, IsHighWaterLevel); // Выставляем диод
    if (IsHighWaterLevel == HIGH) {
      if (AutomaticControl) {
        // Открываем кран колодца
        digitalWrite(RelayWell, LOW);
        RelayWellIsOpen = true;
        // Закрываем кран из бочки
        digitalWrite(RelayBarrelOut, LOW);
        RelayBarrelIsOpen = false;
      }
      checkInterval = 60000;
    } else {
      if (AutomaticControl) {
        // Открываем кран из бочки
        digitalWrite(RelayBarrelOut, HIGH);
        RelayBarrelIsOpen = true;
        // Закрываем кран колодца
        digitalWrite(RelayWell, HIGH);
        RelayWellIsOpen = false;
      }
      checkInterval = 600000;
    }
  }

  // Автоматическое поддержание уровня воды резервной емкости
  long differenceValues2 = currentMillis - ReserveLastCheckMillis;
  if((differenceValues2 > checkIntervalReserve) || (differenceValues2 < 0)) {
    ReserveLastCheckMillis = currentMillis;
    if (ReserveAutomaticControl) {
      int IsHighWaterLevel = digitalRead(A0); // Читаем данные с датчика уровня воды
      int IsHighWaterLevelInBarrel = digitalRead(A1); // Читаем данные с датчика уровня воды в бочке
      if (IsHighWaterLevelInBarrel == LOW && IsHighWaterLevel == HIGH) {
        // Открываем подачу воды из колодца
        digitalWrite(RelayBarrelIn, HIGH);
        RelayBarrelInIsOpen = true;
        checkIntervalReserve = 1000;
      } else {
        // Закрываем подачу воды из колодца
        digitalWrite(RelayBarrelIn, LOW);
        RelayBarrelInIsOpen = false;
        checkIntervalReserve = 600000; // Следующую проверку уровня воды в резервной емкости проведем через 10 минут
      }
    } else {
      // Закрываем подачу воды из колодца
      digitalWrite(RelayBarrelIn, LOW);
      RelayBarrelInIsOpen = false;
    }
  }

  EthernetClient client = server.available();
  if (client) {
    boolean currentLineIsBlank = true;
    String header = "";
    String requestUrl = "";
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        if (c == '\n' && currentLineIsBlank) {
          if (requestUrl == "") {
            client.println("HTTP/1.1 200 OK");
            client.println("Content-Type: text/html; charset=utf-8");
            client.println("Connection: close");
            client.println();
            client.println("<!DOCTYPE HTML>");
            client.println("<html>");
            int IsHighWaterLevel = digitalRead(A0); // Читаем данные с датчика уровня воды
            if (IsHighWaterLevel == HIGH) {
              client.println("<p>Уровень воды в колодце <b>высокий</b></p>");
            } else {
              client.println("<p>Уровень воды в колодце <b>низкий</b></p>");
            }
            client.println("<hr>");
            if (AutomaticControl) {
              client.println("<p>Автоматическое управление клапанами <b>on</b> <i><a href=\"/acoff\">off</a></i></p>");
            } else {
              client.println("<p>Автоматическое управление клапанами <b>off</b> <i><a href=\"/acon\">on</a></i></p>");
            }
            if (RelayWellIsOpen) {
              client.println("<p>Клапан колодца <b>on</b> <i><a href=\"/rwoff\">off</a></i></p>");
            } else {
              client.println("<p>Клапан колодца <b>off</b> <i><a href=\"/rwon\">on</a></i></p>");
            }
            if (RelayBarrelIsOpen) {
              client.println("<p>Клапан бочки <b>on</b> <i><a href=\"/rboff\">off</a></i></p>");
            } else {
              client.println("<p>Клапан бочки <b>off</b> <i><a href=\"/rbon\">on</a></i></p>");
            }
            client.println("<hr>");
            if (ReserveAutomaticControl) {
              client.println("<p>Автоматика резервной емкости <b>on</b> <i><a href=\"/aroff\">off</a></i></p>");
            } else {
              client.println("<p>Автоматика резервной емкости <b>off</b> <i><a href=\"/aron\">on</a></i></p>");
            }
            if (RelayBarrelInIsOpen) {
              client.println("<p>Клапан наполнения бочки <b>on</b> <i><a href=\"/rbioff\">off</a></i></p>");
            } else {
              client.println("<p>Клапан наполнения бочки <b>off</b> <i><a href=\"/rbion\">on</a></i></p>");
            }
            client.println("</html>");
          } else {
            client.println("HTTP/1.1 307 temporary redirect");
            client.println("Location: /");
            client.println("Connection: close");
            if (requestUrl == "acoff") {
              AutomaticControl = false;
            } else if (requestUrl == "acon") {
              AutomaticControl = true;
            } else if (requestUrl == "rwoff") {
              // Закрываем кран колодца
              digitalWrite(RelayWell, HIGH);
              RelayWellIsOpen = false;
            } else if (requestUrl == "rwon") {
              // Открываем кран колодца
              digitalWrite(RelayWell, LOW);
              RelayWellIsOpen = true;
            } else if (requestUrl == "rboff") {
              // Закрываем кран бочки
              digitalWrite(RelayBarrelOut, LOW);
              RelayBarrelIsOpen = false;
            } else if (requestUrl == "rbon") {
              // Открываем кран бочки
              digitalWrite(RelayBarrelOut, HIGH);
              RelayBarrelIsOpen = true;
            } else if (requestUrl == "aroff") {
              ReserveAutomaticControl = false;
            } else if (requestUrl == "aron") {
              ReserveAutomaticControl = true;
            } else if (requestUrl == "rbioff") {
              // Закрываем кран наполнения бочки
              digitalWrite(RelayBarrelIn, LOW);
              RelayBarrelInIsOpen = false;
            } else if (requestUrl == "rbion") {
              // Открываем кран rbioff бочки
              digitalWrite(RelayBarrelIn, HIGH);
              RelayBarrelInIsOpen = true;
            }
          }
          break;
        }
        if (c == '\n') {
          if (header.substring(0, 4) == "GET ") {
            requestUrl = header.substring(5, header.indexOf(" ", 4));
          }
          header = "";
          currentLineIsBlank = true;
        } else if (c != '\r') {
          header = header + c;
          currentLineIsBlank = false;
        }
      }
    }
    delay(1);
    client.stop();
  }

}

Это мой первый проект на этой платформе, прошу советов, критики. Возможно подскажете как оптимизировать расход памяти, так как на данный момент: Глобальные переменные используют 1602 байт (78%) динамической памяти, оставляя 446 байт для локальных переменных. Максимум: 2048 байт.

dmitry63
dmitry63 аватар
Offline
Зарегистрирован: 09.03.2019

Скрин админки

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

Поробуйте все прямые строки в кавычках в операторах print и println взять в макрос F(), типа client.println("HTTP/1.1 200 OK"); -> client.println(F("HTTP/1.1 200 OK"));.

 

dmitry63
dmitry63 аватар
Offline
Зарегистрирован: 09.03.2019

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

Спасибо большое, очень помогло! Глобальные переменные используют 324 байт (15%) динамической памяти, оставляя 1724 байт для локальных переменных. Максимум: 2048 байт.

Подскажите, насколько корректно использовать этот макрос повсеместно? Например, так правильно,

if (requestUrl == F("acoff")) {
     AutomaticControl = false;
}

или не совсем?

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

Интересный вопрос. Я б не использовал в данном месте. Идея макроса и прямом чтении из PROGMEM. Здесь посимвольное сравнение и как компилятор этим озаботится я не знаю. Скорее всего сделает копию в памяти и будет крутить сравнение там.  

sadman41
Offline
Зарегистрирован: 19.10.2016

Можно, думаю, выкрутится через strcmp_p и свойство String-а c_str(), но лучше - избавиться от String.

dmitry63
dmitry63 аватар
Offline
Зарегистрирован: 09.03.2019

nik182, интересно, пока тогда не буду в этих местах использовать этот макрос. Как нибудь разберусь в этом вопросе. А прямое чтение из PROGMEM грозит только потерей производительности или еще чем то? Может быть уменьшением ресурса флеш памяти, или количество чтений из нее так велико, что можно этим пренебречь?

sadman41, извините, не понял как можно избавиться от String. Можете, пожалуйста, дать пример кода?

dmitry63
dmitry63 аватар
Offline
Зарегистрирован: 09.03.2019

Может быть использовать числовые url (http://192.168.0.15/1 http://192.168.0.15/2 http://192.168.0.15/1002...), тогда можно будет до сравнения сделать 

requestIndex = requestUrl.toInt()

И оперировать уже числами?

SLKH
Offline
Зарегистрирован: 17.08.2015

dmitry63 пишет:

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

Спасибо большое, очень помогло! Глобальные переменные используют 324 байт (15%) динамической памяти, оставляя 1724 байт для локальных переменных. Максимум: 2048 байт.

Подскажите, насколько корректно использовать этот макрос повсеместно? Например, так правильно,

if (requestUrl == F("acoff")) {
     AutomaticControl = false;
}

или не совсем?

а не лѣпо ли ны бяшетъ, братіе, обмениваться по линиям связи двухбайтовыми (например) кодами информации/команд, а на приемной стороне, если надо, подставлять при выводе текстовые собщения с желаемой раскраской?

dmitry63
dmitry63 аватар
Offline
Зарегистрирован: 09.03.2019

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

https://github.com/yashindmitry/ReservationWaterFromWell/commit/5b76fa5faa4f1a3a3f8bfdddea514eab60ee64b9

dmitry63
dmitry63 аватар
Offline
Зарегистрирован: 09.03.2019

SLKH, Я саму ардуинку использую в качестве конечного веб сервера. То есть все интерфейсы (по крайней мере в данном проекте) будет прорисовывать она.

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

Программа выполняется из PROGMEM. Ресурс чтения для наших условий бесконечный. Скорость для редких вызовов не является самым важным параметром. URL в int врядли получится, ul нужен, но с портами что делать? 

dmitry63
dmitry63 аватар
Offline
Зарегистрирован: 09.03.2019

Может кому то будет интересно. Провел тест производительности веб сервера ab -c 2 -n 100  http://192.168.0.15/:

1. Вариант, когда строки-аргументы client.println не обернуты F()

Time taken for tests:   2.995 seconds
Requests per second:    33.39 [#/sec] (mean)
Transfer rate:          21.81 [Kbytes/sec] received

2. Вариант, когда строки-аргументы client.println обернуты F()

Time taken for tests:   18.052 seconds
Requests per second:    5.54 [#/sec] (mean)
Transfer rate:          3.62 [Kbytes/sec] received
 
Сервер стал медленнее в 6 раз.