#1 2020-01-09 17:04:10

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Система управление резервом воды и котлом отопления в квартире.

Добрый день!
    Решил в квартире сделать систему управления резервом воды и газовым котлом как элемент умного дома.

    Вот, что из этого получилось:
    Два резервных бака с водой суммарным объемом 400 л. расположены в треугольной закрытой нише 1х1 м высотой 1 м.
    К выходу баков подключены  гидрофор с гидроаккумулятором на 80 л.
    В случае отсутствия необходимого напора воды в водопроводе на входе квартиры включается гидрофор, который поддерживает давление во внутренней системе водопровода.
82c706c53e1d636ecb59fed65ec60064.jpg
                                    Рис. 1. упрощенная схема водопровода


    Поступающая вода через обратный клапан 1 и управляемый мотором кран наполняет баки. На входе баков установлен автоматический запорный клапан по уровню (на схеме не показан). Для дополнительной подстраховки в верхней части бака имеется аварийный перелив в канализацию.
    Раз в неделю, по воскресеньям, происходит автоматическое обновление воды в баках. При этом кран перекрывает воду наполняющую баки и включается гидрофор. Это позволяет расходовать воду из баков на протяжении дня. В конце дня гидрофор отключается, кран открывается и баки наполняются свежей водой.
    Так как давление в водопроводе не превышает 1,5 бар, а гидрофор создает давление от 2,2 до 4,5 бар, при установленных обратных клапанах нет необходимости в перекрывании воды и установке дополнительных кранов.

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

    Первая версия устройства была выполнена на устройствах жесткой логики — реле (см. последние три фото). Уровень воды в баках измерялся  WP-T804. Согласно выставленных уровней происходило срабатывание реле гидрофора, открытие и закрытие  крана наполнения баков. Два независимых датчика показывали текущий расход воды и ее температуру. Управление  вентилятором, озонатором и осушителем было выполнено на реле Sonoff (модели Basic и TH16).


    Знакомство с Arduino состоялось три месяца назад. За это время удалось реализовать данный проект от идеи до готового изделия.
   
    Устройство управления собрано на NodeMCU ESP8266, которое по сети WiFi подключено к домашней сети и имеет выход в интернет.  На входе баков установлен расходомер, измерители температуры воды и давления. Аналогичные измерительные датчики установлены на выходе гидрофора.
    Учет показаний расхода воды выполнен на основе обработки прерываний вызванных импульсами поступающими от расходомеров, которые поступают на ножки D5 и D6 NodeMCU.
    В датчиках температуры воды используются термосопротивления 50 кОм, напряжение на которых измеряется 16 разрядным АЦП ADS1115 (шина I2C). Дополнительно в каждом цикле опроса датчиков измеряется напряжения питания 5 В, которое участвует как опорное при расчете.
    В качестве датчиков температуры и влажности воздуха применены AM2320 работающие по шине I2C. В связи с тем, что в системе имеется два одинаковых датчика, с адресом 0x5C, который не может быть изменен. Поэтому используется мультиплексор шины I2C TCA9548A, который подключен к ножкам D1 и D2 NodeMCU с уровнем 3,3 В. К мультиплексору также подключены два четырех канальных АЦП ADS1115 (0x48), расширитель MCP2317 (0x20), измеритель тока INA226 (0x40) и FRAM (0х50-0х57).
    Измерение уровня воды в баках проводится с помощью датчика уровня дифференциального давления (гидростатический датчик уровня). Выходной сигнал датчика  токовая петля 4-20 мА соответствует уровню воды 0-5 м.  Для возможности точного измерения тока на выходе датчика применен модуль INA226, в котором стандартный шунт 0,01 Ом был увеличен в 10000 раз. Питания датчика уровня осуществляется преобразователем DC-DC MT3608 5 в 24 Вольта. Для управления гидрофора используется твердотельное реле на 100 А. Оказалось, что управляющего сигнала 5 В  было недостаточно для коммутации напряжения 220 В. Поэтому управляющее напряжение было повышено до 24 В через MOSFET IRL540NPBF.
    Резервный источник питания выполнен на одном аккумуляторе 18650, контроллере заряда литиевого аккумулятора TP4056 и повышающем преобразователе MT3608.
Упрощенная структурная схема измерителя показана на рис.2.
22814ccd027701a65ba9f27d9152fe43.jpg

Датчики температуры DS18В20 OneWire контролируют температуру на твердотельном реле включения гидрофора, контроллере заряда литиевого аккумулятора резервного источника питания и элементе Пельтье в осушителе. Предотвращая перегрев реле гидрофора и контроллера и соответственно образования льда на радиаторе элемента Пельтье.
    Датчик протечки следит за протечкой воды и в случае срабатывания отключает гидрофор, перекрывает кран наполнения баков и отправляет  E-mail с предупреждением пользователю.
    Для хранения данных программы используется FRAM FM24CL16, которая подключена по шине I2C. Преимуществом использования данного типа памяти, является высокая скорость работы и практически неограниченное число циклов чтения/записи.
    Положение крана перекрытия воды наполнения баков отслеживается с помощью аналогового датчика Холла и кругового магнита с диаметральной намагниченностью.
    При превышении уровня влажности в нише выше заданного включается осушитель выполненный на элементе Пельтье. Конденсат собранный осушителем накапливается в резервуаре 0,7 л, при наполнении которого мембранный насос по таймеру выкачивает жидкость в канализацию.
    Периодически два раза в день, в соответствии с установленными таймерами, включается вентиляция воздуха в нише. Утром в будние дни срабатывает таймер озонирования, вырабатываемый озон дополнительно очищает пространство в нише. После озонирование происходит вентиляция.
    В качестве интерфейса взаимодействия с пользователем использовано приложение Virtuino. С целью сокращения объема передаваемой информации обновление данных происходит только на выбранной  странице управления. Планшет Samsung Tab 10.1 2014 Android 5.1.1 SM-P601 использован в качестве панели управления.
    Примеры страниц панелей управления представлены на рисунках ниже.
b7488af5239a2a1c8bbcb32cac01477e.png
112887212ecddba0b1744e1f5c0176e5.jpg
e3d1f5fb840ec847742cb6b90c174b51.jpg
f045c3844eda1721cc293fa2d3561526.jpg
40518effc64c6989e9b3133b48f0f825.jpg

Для обновления программного обеспечения используется режим по воздуху через HTTP Update Server.
    Считывание данных выполняется каждую секунду. Контроль наличия связи с планшетом осуществляется каждые 5 секунд.
    В первоначальных версиях программы данные коэффициентов хранились во внутренней EEPROM ESP8266. В библиотеке INA226 также был задействована этот вид памяти. С целью избежания конфликтов использование EEPROM в библиотеке INA226 было исключено для ESP8266.
    Применение в качестве внешней памяти для хранения данных AT24C256 приводило к сбоям программы. Это было вызвано тем, что суммарные данные показаний расходомеров записывались в AT24C256 по срабатыванию таймера на основе библиотеки Ticker. Стандартная библиотека AT24C256 имела задержку на 20 мс при записи. Для предотвращения сбоев в работе устройства - запись в память можно было проводить не по прерыванию, а в цикле loop. Позже память AT24C256 была заменена на FM24CL16.
    Для предотвращения ложного срабатывания реле управления исполнительными устройствами (гидрофор, вентилятор, озонатор, кран) введена первоначальная задержка на 60 секунд при старте.
    В качестве библиотеки по отправке E-mail была выбрана Gsender, как имеющая наименьшую задержку.
    Оперативную коррекцию данных датчиков можно, при необходимости, осуществить через установку нужных коэффициентов в приложении, значения которых будут перезаписаны в памяти.
    В программе используются стандартные библиотеки:
ESP8266WiFi.h
ESP8266WebServer.h
ESP8266HTTPUpdateServer.h
VirtuinoCM.h
Adafruit_AM2320.h
Adafruit_MCP23017.h
Adafruit_ADS1015.h
OneWire.h
Wire.h
Ticker.h
EEPROM.h
    При компилировании программы в среде Arduino IDE сделаны дополнительные следующие установки: Flash size 4M(3M SPIFFS) и IwIP Variant v1.4 Higher Bandwidth. При этом обеспечивается стабильная работа устройства.
    Цикл чтения данных с датчиков DS18B20 (измерение около 750 мс) начинается со считывания, а затем отсылается команда запуска измерения для всех датчиков одновременно. Это позволяет получать данные со всех датчиков практически без задержки с периодичностью 1 сек.
    В связи с тем, что применен мультиплексор I2C TCA9548A при обращении к модулям и датчикам, обязательным является предварительная установка нужного порта мультиплексора.
    Библиотека FM24CL16 переписана и встроена в проект. Память FM24C16 имеет 8 страниц (адреса 0x50-0x57), в каждой по 256 байт. В библиотеке убрано разбиение на страницы и все пространство памяти доступно сплошной адресацией, а функции read и write позволяет сохранять любые данные и структуры.
    Также встроены в проект библиотеки по работе с расходомерами и термосопротивлениями.
    Для дистанционного включения гидрофора, например, из ванной - используется радио-выключатель. Для этого в блоке установлен обучаемый приемник RX480E-4 433 МГц EV1527.
    Для защиты от сбоев по питанию смонтирован ИБП на чипе TP4056 (контроллер заряда), DW01 (схема защиты) и ML8205A (сдвоенный ключ MOSFET), повышающем  преобразователе на MT3608 и аккумуляторе 18650. Информация об уровне заряда передается в приложение.
    Фото устройства приведено ниже. На последних трех фотографиях - блок управления первой версии.

    Датчики температуры DS18В20 OneWire контролируют температуру на твердотельном реле включения гидрофора, контроллере заряда литиевого аккумулятора резервного источника питания и элементе Пельтье в осушителе. Предотвращая перегрев реле гидрофора и контроллера и соответственно образования льда на радиаторе элемента Пельтье.
    Датчик протечки следит за протечкой воды и в случае срабатывания отключает гидрофор, перекрывает кран наполнения баков и отправляет  E-mail с предупреждением пользователю.
    Для хранения данных программы используется FRAM FM24CL16, которая подключена по шине I2C. Преимуществом использования данного типа памяти, является высокая скорость работы и практически неограниченное число циклов чтения/записи.
    Положение крана перекрытия воды наполнения баков отслеживается с помощью аналогового датчика Холла и кругового магнита с диаметральной намагниченностью.

29/12/2019 Дополнено управлением газового котла.
Упрощенная структурная схема измерителя показана на рис.3.
ce8cf9ebd70a2d6117212111432f5d43.jpg

    В случае понижения температуры в комнате ниже установленной, которая измеряется датчиком температуры в комнате DS18B20 (контакт D5 - шина OneWire) происходит срабатывание реле (контакт D6), которое переводит котел в режим отопления. Датчик температуры в комнате подключен по двум проводам и запитывается через диод Шотке и конденсатор 10 мкФ.
268489a19ed98cbfcdf799bc8621b519.jpg
Датчик наружной температуры подключен по трех проводной схеме.
    Энергометр PZEM-004T производит измерение показателей сети и параметров потребления котлом. Энергометр подключен по UART D7 Rx D8 Tx работающий в режиме программного серийного порта. Для возможности программирования и загрузки выход D8 включен через PNP транзистор.
0667698cd3388a4e7f8a5980eb0ba166.jpg
    Дополнительно показания температуры выводятся на LCD1602 (рис. 4), который подключен к процессору по шине I2C (D1 - I2C SDA, D2 - SCL) через преобразователь уровней.
045c8a832fa1826846aefc5597f46423.jpg
С помощью кнопок + и — можно задать требуемую температуру в комнате. Кнопки подключены к резистивной матрице, выход которой подключено к A0.

    В случае использования аппаратного порта UART для обмена с энергометром вместо программного - для вывода информации можно использовать D4 выход Serial1 подключаемый через отдельный переходник USB-TTL к компьютеру. Для согласования применен преобразователь логических уровней.

    Во внутреннем EEPROM сохраняется значение установленной температуры, коррекция датчика температуры в комнате, гистерезис управления котлом по датчику температуры и коррекция датчика температуры на лице.

Обмен с сервером Virtuino происходит по каналу WiFi.
e03267b7acc28f2c5e7e73cab5c03fd7.png
80b13873ca4fa3394bb874b6a02a176a.jpg
9acba17505ab713571e1b512df517add.jpg
a79ae79432667f506aadc19859016831.jpg
50be4e5edd31d75746c649908d676603.jpg
022fac7d41db2002859fb6f6a197e77d.jpg
948f18343fbf247adf34e35c30459718.jpg
2cbdcbbad9575c194dcb0050a1d69592.jpg
48e07dabbefab4a4387ce8f43b0d2c57.jpg
ea746bc4f103f4e1bb9f7b267c0ab06f.jpg

Остання редакція NickVectra (2020-01-09 19:38:14)

Неактивний

#2 2020-01-09 17:16:28

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Система управление резервом воды и котлом отопления в квартире.

Внешний вид устройства управления газовым котлом представлен на фото ниже
28a7c737a75eb9fa24dfb4c9db22f6a9.jpg
45016717769dbcdb549aa3132c52d1b2.jpg

Неактивний

#3 2020-01-09 17:25:32

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Система управление резервом воды и котлом отопления в квартире.

Код основного проекта

/* Virtuino NodeMCU ESP8266   Created by Ilias Lamprou
   Contact address for questions or comments: iliaslampr@gmail.com
*/
#include <ESP8266WiFi.h>
const char* ssid;
const char* password;

#include <ESP8266WebServer.h>
#include <ESP8266HTTPUpdateServer.h>
ESP8266WebServer HttpServer(8080);      // Порт для входа, стандартный 80 занят для virtuino
ESP8266HTTPUpdateServer httpUpdater;

WiFiServer server(80);                  // Server port virtuino 80

#include <VirtuinoCM.h>

VirtuinoCM virtuino;

#define V_memory_count 85          // the size of V memory. You can change it to a number <=255)
float V[V_memory_count];           // This array is synchronized with Virtuino V memory. You can change the type to int, long etc.
bool debug = false;              // set this variable to false on the finale code to decrease the request time.

//Опеределенее ячеек памяти на сервере virtuino
#include "virtuino_pins.h"

#define FirstWait 60 //первоначальная задержка на 60 секунд перед обработкой всех датчиков при старте

#include "Adafruit_Sensor.h" // Общее по датчикам

#include "AM2320.h" // Датчик температуры и влажности
AM2320 am2320;  //адрес 0х5С - не меняется поэтому используется мультиплексор I2C

#include "Adafruit_MCP23017.h" //Расширитель цифровых портов
Adafruit_MCP23017 mcp; //расширитель портов MCP23017 16 портовый адрес 0х20

#include "TCA9548.h"  //мультиплексор I2C

#include "ADS1115.h"        // АЦП 4-х канальное

#include "ROM.h" //запись во внешнюю или внутреннюю память

#include "INA226_N.h" //Zanshin INA Library из библиотеки убрана работа с EEPROM
INA_Class INA; //< INA class instantiation
#define INA_Voltage_Conv_Time 588 //время конвертиции напряжения
#define INA_Current_Conv_Time 2116 //время конвертиции тока
#define INA_Averaging 256 //усредненее

#include "Flow_meter.h"

#define flow_on_input D5 //DV05 расходомер на входе баков ножки разъема №4 1-земля 2-выход 3-+5В D5
#define flow_on_output D6 //DV06 расходомер на входе баков ножки разъема №6 1-земля 2-выход 3-+5В D6

//в функцию прерывания нельзы передавать параметры
void ICACHE_RAM_ATTR pulseCounter_input(); //объявление фенкции обработки прерывания
void ICACHE_RAM_ATTR pulseCounter_output(); //объявление фенкции обработки прерывания

Flow_meter flow_sensor1; // создаем объект класса
Flow_meter flow_sensor2; // создаем объект класса

#include "thermistor.h"

// Thermistor object
THERMISTOR therm_input  (50000,  // Nominal resistance at 25 ºC
                         3950,   // thermistor's beta coeff
                         34440); // Value of the series resistor

THERMISTOR therm_output (50000,  // Nominal resistance at 25 ºC
                         3950,   // thermistor's beta coeff
                         34440); // Value of the series resistor

#include "coefficient.h" //корректировочные коэффициенты для датчиков


#include <OneWire.h>
#define ONE_WIRE_BUS D7 // Data wire is plugged into port D4 on the Arduino

OneWire  ds(ONE_WIRE_BUS); //Датчик температуры DS18B20
byte addr[3][8] = {{0x28, 0x32, 0xB2, 0xFC, 0x08, 0x00, 0x00, 0x17}, //internal pump relay
  {0x28, 0x18, 0x42, 0xFD, 0x08, 0x00, 0x00, 0x47}, //internal
  {0x28, 0x28, 0xC7, 0x78, 0x0B, 0x00, 0x00, 0x0F}  //external drum
};
  
#include "Gsender.h" //отправка Gmail
char Message[50]; //Массив для отправки сообщений по E-mail
Gsender *gsender = Gsender::Instance();    // Getting pointer to class instance

#define use_Ticker //использовать Ticker или Scheduler

#ifdef use_Ticker
#include <Ticker.h> //esp8266 library that calls functions periodically
Ticker FlowTicker; //используется для вызова функции расчета потока расходомера каждую секунду
Ticker FlowTotalTicker; //используется для записи общего количества литров расходомера, например, каждый час - 3600 сек
#else
#include <TickerScheduler.h>
TickerScheduler ts(3);
#endif

  unsigned long m = millis(); //перемення для хранения значения millis()
  unsigned long ozone_time = m; //для отслеживания времени работы озонатора
  unsigned long pump_time = m; //для отслеживания времени работы гидрофора
  unsigned long refresh_time = m; //для отслеживания времени работы обновления воды в баке
  unsigned long vent_time = m; //для отслеживания времени работы вентилятора    
  unsigned long drain_time = m; //для отслеживания времени работы осушителя    
  unsigned long drain_pump_time = m; //для отслеживания времени работы помпы осушителя    

//общее время опроса сервера Virtuino выставлено в программе
  unsigned long storedTime = m; //для ослеживания задержки чтение данных датчиков
  unsigned long switchTime = m; //для ослеживания задержки чтение выключателей
  unsigned long CommunicationTime = m; //для анализа состояния связи
    
  uint8_t flag_radio_switch_pump = OFF; //предыдущее значение радиопереключателя

  uint8_t flag_send_Email = 0; //флаг отправки E-mail


//================================================================= setup
void setup()
{
  virtuino.begin(onReceived, onRequested, 600); //Start Virtuino.
  //Установить размер буфера 1280. Если выводить данные на все панели сразу то размер буфера не манее 1138 байт
  //Для отображения float с 6 знаками после запятой необходимо до 20 байт
  //Для T(text) русские символы занимают два байта плюс служебные символы. Например, длина коэффициента №19 - 244 байта.
  //Установил запрос на данные только для текущей панели - размер данных уменьшился до 475 символов - панель "Установки"
  //для остальных

  virtuino.key = "1234";                        //This is the Virtuino password. Only requests the start with this key are accepted from the library

  Serial.begin(115200);
  delay(10);

  //----- NodeMCU module settings
  //LED_BUILTIN
  pinMode(D4, OUTPUT); //Использовать D4 или 2
  digitalWrite(D4, 1); //просто отключить светодиод на плате

  WiFi_Connect(); //найти и подсоединиться к сети WiFi

  server.begin(); // ---- Start the server
  Serial.println(F("Server started"));

  Wire.begin(SDA, SCL); //подготовили шину I2C

  //попытаться найти внешнюю память использовать внутреннюю память 0 - нет 1 да
  TCA_select(Mux_Ext_EEPROM); //память подключена к мальтиплексору
  if (FRAM.begin())
  {
    //проверить подключена ли внешнняя память
    V[V_Internal_External] = false;
    Serial.println(F("Найдена внешння память по адресу 0x50 шины I2C"));
  }
  else
  {
    EEPROM.begin(512);//Установить размер внутренней памяти для хранения первоначальных значений
    V[V_Internal_External] = true;
    Serial.println(F("Не найдена память по адресу 0x50 шины I2C - используется внутрення EEPROM"));
  }
 
  ads.begin();      //включаем АЦП

  mcp.begin();      // Используем стандартный адресс 0x20 для расширителя портов
  mcp_start();      //Настройка портов расширителя портов на вход-выход

  INA_start();      //Установка начальных значений для датчика тока и напряжения возвращает false если не найден датчик

  mcp_switch(V_vent, vent_mcp, OFF); //установить вентилятор в положение выключено
  mcp_switch(V_ozone, ozone_mcp, OFF); //установить озонатор в положение выключено
  mcp_switch(V_drain, drain_mcp, OFF); //установить осушитель в положение выключено
  mcp_switch(V_pump, pump_mcp, OFF); //установить гидрофор в положение выключено
  mcp_switch(V_tap, tap_mcp, OFF); //установить кран в положение закрыто

  V[V_Alarm_leak_Count] = 0; //счетчик количества секунд протечки

  fill_coeff(); //установка первоначальных коэффициентов из памяти

  flow_sensor1.begin(flow_on_input, &coeff[cor_calibrationFactor1].coeff, pulseCounter_input);
  flow_sensor2.begin(flow_on_output, &coeff[cor_calibrationFactor2].coeff, pulseCounter_output);

#ifdef use_Ticker
  //вызывать пересчет данных расходомеров каждую секунду
  FlowTicker.attach(1, tick_flow, false); //вызывать пересчет данных расходомеров каждую секунду
#else
  //вызывать пересчет данных расходомеров каждую секунду
  ts.add(0, 1000,  [&](void *) {
    tick_flow(false);
  }, nullptr, false);
  //для проверки связи с планшетом вызывается каждые 5 секунд
  ts.add(1, 5000,  [&](void *) {
    tick_communication(true);
  }, nullptr, false);
#endif

  if (!V[V_Internal_External])
  {
    //записать общее количество литров расходомеров каждые 10 минуту
#ifdef use_Ticker
    FlowTotalTicker.attach(600, tick_Totalflow);
#else
    ts.add(2, 600000,  [&](void *) {
      tick_Totalflow();
    }, nullptr, false);
#endif

    TCA_select(Mux_Ext_EEPROM);
    flow_sensor1.writeTotalLitres(Mem_float_read(Totalflow_addr));
    flow_sensor2.writeTotalLitres(Mem_float_read(Totalflow_addr + 4));
    V[V_total_liter_input] = Mem_float_read(Totalflow_addr); //общее количество литров датчика 1
    V[V_total_liter_output] = Mem_float_read(Totalflow_addr + 4); //общее количество литров датчика 2
  }

  Serial.print(F("Всего занято EEPROM: "));
  Serial.println(coeff_address + (sizeof(coeff) / sizeof(coeff[0])) * 4);

  //объем памяти https://www.esp8266.com/viewtopic.php?p=69937
  V[V_FreeHeap] = ESP.getFreeHeap();

  //причина презагрузки
  //https://esp8266.ru/forum/threads/otvety-esp-getresetreason.4145/
  T1 = ESP.getResetReason();

  //для анализа количества запусков программы
  V[V_Total_run] = Mem_float_read(Total_run_addr) + 1; //считать значения из памяти EEPROM и увеличить его на 1

  Mem_float_write(Total_run_addr, V[V_Total_run]); //записать значения в памяти EEPROM
  if (V[V_Internal_External]) //если внутрення память
    EEPROM.commit(); //подтвердить запись в память

//  sprintf(Message, "Runs = %.0f ", V[V_Total_run]);
  sprintf(Message, "Runs = %.0f %s ",V[V_Total_run], ESP.getResetReason().c_str());
  if (gsender->Subject("Home NodeMCU")->Send("testnick55@gmail.com", Message +ESP.getResetInfo()))
//  if (gsender->Subject("Home NodeMCU")->Send("testnick55@gmail.com", Message + ESP.getResetReason()+" "+ESP.getResetInfo()))
  {
    Serial.print(F("Message send."));
    Serial.println(Message);
  }
  else
  {
    Serial.print(F("Error sending message: "));
    Serial.print(Message);
    Serial.print(" ");
    Serial.println(gsender->getError());
  }

  V[V_RESTART] = OFF; //сбросить клавишу рестарта

  V[V_drain_time] = -3*60*60;     //сброс времени работы осушителя в секундах
  V[V_vent_time] = -3*60*60;      //сброс времени работы вентилятора в секундах
  V[V_ozone_time] = -3*60*60;     //сброс времени работы озонатора в секундах
  V[V_pump_time] = -3*60*60;      //сброс времени работы гидрофора в секундах
  V[V_refresh_time] = -3*60*60;   //сброс времени работы таймера обновления воды в баках в секундах
  V[V_drain_pump_time] = -3*60*60;//сброс времени работы насос осушителя в секундах

  V[V_lastCommTime] = 0; //сброс времени последней связи с планшетом 

  //  httpUpdater.setup(&HttpServer, OTAPATH, OTAUSER, OTAPASSWORD);
  httpUpdater.setup(&HttpServer, "admin", "admin");
  HttpServer.onNotFound(handleNotFound);
  HttpServer.begin();
  Serial.print(F("Start server IP adress:"));
  Serial.print(WiFi.localIP());
  //  Serial.println(OTAPATH);
  Serial.println(F("/update"));
}
//================================================================= setup

/* Выводить надпись, если такой страницы ненайдено */
void handleNotFound() {
  HttpServer.send(404, "text/plain", "404: Not found");
}


//unsigned long millis() сбрасывается каждые 50 дней.
//должно быть if ((unsigned long)(millis() - switchTime) > 1000)
// https://www.norwegiancreations.com/2018/10/arduino-tutorial-avoiding-the-overflow-issue-when-using-millis-and-micros/

 
//================================================================= loop
void loop()
{
  HttpServer.handleClient();       // Прослушивание HTTP-запросов от клиентов
  
  virtuinoRun();        // Necessary function to communicate with Virtuino. Client handler

#ifndef use_Ticker
  ts.update();
#endif

  if ((unsigned long)(millis() - storedTime) > 1000)
  {
    // Чтение данных датчиков

    dallRead(); //чтение данных с датчика темперауры реле и Пельтье имеет свой счетчик времени


    //считываем напряжение питания для дальнейших расчетов как опорное
    V[V_5_supply]  = Read_ADC(V_5_supply_ADC, false); // АЦП 2 канал 0 опорное напряженее 5В для датчиков NTC

    //давленее воды на входе
    float p = pressure_calc(Read_ADC(pressure_input_ADC, false), INPUT); // АЦП 1 канал 0 давление воды на входе
    V[V_pressure_input] = p;

    //сравнение с пороговым - если меньше увеличить счетчик
    if ((p < coeff[pressure_input_limit].coeff)//низкое давленее
        && (V[V_lastCommTime] > FirstWait) //старт программы первые 60 секунд
        && (lastCommTime != 0)) //именно старт а не переполненее счетчика времени
    {
      float c =  V[V_pressure_input_count]; //считать накопленное значенее секунд низкого давления воды

      //после сброса показывает показывает предыдущее значенее
      V[V_pressure_input_count] = ++c; //записать значенее счетчика увеличенное на 1
      if (c > coeff[pressure_input_wait].coeff) //сравнить с порогом ожидания
      {
        V[V_pressure_input_to_low] = ON; //включить индикатор
      }
    }
    else
    {
      V[V_pressure_input_to_low] = OFF; //сбросить флаг
      V[V_pressure_input_count] = 0; //сбросить счетчик
    }
    
    //давленее воды на выходе гидрофора
    p = pressure_calc(Read_ADC(pressure_output_ADC, false), OUTPUT);  // АЦП 1 канал 2 давление воды на входе
    V[V_pressure_output] =  p;

    V[V_tap_voltage] = Read_ADC(Holl_sensor_ADC, false); // АЦП 2 канал 2 Датчик Холла
    V[V_Holl_sensor] = mapf(V[V_tap_voltage], coeff[min_V_Holl_sensor].coeff, coeff[max_V_Holl_sensor].coeff, 90, 0);

    V[V_battery] =  Read_ADC(V_battery_ADC, false); // АЦП 2 канал 1 батарея

    //обработка датчика протечки
    V[V_Alarm_leak_Analog] = Read_ADC(Alarm_ADC, false); // АЦП 2 канал 3 напряженее с выхода датчика протечки

    if ((V[V_Alarm_leak_Analog] < coeff[leak_limit].coeff)          //напряжение от датчика протечки ниже заданного - большее напряжение сухо
        && (V[V_lastCommTime] > FirstWait) //старт программы первые 60 секунд
        && (lastCommTime != 0)) //именно старт а не переполненее счетчика времени
    {
      /*
        Serial.print("Ниже порога ");
        Serial.print(V[V_Alarm_leak_Count]);
        Serial.print("\t");
        Serial.print(V[V_Alarm_leak_Analog]);
        Serial.print("\t");
        Serial.println(coeff[leak_limit].coeff);
      */
      V[V_Alarm_leak_Count]++; //увеличим счетчик ожидания перед срабатыванием датчика протечки
      if (V[V_Alarm_leak_Count] > 20) //сравнить с порогом ожидания 20 секунд - защита от ложных срабатываний
      {
        //действия связанные с  протечкой
        mcp_switch(V_drain, drain_mcp, OFF); // выключить осушитель
        mcp_switch(V_pump, pump_mcp, OFF); //выключить гидрофор
        mcp_switch(V_tap, tap_mcp, OFF); //закрыть кран

        if (flag_send_Email < 3) //E-mail отсылаем 3 раза
        {
          if (gsender->Subject("Attension leak!!!")->Send("testnick55@gmail.com", "Attension leak!!!"))
          {
            Serial.println(F("Message send. Attension leak!!!"));
            flag_send_Email++;
          }
          else
          {
            Serial.print(F("Error sending message: Attension leak!!! "));
            Serial.println(gsender->getError());
          }
        }
      }
    }
    else
    {
      /*
        Serial.print("Выше порога сброс счетиков");
        Serial.print(V[V_Alarm_leak_Count]);
        Serial.print("\t");
        Serial.print(V[V_Alarm_leak_Analog]);
        Serial.print("\t");
        Serial.println(coeff[leak_limit].coeff);
      */
      V[V_Alarm_leak_Count] = 0; //сбросить счетчик
      flag_send_Email = 0;
    }

    //вывод информации датчиков температуры воды
    V[V_temp_water_input] = therm_input.read(
                              Read_ADC(temp_water_input_ADC, false), V[V_5_supply]) + coeff[cor_temp_water_input].coeff; // АЦП 1 канал 1 температура воды на входе
    V[V_temp_water_output] = therm_output.read(
                               Read_ADC(temp_water_output_ADC, false), V[V_5_supply]) + coeff[cor_temp_water_output].coeff; // АЦП 1 канал 3 температура воды на входе

    //вывод информации с датчиков температуры и влажности воздуха возле баков и в комнате
    readAM2320(Mux_AM2320_2, V_temp_room,  V_hum_room,  coeff[cor_temp_tanks].coeff, coeff[cor_hum_tanks].coeff, false);
    readAM2320(Mux_AM2320_1, V_temp_tanks, V_hum_tanks, coeff[cor_temp_room].coeff,  coeff[cor_hum_room].coeff, false);

    SetHumidityLimits(); //Установка пределов срабатывания по датчику влажности у баков

    Read_Water_level(false); //измерить уровень воды в баках

    V[V_drain_pump] = !mcp_switch_input(drain_pump_mcp); //состяние насоса осушителя 0- включен 1-выключен

    storedTime = millis();
  }

  if ((unsigned long)(millis() - switchTime) > 1000)
  {
    //чтение данных переключателей и установка уровней

    if (V[V_RESTART]) ESP.restart(); //если нажата клавиша перезагрузки в приложении - перегрузить. Пароль 1234

    V[V_FreeHeap] = ESP.getFreeHeap(); //объем памяти https://www.esp8266.com/viewtopic.php?p=69937
    Serial.println(V[V_FreeHeap], 0);

    check_timer(); //устанавливаем реле по таймерам

    Set_water_level(); //проверка уровня бака по выставленным уровням

    //обработка данных счетчиков расходомеров
    if (V[V_reset_total_L_1])
    {
      flow_sensor1.resetTotalLitres();  //сбросить счетчик 
      V[V_reset_total_L_1] = OFF; //сбросить переключатель
    }

    if (V[V_reset_total_L_2])
    {
      flow_sensor2.resetTotalLitres(); //сбросить счетчик
      V[V_reset_total_L_2] = OFF; //сбросить переключатель
    }

    V[V_flow_rate_input]  = flow_sensor1.readflowRate();   // расходомер л/мин
    V[V_flow_rate_output] = flow_sensor2.readflowRate();   // расходомер л/мин

    V[V_total_liter_input]  = flow_sensor1.readTotalLitres(); //общее количество литров
    V[V_total_liter_output] = flow_sensor2.readTotalLitres(); //общее количество литров

    if (V[V_unlock_settings])
    {
      //проверка на ввод пароля
    }

    change_coeff(); //проверка на измененее коэффициентов

    switchTime = millis();
  }

  
#ifdef use_Ticker
  if ((unsigned long)(millis() - CommunicationTime) > 5000)
  {
    //При пересоединении с сетью WiFi_Connect() используется delay
    //поэтому нужно вызывать проверку tick_communication через millis
    tick_communication(true);

    CommunicationTime = millis();
  }
#endif

}
//================================================================= loop


//================================================================= mapf
//функция возвращает пересчитанное значенее в формате float
float mapf(float val, float in_min, float in_max, float out_min, float out_max) {
  return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
//================================================================= mapf


//================================================================= pressure_calc
float pressure_calc(float voltage, bool input_output)
//расчет давления воды по выходному напряжению датчика
//voltage - напряжение с датчика
//input_output - вход баков - 0 выход гидрофора - 1
{
  float V_test;// 5.05 - напряжение питания при котором проводилось снятие показаний

  if (input_output) //выход
    V_test = coeff[cor_pressure_output].coeff;
  else //вход
    V_test = coeff[cor_pressure_input].coeff;

  float coeff_V_5 = V[V_5_supply] / V_test; //расчет коэффициента смещения из-за ухода напряжения 5 В
  float pressure = (2.85236941168935 * voltage - 1.51412252875401) * coeff_V_5; //формула апроксимации из Excel
  /*
    Serial.print("\n\t\t V_test = ");
    Serial.print(V_test,3);
    Serial.print("\t coeff_V_5 = ");
    Serial.print(coeff_V_5,3);
    Serial.print("\t pressure = ");
    Serial.println(pressure,3);
  */
  return pressure;
}
//================================================================= pressure_calc


//================================================================= dallRead
//считывание температуры c DS18B20
//http://arduino.ru/forum/programmirovanie/pochistil-sketch-primera-raboty-s-ds18b20-iz-bibly-oneware?page=1#comment-183588
//в начале считываем данные с датчиков, а зетем даем команду на получение новых данных
void dallRead()
{
//датчик 1  - реле гидрофора
  ds.reset();
  ds.select(addr[0]);
  ds.write(0xBE); //Считывание значения с датчика реле гидрофора
  int temp = (ds.read() | ds.read() << 8); //Принимаем два байта температуры
  V[V_relay_temp] = temp / 16.0 + coeff[cor_temp_relay].coeff;
  if (temp / 16.0 + coeff[cor_temp_relay].coeff > coeff[Relay_max_temp].coeff)
  {
    V[V_Relay_to_high] = ON;
    mcp_switch(V_pump, pump_mcp, OFF); //выключить гидрофор
  }
  else
  {
    V[V_Relay_to_high] = OFF;
  }

//датчик 2 - преобразователь ИБП
  ds.reset();
  ds.select(addr[1]);
  ds.write(0xBE); //Считывание значения с датчика преобразователя напряжения
  temp = (ds.read() | ds.read() << 8); //Принимаем два байта температуры
  V[V_temp_Converter] = temp / 16.0 + coeff[cor_temp_Converter].coeff;

  if (temp / 16.0 + coeff[cor_temp_Converter].coeff > coeff[Converter_max_temp].coeff)
  {
    V[V_Converter_to_high] = ON;
 //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++   
    //действия при срабатывании датчика температуры преобразователя
  }
  else
  {
    V[V_Converter_to_high] = OFF;
  }

//датчик 3 - элемент Пельтье
  ds.reset();
  ds.select(addr[2]);
  ds.write(0xBE); //Считывание значения с датчика элемент Пельтье
  temp = (ds.read() | ds.read() << 8); //Принимаем два байта температуры
  V[V_temp_Peltier] = temp / 16.0 + coeff[cor_temp_Peltier].coeff;
  if (temp / 16.0 + coeff[cor_temp_Peltier].coeff < coeff[Peltier_min_temp].coeff)
  {
    V[V_Peltier_to_low] = ON;
    mcp_switch(V_drain, drain_mcp, OFF); // выключить осушитель
  }
  else
  {
    V[V_Peltier_to_low] = OFF;
  }

  ds.reset();
  ds.write(0xCC); //Обращение ко всем датчикам
  ds.write(0x44); //Команда на конвертацию
}
//================================================================= dallRead


//================================================================= INA_start
bool INA_start()
{
  TCA_select(Mux_INA); //порт измерителя тока - уровень воды в баке
  // Set to an expected 1 Amp maximum and a 100000 microOhm resistor
  //  while (INA.begin(1, 117000) == 0)

  if (INA.begin(1, 117000) == 0)
  {
    Serial.println(F("No INA device found !!!!!"));
    return false;
  }
  INA.setBusConversion(INA_Voltage_Conv_Time);            // 1,1 мс Maximum conversion time 8.244ms
  INA.setShuntConversion(INA_Current_Conv_Time);          // 1,1 мс Maximum conversion time 8.244ms
  INA.setAveraging(INA_Averaging);                 // Average each reading n-times
  INA.setMode(INA_MODE_CONTINUOUS_BOTH); // Bus/shunt measured continuously
  INA.AlertOnBusOverVoltage(true, 25000); // Trigger alert if over 25V on bus
  return true;
}
//================================================================= INA_start


//================================================================= Water_level
//получение данных уровня воды в баке - датчик тока 
void Read_Water_level(bool _show)
{
  TCA_select(Mux_INA);
  //    INA.waitForConversion();
  //  INA.setAveraging(INA_Averaging);
  float A  = INA.getBusMicroAmps();
  float L = mapf(A, coeff[cor_min_current_tank].coeff, coeff[cor_max_current_tank].coeff, 0.0, 350.0);
  V[V_tank_level] = L * coeff[cor_tank_level].coeff;

  float B = INA.getBusMilliVolts() / 1000.0;  // convert mV to Volts
  float C = INA.getShuntMicroVolts() / 1000.0; // convert uV to Millivolts
//  V[V57] = A;
//  V[V58] = B;
//  V[V59] = C;

  if (_show)
  {
    Serial.print(F("From INA226 Напряженее питания, В "));
    Serial.print(B, 4);
    Serial.print(F("\tНапряжение на шунте, мВ "));
    Serial.print(C, 3);
    Serial.print(F("\tТок шунта "));
    Serial.print(A, 0);
    Serial.print(F("\tлитры "));
    Serial.println(L, 1);
  }
}
//================================================================= Water_level


//================================================================= mcp_start
void mcp_start()
{
/* подготовка расширителя портов
  drain_mcp       0 //осушителя
  vent_mcp        1 //вентилятор
  ozone_mcp       2 //озонатор
  pump_mcp        3 //гидрофор - реле на 100 А
  tap_mcp         4 //кран поступления воды в баки
  radio_pump_mcp  5 //радиопереключатель гидрофора вход
  drain_pump_mcp  6 //насос осушителя
  reserv3_mcp     7 //резервный выход 3 не подключен
  water_leak_mcp  8 //датчки протечки вход В0 - не используется
*/

  //Настройка портов расширителя портов на вход-выход
  TCA_select(Mux_MCP);

  mcp.pinMode(drain_mcp, OUTPUT);  //осушитель
  mcp.pinMode(vent_mcp, OUTPUT);  //вентилятор
  mcp.pinMode(ozone_mcp, OUTPUT);  // озонатор
  mcp.pinMode(pump_mcp, OUTPUT);  // гидрофор
  mcp.pinMode(tap_mcp, OUTPUT);  // кран поступления воды в баки

  mcp.pinMode(reserv3_mcp, OUTPUT);  // резервный выход 3

  mcp.pinMode(radio_pump_mcp, INPUT);  //вход радио переключатель гидрофора

  mcp.pinMode(drain_pump_mcp, INPUT);  // вход насос осушителя
  mcp.pullUp(drain_pump_mcp, HIGH);  // подключить внутреннее сопротивленее 100K pullup 

  mcp.pinMode(water_leak_mcp, INPUT);  // датчик протечки 1 - не используется
}
//================================================================= mcp_start


//================================================================= mcp_switch
//передаем информацию на выходы управления(реле, светодиоды) в приложение
void mcp_switch(uint8_t virtu_pin, uint8_t mcp_pin, uint8_t state_pin)
//virtu_pin - ячейка памяти в virtuino
//mcp_pin - контакт подключения реле к мультиплексору mcp
//state_pin - состояние
{

  TCA_select(Mux_MCP);
/*  
    Serial.print("\t\t------ virtu_pin ");
    Serial.print(virtu_pin);
    Serial.print("\t\t------ state_pin ");
    Serial.print(state_pin);
    Serial.print("\t\t------ mcp_pin ");
    Serial.println(mcp_pin);
*/  
  if (mcp_pin == pump_mcp)
    mcp.digitalWrite(mcp_pin, state_pin);  //упраляем реле через mcp - логика работы реле прямая
  else
    mcp.digitalWrite(mcp_pin, !state_pin);  //упраляем реле через mcp - логика работы реле обратная

  V[virtu_pin] = state_pin; //передать состяние в приложение для входов и выходов
}
//================================================================= mcp_switch


//================================================================= mcp_switch_input
//получаем информацию от цифровых входов мультиплексора
uint8_t mcp_switch_input(uint8_t mcp_pin)
//mcp_pin - контакт подключения реле к мультиплексору mcp
{
  uint8_t state_pin;

  TCA_select(Mux_MCP);
  state_pin =  mcp.digitalRead(mcp_pin);  //считываем данные от датчика общий случай

  return state_pin;
}
//================================================================= mcp_switch_input
 
 
//================================================================= check_timer
//передаем информацию на от таймеров для управления(реле, светодиоды) из приложения
void check_timer()
{
 
  /*
    Serial.print("\n\n\t\t");
    Serial.print(virtuino.vMemoryRead(refresh_timer));
    Serial.print("\t");
    Serial.println(refresh_timer_old);
  */
  if (V[V_refresh_timer] && !refresh_timer_old)
  { //сработал таймер обновления воды в баке первый раз
    //проверка допустимых уровеней воды в баке осуществляется в Set_water_level();
    Serial.println("Сработал таймер обновления воды в баке первый раз");

    mcp_switch(V_tap, tap_mcp, OFF); //закрыть кран

    mcp_switch(V_pump, pump_mcp, ON); //включить гидрофор
    refresh_timer_old = ON; //таймер был включен поэтому и предыдущее устанавливаем во включено
  }

  if (!V[V_refresh_timer] && refresh_timer_old)
  {
    Serial.println(F("Отключение по таймеру обновления воды в баке"));
    mcp_switch(V_tap, tap_mcp, ON); //открыть кран

    mcp_switch(V_pump, pump_mcp, OFF); //выключить гидрофор
    refresh_timer_old = OFF; //таймер был выключен установим и предыдущее значение в выключено
  }

  mcp_switch(V_vent, vent_mcp, V[V_vent]);  //вентилятор по клавише приложения или таймеру

  mcp_switch(V_ozone, ozone_mcp, V[V_ozone]); // озонатор по клавише приложения или таймеру
  
  if (V[V_ozone]) {
    //время работы озонатора
    V[V_ozone_time] = ((unsigned long)(millis()-ozone_time)/1000.0)-3*60*60.0; //перевод в секунды
    //если включен отследить 20 минут
    if ((unsigned long)(millis() - ozone_time) > 20 * 60 * 1000) { //если более 20 минут - выключить 20*60*1000
      V[V_ozone] = OFF;
      Serial.println(F("Отключение превышения времени работы озонатора более 20 минут"));
    }
  }
  else {
    ozone_time = millis();
  }

  mcp_switch(V_drain, drain_mcp, V[V_drain]); // осушитель по клавише

  //обработка радиопереключателя нажал включилось еще раз отключилось
  uint8_t radio_pump = mcp_switch_input(radio_pump_mcp); //считать значение радиопереключатель гидрофора

  if (radio_pump == ON && flag_radio_switch_pump == OFF) { //если радиопереключатель нажат и предыдущее OFF
    flag_radio_switch_pump = ON;
    V[V_pump] = !V[V_pump]; //переключить гидрофор в другое состояние
  }
  if (radio_pump == OFF && flag_radio_switch_pump == ON) { //если радиопереключатель нажат и предыдущее ON
    flag_radio_switch_pump = OFF;
  }

  mcp_switch(V_pump, pump_mcp, V[V_pump]); // гидрофор по клавише

  m = millis(); //переменная m = millis();

//время работы помпы осушителя
 if (V[V_drain_pump]) {
      V[V_drain_pump_time] = ((unsigned long)(m-drain_pump_time)/1000.0)-3*60*60.0; //перевод в секунды
  }
  else {
    drain_pump_time = m; //переменная m = millis();
  }

//время работы осушителя
 if (V[V_drain]) {
      V[V_drain_time] = ((unsigned long)(m-drain_time)/1000.0)-3*60*60.0; //перевод в секунды
  }
  else {
    drain_time = m;//переменная m = millis();
  }

//время работы вертилятора
 if (V[V_vent]) {
      V[V_vent_time] = ((unsigned long)(m-vent_time)/1000.0)-3*60*60.0; //перевод в секунды
  }
  else {
    vent_time = m; //переменная m = millis();
  }
  
//время работы гидрофор
 if (V[V_pump]) {
      V[V_pump_time] = ((unsigned long)(m-pump_time)/1000.0)-3*60*60.0; //перевод в секунды
  }
  else {
    pump_time = m; //переменная m = millis();
  }
//время работы таймера обновления воды в баке
 if (V[V_refresh_timer]) {
      V[V_refresh_time] = ((unsigned long)(m-refresh_time)/1000.0)-3*60*60.0; //перевод в секунды
  }
  else {
    refresh_time = m; //переменная m = millis();
  }

}
//================================================================= check_timer


//================================================================= check_limits
bool check_limits(float & V_test, float V_max, float V_min, float V_set, uint16_t V_MI, uint16_t V_addr)
{
  //Проверка на нахождение в допустимых прелах переменной V_test
  //V_test - значение, который нужно проверить
  //V_max - максимальное значение передела
  //V_min - минимальное значение передал
  //V_set - установить если вне предлов
  //V_MI - ячейка памяти в virtuino
  //V_addr - адрес ячейки для сохранения

  if (V_test >= V_max || V_test < V_min || isnan(V_test))
  { //если значение вне пределов или неопределено - nan
    //    Serial.print("Установка первоначального значения: V_set " + (String)V_set
    //                 + "\tV_test " + (String)V_test + "\tV_MI " + (String)V_MI + "\tV_V_addr " + (String)V_addr);
    Serial.printf("Установка первоначального значения: V_set %f\tV_test %f\tV_MI %d\tV_addr %d\n", V_set, V_test, V_MI, V_addr);

    V_test = V_set; //не будет изменения
    V[V_MI] = V_test;
    //сохранить в памяти EEPROM
    Mem_float_write(V_addr, V_test);

    return true;
  }
  else
  {
    return false;
  }
}
//================================================================= check_limits


//================================================================= change_limits
bool  change_limits(float & V_test, float V_set, float V_max, float V_min, uint16_t V_MI, uint16_t V_addr)
{
  //V_test - уровень, который нужно установить
  //V_set - значение из приложения
  //V_max - максимальное значение - предел
  //V_min - минимальное значение - предел
  //V_MI - ячейка памяти в virtuino
  //V_addr - адрес ячейки для сохранения
  // возвращает true - если нужно обновить память

  //Установка значения из приложения
  if (V_test != V_set)
  { //Если не равны в памяти и приложении
    if (V_set < V_max && V_set > V_min)
    { //усли в допустимых пределах
      V_test = V_set;
      //сохранить в памяти EEPROM
      Mem_float_write(V_addr, V_test);
      V[V_MI] = V_test; //для работы коэффициентов обновит тут значенее
      Serial.print(F("Установка значения из приложения V_MI "));
      Serial.print(V_MI);
      Serial.print(F("\tV_test "));
      Serial.print(V_test, 3);
      Serial.print(F("\tV_addr "));
      Serial.println(V_addr);

      return true;
    }
    else
    { //если не в прелах, то установить значение из памяти
      V[V_MI] = V_test;
      //        Serial.print(" Установка значения в приложение из памяти V_MI ");
      //        Serial.print(V_MI);
      //        Serial.print("\tV_test ");
      //        Serial.println(V_test, 3);

      return false;
    }
  }
}
//============================================================== change_limits

//============================================================== Set_voter_level
void Set_water_level()
{
  bool write_EEPROM_flag = false;

  float pump_off_lev, tap_off_lev, tap_on_lev, tap_off_on_lev;
  float pump_off_level_set, tap_off_level_set, tap_on_level_set, tap_off_on_level_set;

  pump_off_lev = Mem_float_read(pump_off_level_addr); //считать значения из памяти EEPROM
  tap_off_lev = Mem_float_read(tap_off_level_addr);
  tap_on_lev = Mem_float_read(tap_on_level_addr);
  tap_off_on_lev = Mem_float_read(tap_off_on_level_addr);

  pump_off_level_set = V[V_pump_off_level]; //считать данные если они установлены в приложении
  tap_off_level_set = V[V_tap_off_level];
  tap_on_level_set = V[V_tap_on_level];
  tap_off_on_level_set = V[V_tap_off_on_level];

  //   Serial.println("Значения из памяти pump_off_lev " + (String)pump_off_lev + " tap_on_lev " + (String)tap_on_lev);
  //   Serial.println("Значения из приложения pump_off_level_set " + (String)pump_off_level_set + " tap_on_level_set " + (String)tap_on_level_set);

  //проверка на нахождение значенения в пределах, т.к. неизвеcтны первоначальные значения в памяти
  write_EEPROM_flag |= check_limits(pump_off_lev, 100, 10, 50, V_pump_off_level, pump_off_level_addr);
  write_EEPROM_flag |= check_limits(tap_off_lev, 400, 300, 350, V_tap_off_level, tap_off_level_addr);
  write_EEPROM_flag |= check_limits(tap_on_lev, 100, 10, 70, V_tap_on_level, tap_on_level_addr);
  write_EEPROM_flag |= check_limits(tap_off_on_lev, 350, 280, 320, V_tap_off_on_level, tap_off_on_level_addr);


  //Изменение значения из приложения
  write_EEPROM_flag |= change_limits(pump_off_lev, pump_off_level_set, 100, 10, V_pump_off_level, pump_off_level_addr);
  write_EEPROM_flag |= change_limits(tap_off_lev, tap_off_level_set, 400, 300, V_tap_off_level, tap_off_level_addr);
  write_EEPROM_flag |= change_limits(tap_on_lev, tap_on_level_set, 100, 10, V_tap_on_level, tap_on_level_addr);
  write_EEPROM_flag |= change_limits(tap_off_on_lev, tap_off_on_level_set, 350, 280, V_tap_off_on_level, tap_off_on_level_addr);

  //запись данных в память
  if (write_EEPROM_flag)
  { //записать данные в память
    if (V[V_Internal_External]) //если внутпрення память
      EEPROM.commit(); //подтвердить запись в память
    Serial.println(F("\n\nОбновлены данные в памяти\n"));
  }

  float t_level = V[V_tank_level];//получим значенее записанное ранее
  /*
    Serial.print("\n\n\t\t t_level ");
    Serial.print(t_level);
    Serial.print("\t refresh_timer ");
    Serial.print(refresh_timer);
    Serial.print("\t tap_on_lev ");
    Serial.print(tap_on_lev);
    Serial.print("\t tap_on_lev ");
    Serial.print(tap_on_lev);
  */

  if (t_level < pump_off_lev)
  {
    //    Serial.println("Уровень воды в баке ниже установленного выключаем гидрофор");

    mcp_switch(V_pump, pump_mcp, OFF); //выключить гидрофор

    //сбросить таймер обновления воды
    V[V_refresh_timer] = OFF;
  }

  mcp_switch(V_tap, tap_mcp, V[V_tap]); //установить кран по кнопке

  if (t_level < tap_on_lev)
  {
//        Serial.println(F("Уровень воды в баке ниже установленного открываем кран"));
/*        Serial.print(t_level);
        Serial.print(" ");
        Serial.print(tap_on_lev);
        Serial.print(" ");
        Serial.print(V[V_lastCommTime]);
        Serial.print(" ");        
        Serial.println(lastCommTime);
 */       
    if ((V[V_lastCommTime] > FirstWait) //подождать 60 сек в начале старта программы
        && (lastCommTime != 0)) //именно старт а не переполненее счетчика времени

      mcp_switch(V_tap, tap_mcp, ON); //открыть кран
  }

  if (t_level > tap_off_lev)
  {
    //    Serial.println(F("Уровень воды в баке выше установленного закрываем кран"));
    mcp_switch(V_tap, tap_mcp, OFF); //закрыть кран
  }

  if ((t_level < tap_off_on_lev) &&  !V[V_refresh_timer])
  {
  //      Serial.println(F("Уровень воды в баке понизился и нет таймера открываем кран"));
    if ((V[V_lastCommTime] > FirstWait) //подождать 60 сек в начале старта программы
        && (lastCommTime != 0)) //именно старт а не переполненее счетчика времени
      mcp_switch(V_tap, tap_mcp, ON); //открыть кран
  }

}
//================================================================= Set_voter_level


//================================================================= SetHumidityLimits
void SetHumidityLimits()
//установка переделов срабатывания датчика по влажности
{
  bool write_EEPROM_flag = false;


  //считать значения из памяти EEPROM
  float humidity_low = Mem_float_read(hum_lower_addr);
  float humidity_upp = Mem_float_read(hum_upper_addr);

  float humidity_lower_set = V[V_humidity_lower]; //считать данные если они установлены в приложении
  float humidity_upper_set = V[V_humidity_upper];

  //  Serial.println("Значения из памяти нижний предел " + (String)humidity_low + " верхний предел " + (String)humidity_upper);
  //  Serial.println("Значения из приложения нижний предел " + (String)humidity_lower_set + " верхний предел " + (String)humidity_upper_set);
  //  Serial.print("Start= "); Serial.print(write_EEPROM_flag); Serial.print("\t");
  //проверка на нахождение значенения в пределах, т.к. неизвеcтны первоначальные значения в памяти

  write_EEPROM_flag |= check_limits(humidity_low, 100, 10, 45, V_humidity_lower, hum_lower_addr);
  //  Serial.print(" Lower= ");Serial.print(write_EEPROM_flag); Serial.print("\t");
  write_EEPROM_flag |= check_limits(humidity_upp, 100, 10, 55, V_humidity_upper, hum_upper_addr);
  //  Serial.print(" Upper= "); Serial.print(write_EEPROM_flag); Serial.print("\t");
  //Изменение значения из приложения
  write_EEPROM_flag |= change_limits(humidity_low, humidity_lower_set, 100, 10, V_humidity_lower, hum_lower_addr);
  write_EEPROM_flag |= change_limits(humidity_upp, humidity_upper_set, 100, 10, V_humidity_upper, hum_upper_addr);
  //  Serial.print(" Program= "); Serial.print(write_EEPROM_flag); Serial.print("\n");

  //проверка на минимальный передел между нижней границей и верхней
  if (humidity_low + 10 > humidity_upp)
  { //установить верхний предел на 10% больше
    humidity_upp = humidity_low + 10;
    V[V_humidity_upper] = humidity_upp;
    //сохранить в памяти EEPROM
    Mem_float_write(hum_upper_addr, humidity_upp);
    write_EEPROM_flag = true;
    Serial.println(F("Обновлен верхний предел"));
  }

  //запись данных в память
  if (write_EEPROM_flag)
  { //записать данные в память
    if (V[V_Internal_External])
      EEPROM.commit(); //подтвердить запись в память
    Serial.println(F("Обновлены данные в памяти"));
  }

  //  Serial.println("Переделы после обработки нижний " + (String)humidity_low + " верхний " + (String)humidity_upper);

  //Включение или выключение осушителя
  if (V[V_hum_tanks] > humidity_upp)
  {
    //    Serial.println("Низкая влажность включаем осушитель");
    mcp_switch(V_drain, drain_mcp, ON);
  }

  if ((V[V_hum_tanks] < humidity_low) || (V[V_hum_tanks] < V[V_hum_room])) //Если влажнось ниже установленного минимума или ниже чем в комнате
  {
    //    Serial.println(F("Влажность в норме отключаем осушитель"));
    mcp_switch(V_drain, drain_mcp, OFF);
  }
}
//==================================================================== SetHumidityLimits


//==================================================================== readAM2350
void readAM2320(uint8_t Mux_number, uint16_t temp_MI, uint16_t hum_MI, float cor_t, float cor_h, bool _show)
//чтенее данных и их сохраненее датчиков влажности и темературы
{
  // Mux_number номер ножек подключения AM2320 в мультиплексоре I2C
  // temp_MI - номер ячейки памяти в virtuino
  // hum_MI - номер ячейки памяти в virtuino
  // cor_t - коррекция показаний
  // cor_h - коррекция показаний
  //_show - выводить инофрмацию

  //Чтение данных от датчиков температуры и влажности AM2320 подключенным через шину I2C

  if (_show)
    Serial.print(F("Read sensors' AM2320 "));

  TCA_select(Mux_number); //подготовка чтения через мультиплексор шины I2C
   switch(am2320.Read()) {
    case 2:
      if (_show)
        Serial.printf("CRC failed AM2320 %d\n", Mux_number);
      break;
    case 1:
      if (_show)
        Serial.printf("Sensor offline AM2320 %d\n", Mux_number);
      break;
    case 0:
      float temperature = am2320.t;
      float humidity = am2320.h;      
      temperature += cor_t; //коррекция показаний
      humidity += cor_h; //коррекция показаний
      V[temp_MI ] = temperature;   // write temperature to virtual memory
      V[hum_MI] =  humidity;   // write temperature 1 to virtual memory
      
      if (_show) 
        Serial.printf("Temp= %.2f*C Humidity= %.2f\%\n", temperature, humidity);
       
      break;
    }
}
//==================================================================== readAM2320


//==================================================================== tick_Totalflow
//сохраняет значенее общего количества литров в памяти
void tick_Totalflow()
{
  if (!V[V_Internal_External]) //записать если используется внешння память
  {
    TCA_select(Mux_Ext_EEPROM);

    //т.к. FRAM разрушаемая - при чтении данные разрушаются и потом восстанавливаются, то нет смысла только читать для уменьшения количества циклов
    Mem_float_write(Totalflow_addr, flow_sensor1.readTotalLitres());

    //      if (Mem_float_read(Totalflow_addr + 4) != flow_sensor2.readTotalLitres()) //если общее количество литров датчика 2 и в памяти не совпадает - сохранить
    Mem_float_write((Totalflow_addr + 4), flow_sensor2.readTotalLitres());
  }

}
//==================================================================== tick_Totalflow


//==================================================================== tick_flow
//процедура вызова отбработчика даных расходомера - каждую секунду для перевода импульсов в литры
void tick_flow(bool _show)
{
  flow_sensor1.calculate_flow(_show);
  flow_sensor2.calculate_flow(_show);
}
//==================================================================== tick_flow
#ifndef use_Ticker
//процедура вызова отбработчика даных расходомера - каждую секунду для перевода импульсов в литры
void Scheduler()
{
  Serial.println(V[V_FreeHeap], 0);
}
//==================================================================== tick_flow
#endif


//==================================================================== tick_communication
//процедура вызова обработчика наличия связи с планшетом

 bool Flag_CommCount = 0;
 
void tick_communication(bool _show)
{
 
  
  if ((V[V_lastCommTime] == lastCommTime) && (V[V_lastCommTime] != 0) && (V[V_lastCommTime] > FirstWait))
  { //если прекращена связь и не старт программы
    
    if(!Flag_CommCount) V[V_lastCommCount] = 0; //сбросить счетчик если опять нет связи
    Flag_CommCount = 1; //установить флаг 
    ++V[V_lastCommCount];
    
    if (_show) {
      Serial.print(F("\t\V[V_lastCommTime] "));
      Serial.print(V[V_lastCommTime]);
      Serial.print(F("\t\lastCommTime "));
      Serial.print(lastCommTime);
      Serial.print(F("\tlastCommCount "));
      Serial.print(V[V_lastCommCount]);
      Serial.print(F("\tWiFi.status "));
      Serial.println(WiFi.status());
    }  
      /*
      WL_NO_SHIELD        = 255 – если не подключен WiFi-модуль
      WL_IDLE_STATUS      = 0 временный статус. Он возвращается, когда функция WiFi.begin() вызвана и остается активной. 
                              Если количество попыток подключения будет исчерпано, этот статус меняется на WL_CONNECT_FAILED, 
                              а если соединение будет успешно установлено, то на WL_CONNECTED
      WL_NO_SSID_AVAIL    = 1 – нет доступных SSID
      WL_SCAN_COMPLETED   = 2 – когда завершено сканирование сетей
      WL_CONNECTED        = 3 – если соединение с WiFi-сетью успешно установлено
      WL_CONNECT_FAILED   = 4 – когда все попытки подключения заканчиваются неуспешно
      WL_CONNECTION_LOST  = 5 – если соединение прервано
      WL_DISCONNECTED     = 6  – при отключении от сети 
      */

    if ((WiFi.status() != WL_CONNECTED) && (V[V_lastCommCount] > 60)) { //Если нет связи и прошло 5 минут(60*5 секунд) - пересоединиться 
      //WiFi_Connect();
      WiFi.begin(ssid, password);
    }
  }
  else
  {
    Flag_CommCount = 0; //сбросить флаг 
//    V[V_lastCommCount] = 0;  //в приложение отображается предыдущее значенее
  }
  lastCommTime = V[V_lastCommTime];
}
//==================================================================== tick_communication


//==================================================================== pulseCounter_input
void pulseCounter_input()
//подсчет исмульсов при прерывании - поступлении импульса
{
  // Increment the pulse counter
  flow_sensor1.count();
}
//==================================================================== pulseCounter_input


//==================================================================== pulseCounter_output
void pulseCounter_output()
//подсчет исмульсов при прерывании - поступлении импульса
{
  // Increment the pulse counter
  flow_sensor2.count();
}
//==================================================================== pulseCounter_output


//================================================================= find_wifi
bool find_wifi()
{
  //поиск известной сети. Если не найден продолжить поиск

  uint8_t n = WiFi.scanNetworks();

  Serial.println();
  if (n == 0)
  {
    Serial.println(F("No networks found"));
    return false;
  }
  else
  {
    Serial.print(n);
    Serial.println(F(" networks found :"));
    for (uint8_t i = 0; i < n; ++i)
    {
      Serial.println(WiFi.SSID(i));
      if (WiFi.SSID(i) == "******1")
      {
        ssid = "******1";
        password = "******";
        return true;
      }
      if (WiFi.SSID(i) == "******2")
      {
        ssid = "******2";
        password = "******";
        IPAddress ip(10, 0, 1, 208);
        IPAddress gateway(10, 0, 1, 2);
        IPAddress subnet(255, 255, 255, 0);
        IPAddress dns(8, 8, 8, 8);
        WiFi.config(ip, dns, gateway, subnet);          // If you don't want to config IP manually disable this line
        return true;
      }
    }
  }
  return false;
}
//=================================================================  find_wifi


//================================================================= WiFi_Connect
void WiFi_Connect()
{
  //просканируем на изветные сети, чтобы подключить автоматически
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  delay(100);
  while (!find_wifi())
  { //поиск известной сети. Если не найден продолжить поиск
    Serial.println(F("\nTrying to find known WiFi network"));
  }

  //подключимся к знакомой сети
  Serial.println("Connecting to " + String(ssid));
  //  WiFi.mode(WIFI_STA);                       // Конфигурация как клиет WiFi
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.print(F("\nWiFi connected. Local IP "));
  Serial.println(WiFi.localIP());
}
//================================================================= WiFi_Connect

Код управления газовым котлом

#include <ESP8266WiFi.h>

#include <ESP8266WebServer.h>
#include <ESP8266HTTPUpdateServer.h>

#include <PZEM004Tv30.h>

#include <LCD_1602_RUS.h> //Бибилиотека с русским шрифтом для шины I2C
#include <Wire.h>

LCD_1602_RUS lcd(0x3f, 16, 2); // set the LCD address to 0x3f for a 16 chars and 2 line display

ESP8266WebServer HttpServer(8080);      // Порт для входа, стандартный 80 занят для virtuino
ESP8266HTTPUpdateServer httpUpdater;

const char* ssid;
const char* password;

char Message[512]; //Массив для отправки сообщений серверу Virtuino

#include "ROM.h" //внутрення EEPORM для хранения данных коэффициентов

#include "VirtuinoCM_command.h"

#include <OneWire.h>
#define ONE_WIRE_BUS D5 // Ножка подключения датчика температуры

// https://habr.com/ru/post/470217/ - питание внутреннего датчика

OneWire  ds(ONE_WIRE_BUS); //Датчик температуры DS18B20

byte addr_ext[8] = {0x28, 0xE4, 0xF1, 0x79, 0x97, 0x11, 0x03, 0x23}; //external
byte addr_int[8] = {0x28, 0xB3, 0x87, 0x79, 0xA2, 0x00, 0x03, 0xBA}; //internal

/*
byte addr_ext[8] = {0x10, 0xB7, 0xA0, 0x59, 0x01, 0x08, 0x00, 0xDC}; //external
byte addr_int[8] = {0x28, 0x6E, 0x57, 0x79, 0xA2, 0x00, 0x03, 0x6A}; //internal
*/

#define PIN_RELAY D6 // Реле управления котлом
//#define PIN_CALIBRATE D6 // Реле калибровки - нет необходимости

int flag_P = 0; //флага начала учета мощности потребления

//HardwareSerial hwserial(UART0);     // Use hwserial UART0 at pins GPIO1 (TX) and GPIO3 (RX) занято под USB
//PZEM004Tv30 pzem(&hwserial);
PZEM004Tv30 pzem(D7, D8); //Sowtware 
/* Use software serial for the PZEM
   Pin D7 Rx (Connects to the Tx pin on the PZEM)
   Pin D8 Tx (Connects to the Rx pin on the PZEM)
*/

//описание https://www.innovatorsguru.com/pzem-004t-v3/
//https://mysku.ru/blog/china-stores/43331.html транзистор
//https://github.com/mandulaj/PZEM-004T-v30 библиотека

int errorLCD; //ошибка поска дисплея
//================================================================= setup
void setup() {
  pinMode(PIN_RELAY, OUTPUT); // Объявляем пин реле как выход
  digitalWrite(PIN_RELAY, OFF); // Выключаем реле - посылаем высокий

//  hwserial.swap();           // (optionally) swap hw_serial pins to gpio13(rx),15(tx)
  
  Serial.begin(115200); //далее используем порт Serial1 выход D4 (Tx) и отдельный переходник USB-TTL
  Serial.println();

  Wire.begin(SDA, SCL); //подготовили шину I2C D1 D2
  Wire.beginTransmission(0x3f); //Поиск дисплея
  errorLCD = Wire.endTransmission(); 

  if (errorLCD == 0) {
    lcd.init();
    lcd.setBacklight(255);
    lcd.home();
    lcd.clear();
    lcd.setCursor(4, 0);
    lcd.print("Загрузка");
  } 
  else {
    Serial.print(F("Error : LCD not found."));
    Serial.println(errorLCD);
  }

  EEPROM.begin(64);//Установить размер внутренней памяти для хранения первоначальных значений

  WiFi_Connect(); //найти и подсоединиться к сети WiFi
  Serial.printf("\nWiFi connected to %s IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());

  httpUpdater.setup(&HttpServer, "admin", "admin");
  HttpServer.onNotFound(handleNotFound);
  HttpServer.begin();
  Serial.print(F("Start server IP adress:"));
  Serial.print(WiFi.localIP());
  Serial.println(F(":8080/update"));

  init_variables(); //инициализация переменных Virtuino
 
  if (errorLCD == 0) { //подготовить дисплей для отображения информации
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(F("Troom= ??"));
    lcd.setCursor(0, 1);
    lcd.print(F("Tset = ??"));
  }
}
//================================================================= setup

//================================================================= handleNotFound
/* Выводить надпись, если такой страницы ненайдено */
void handleNotFound() {
  HttpServer.send(404, "text/plain", "404: Not found");
}
//================================================================= handleNotFound

#define server_cycle 5000 //период опроса датчиков, передачи и приема данных от сервера Virtuino
#define key_cycle 500 //период опроса кнопок на устройстве
//================================================================= loop
void loop() {
  static uint32_t switchTime = millis(); //таймер обращения к серверу Virtuino
  static uint32_t keyTime = millis(); //таймер опроса кнопок
  static uint32_t termoTime = millis(); //таймер опроса кнопок
  static int positionCounter = 12;  

  HttpServer.handleClient();       // Прослушивание HTTP-запросов от клиентов

  if ((unsigned long)(millis() - switchTime) > server_cycle) //запрос Virtuino каждые 5 секунд
  {
 //  Serial.println(ESP.getFreeHeap());
   read_pzem(); //запрос данных от энергометра
   dallRead(); //опрос датчиков температуры
 
    V[V_communication]++; //увеличить счетчик каждые 5 секунд
    
    //  Serial.println("Send request to Virtuino server 172.20.0.34 from loop");
    sprintf(Message, "1234!V%d=%.1f$!V%d=%.3f$!V%d=%.1f$\
                          !V%d=%.3f$!V%d=%.2f$!V%d=%.2f$\
                          !V%d=%.0f$!V%d=%.0f$!V%d=%.1f$\
                          !V%d=%.2f$!V%d=%.0f$!V%d=%.2f$\
                          !V%d=?$!V%d=?$!V%d=?$!V%d=?$!V%d=?$!V%d=?$!V%d=?$",
            
            V_Voltage, V[V_Voltage], V_Current, V[V_Current], V_Power, V[V_Power],
            V_Energy, V[V_Energy], V_temp_room_heat, V[V_temp_room_heat], V_communication, V[V_communication],
            V_heat_on_off, V[V_heat_on_off], V_connection_failed, V[V_connection_failed], V_Frequency, V[V_Frequency], 
            V_PF, V[V_PF], V_error_pzem, V[V_error_pzem],V_external_temp, V[V_external_temp],
            //запрос данных
            V_Energy_reset, V_cor_temp_room_heat, V_temp_heat_delta, V_temp_heat_Virtuino, V_flag_timer, V_cor_external_temp, V_timer_temp);

    if (debug) Serial.println(Message);

    send_request("172.20.0.34", 80, Message, 800); //500 - при большем значении затруднение в обновлении по воздуху. При меньшем потери связи

//сбросить показания накопленной энергии
    if (V[V_Energy_reset] == 1) { 
      pzem.resetEnergy();
      V[V_error_pzem] = 0; //сбросить счетчик ошибок
    }  
    
//проверка режима работы таймера - отлавиливаем начало и конец
    if(V[V_flag_timer] == 1 && flag_timer == 0) { //включился таймер первый раз
      flag_timer = 1;
      old_room_temp = V[V_temp_heat_Virtuino]; //запомнить старую температуру 
      V[V_temp_heat_Virtuino] = V[V_timer_temp]; //установить новую температуру
      //передать температуру приложению
      sprintf(Message, "1234!V%d=%.1f$", V_temp_heat_Virtuino, V[V_temp_heat_Virtuino]);
      if (debug) Serial.println(Message);
      send_request("172.20.0.34", 80, Message, 200); //200 только передача
    }
    
    if(V[V_flag_timer] == 0 && flag_timer == 1) { //выключился таймер первый раз
      flag_timer = 0;
      V[V_temp_heat_Virtuino] = old_room_temp; //восстановить старую температуру 
      //передать температуру приложению
      sprintf(Message, "1234!V%d=%.1f$", V_temp_heat_Virtuino, V[V_temp_heat_Virtuino]);
      if (debug){
        Serial.print("восстановить старую температуру ");
        Serial.println(V[V_temp_heat_Virtuino]);
        Serial.println(Message);
      }
      send_request("172.20.0.34", 80, Message, 200); //200 только передача
    }

//сравнение температуры в комнате и установленной
    if (V[V_temp_heat_Virtuino] - V[V_temp_heat_delta] > V[V_temp_room_heat]) { //включить котел
      V[V_heat_on_off] = ON;
    }
    if (V[V_temp_heat_Virtuino] + V[V_temp_heat_delta] < V[V_temp_room_heat]) { //выключить котел
      V[V_heat_on_off] = OFF;
    }
    digitalWrite(PIN_RELAY, V[V_heat_on_off]);//выключить/включить котел

    change_variables(false); //изменить значение из приложения

    //вывод информации на LCD
    if (errorLCD == 0) {
      lcd.setCursor(7, 0);
      lcd.print(V[V_temp_room_heat], 2);
      lcd.setCursor(7, 1);
      lcd.print(V[V_temp_heat_Virtuino], 2);
      
      lcd.setCursor(12, 0); //индикатор связи
      if(V[V_connection_failed] != 0) lcd.print("   \\");
        else lcd.print("   Y");
    }
    switchTime = millis();
  }

  //бегущая строка индиктора отопления
  if ((unsigned long)(millis() - termoTime) > 400) 
  {
      if (positionCounter++ > 14) positionCounter = 12;
      lcd.setCursor(positionCounter, 1); //индикатор включения отопления
      if (V[V_heat_on_off] == ON) lcd.print(" > ");
      else lcd.print("    ");
      termoTime = millis();
  }
  
  //обработка нажатия клавиш на устройстве
  if ((unsigned long)(millis() - keyTime) > key_cycle) //запрос каждую 0.5 секунды
  {
    float keyValue = key();
    //     Serial.println(keyValue);
    if (keyValue != 0) //опрос кнопки
    {
      V[V_temp_heat_Virtuino] +=  keyValue; //изменить значение и передать приложению
      sprintf(Message, "1234!V%d=%.1f$", V_temp_heat_Virtuino, V[V_temp_heat_Virtuino]);
      if (debug) Serial.println(Message);
      send_request("172.20.0.34", 80, Message, 200); //200 только передача
      lcd.setCursor(7, 1);
      lcd.print(V[V_temp_heat_Virtuino], 2);
    }
    keyTime = millis();
  }

}
//================================================================= loop

//================================================================= key
float key() {
  int val = analogRead(0); // считываем значение с аналогового входа и записываем в переменную val
  if (val < 50) return -0.1; // сверяем переменную, если val меньше 50 возвращаем -1 (левая кнопка)
  else if (val > 450) return 0.1; // если val больше 600 правая кнопка)возвращаем 1
  else return 0;
}
//================================================================= key

//================================================================= read_pzem
void read_pzem()
{
  float voltage = pzem.voltage();
  if ( !isnan(voltage) ) {
    //        Serial.print("Voltage: "); Serial.print(voltage); Serial.println("V");
    V[V_Voltage] = voltage;
  } else {
    Serial.println("Error reading voltage");
    V[V_error_pzem]++;
  }

  float current = pzem.current();
  if ( !isnan(current) ) {
    //        Serial.print("Current: "); Serial.print(current); Serial.println("A");
    V[V_Current] = current;
  } else {
    Serial.println("Error reading current");
    V[V_error_pzem]++;
  }

  float power = pzem.power();
  if ( !isnan(power) ) {
    //        Serial.print("Power: "); Serial.print(power); Serial.println("W");
    V[V_Power] = power;
  } else {
    Serial.println("Error reading power");
    V[V_error_pzem]++;
  }

  float energy = pzem.energy();
  if ( !isnan(energy) ) {
    //        Serial.print("Energy: "); Serial.print(energy,3); Serial.println("kWh");
    V[V_Energy] = energy;
  } else {
    Serial.println("Error reading energy");
    V[V_error_pzem]++;
  }

  float frequency = pzem.frequency();
  if ( !isnan(frequency) ) {
    //        Serial.print("Frequency: "); Serial.print(frequency, 1); Serial.println("Hz");
    V[V_Frequency] = frequency;
  } else {
    Serial.println("Error reading frequency");
    V[V_error_pzem]++;
  }

  float pf = pzem.pf();
  if ( !isnan(pf) ) {
    //        Serial.print("PF: "); Serial.println(pf);
    V[V_PF] = pf;
  } else {
    Serial.println("Error reading power factor");
    V[V_error_pzem]++;
  }

  //    Serial.println();
}
//================================================================= read_pzem

//================================================================= dallRead
void dallRead()
{
  ds.reset();
  ds.select(addr_int);
  ds.write(0xBE); //Считывание значения с датчика
  int16_t temp = (ds.read() | ds.read() << 8); //Принимаем два байта температуры 16-разрядное целое число со знаком
  V[V_temp_room_heat] = (float)temp * 0.0625 + V[V_cor_temp_room_heat];

  ds.reset();
  ds.select(addr_ext);
  ds.write(0xBE); //Считывание значения с датчика
  temp = (ds.read() | ds.read() << 8); //Принимаем два байта температуры

//int16_t raw = (data[1] << 8) | data[0];
//float ft = (int16_t) ((MSB << 8) | LSB);
/*
  if (temp & 0x8000) // Знак значения если значение меньше нуля
  {
    temp = (temp ^ 0xffff) + 1;  // Инвертируем биты, если знак отрицательный и прибавляем единицу
    temp = 0 - temp; // Make negative
  }
*/  
  V[V_external_temp] = temp* 0.0625+V[V_cor_external_temp]; //Умножаем на 0.0625

  ds.reset();
  ds.write(0xCC); //Обращение ко всем датчикам
  ds.write(0x44, 1); //Команда на конвертацию
}
//================================================================= dallRead


//================================================================= find_wifi
bool find_wifi()
{
  //поиск известной сети. Если не найден продолжить поиск

  uint8_t n = WiFi.scanNetworks();

  Serial.println();
  if (n == 0)
  {
    Serial.println(F("No networks found"));
    return false;
  }
  else
  {
    Serial.print(n);
    Serial.println(F(" networks found :"));
    for (uint8_t i = 0; i < n; ++i)
    {
      Serial.println(WiFi.SSID(i));
      if (WiFi.SSID(i) == "******1")
      {
        ssid = "******1";
        password = "******";
        return true;
      }
      if (WiFi.SSID(i) == "******2")
      {
        ssid = "******2";
        password = "87654321";
        IPAddress ip(10, 0, 1, 208);
        IPAddress gateway(10, 0, 1, 2);
        IPAddress subnet(255, 255, 255, 0);
        IPAddress dns(8, 8, 8, 8);
        WiFi.config(ip, dns, gateway, subnet);          // If you don't want to config IP manually disable this line
        return true;
      }
    }
  }
  return false;
}
//=================================================================  find_wifi


//================================================================= WiFi_Connect
void WiFi_Connect()
{
  //просканируем на изветные сети, чтобы подключить автоматически
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  delay(100);
  while (!find_wifi())
  { //поиск известной сети. Если не найден продолжить поиск
    Serial.println(F("\nTrying to find known WiFi network"));
  }

  //подключимся к знакомой сети
  Serial.println("Connecting to " + String(ssid));
  //  WiFi.mode(WIFI_STA);                       // Конфигурация как клиет WiFi
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
//  Serial.print(F("\nWiFi connected. Local IP "));
//  Serial.println(WiFi.localIP());
}
//================================================================= WiFi_Connect

Неактивний

#4 2020-01-09 17:30:04

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Система управление резервом воды и котлом отопления в квартире.

Ссылки на остальные тексты программы
https://drive.google.com/open?id=13i1B7 … nIwDIfkgXI

https://drive.google.com/open?id=1jvYK4 … dc-yMX0mTX

Решил опубликовать - может кто-то найдет что-то для себя полезное.
Сильно не ругайте - если случайно нарушил правила форума.
Всем удачи

Неактивний

#5 2020-01-09 23:51:51

Lic Control
Учасник
Зареєстрований: 2016-01-23
Повідомлень: 124

Re: Система управление резервом воды и котлом отопления в квартире.

Отличная работа. Молодец!

Неактивний

#6 2020-01-10 00:08:08

renoshnik
Учасник
Зареєстрований: 2017-04-03
Повідомлень: 1,025

Re: Система управление резервом воды и котлом отопления в квартире.

Отлично !!!

Неактивний

#7 2020-01-10 10:21:57

Watchdog
Гість

Re: Система управление резервом воды и котлом отопления в квартире.

Впечатляет!

#8 2020-01-10 10:26:49

vvr
Учасник
Зареєстрований: 2015-04-12
Повідомлень: 874

Re: Система управление резервом воды и котлом отопления в квартире.

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

Неактивний

#9 2020-01-18 21:22:47

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Система управление резервом воды и котлом отопления в квартире.

Учитывая то, что устройство управления газовым котлом выполнено на NodeMCU (процессор имеет достаточно мощности) и  эксплуатация в течении двух недель показали целесообразность построить отдельный сервер Virtuino на этом процессоре.
Благодаря этому скорость реакции на нажатие клавиш в приложении увеличилась, что сделало систему более информативной и дружественной. Также это было возможным так, как приложение Virtuino может одновременно быть подключенным к нескольким серверам (один виртуальный и два реальных).
Новый скетч для устройства управления газовым котлом приведен ниже.
https://drive.google.com/open?id=1tWN54 … ufIqAW69nj

Неактивний

#10 2020-01-26 19:23:05

Жорж
Учасник
Зареєстрований: 2019-02-23
Повідомлень: 14

Re: Система управление резервом воды и котлом отопления в квартире.

нарешті некомерційний цікавий проект, що ще й має практичну цінність)))
дякую що опублікували. сподобався ваш метод вимірювання кількості води в баках. чомусь досі не зустрічав таких погружних датчиків тиску, вважав що є тільки врізні. поділіться посиланням на датчик чи напишіть його модель (у пості модель ніде не вказана). яка точність показань датчика?
у самого стоїть аналогічна задача вимірювання кількості запасеної води в баку, поки що не можу визначитися з методом вимірювання:
- ультразвукове вимірювання відстані до дзеркала води (є сумніви щодо живучості датчиків при великій вологості всередині баку)
- вимірювання тиску рідини (не хотілося робити зайві врізки в бак. виявляється є погружні датчики)
- визначення факту наявності/відсутності води на певній висоті сенсорним датчиком (досить обмежений метод, але зручний тим, що не треба різати бак, датчики спокійно працюють через пластмасову стінку баку)
- ну і всякі інші кустарні методи типу купи герконів і поплавка з магнітом.

із позитиву відмічу те, що у вашій системі реалізований аварійний перелив в каналізацію. але не зрозуміло як саме він організований, якого діаметру труба, як підключено: зразу в каналізацію через "сифон", чи відкритий кінець труби виведено в умивальник. є підозри, що із-за того, що в нормально діючій системі перелив має траплятися дуже рідко, в сифоні за період простою просто зацвіте вода чи розведуться якісь бактерії.
в моєму баку переливу поки що нема, бо він розташований на горищі, і не зрозуміло, як його краще організувати в умовах замерзання.

ще цікавить де у вашій системі розташований датчик протєчки: в аварійному переливі чи десь знизу на полиці чи підлозі.

Неактивний

#11 2020-01-26 21:29:52

Жорж
Учасник
Зареєстрований: 2019-02-23
Повідомлень: 14

Re: Система управление резервом воды и котлом отопления в квартире.

ну і трохи критики, сподіваюсь конструктивної ))):

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

-при керуванні заповненням баку я б повністю поклався на поплавковий клапан, а не на кран з сервоприводом.

-незрозумілий алгоритм керування насосною станцією (гідрофором). при заявленому тиску в системі водопроводу 1,5 бар (а на графіку протягом всього дня 09/10/2019 бачимо лише 0,8-0,9 бар) він же ж має бути постійно підключений до мережі. а у вас ще й передбачена "чарівна" радіо-кнопочка для дистанційного включення гідрофора із ванни.
корисним буде хіба що вночі його відключати, щоб насос всіх не будив, коли хтось сходить в туалет. це якщо ви живете в квартирі.

чи ви беспосередньо керуєте мотором на основі показів датчиків протоку і тиску води? тоді великі питання до надійності датчиків. справа в тому, що такі електронні датчики тиску мають одну неприємну особливість: вони з часом забиваються і перестають показувати наявність будь-якогось тиску. при всіх закритих кранах система буде намагатися нескінченно включати мотор (згідно показів датчика тиску ж нема), і мотор буде працювати "на закриту задвижку" доки не перегріється.

-навіщо постійно вимірювати температуру водопровідної води?

Датчики температуры DS18В20 OneWire контролируют температуру на твердотельном реле включения гидрофора... Предотвращая перегрев реле гидрофора"

- а чому воно має грітися? та ще й так, що його температуру необхідно контролювати?

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

можливо краще, щоб весь конденсат зливався зразу в каналізацію через аварійний злив? одним елементом в системі буде менше

Неактивний

#12 2020-01-26 21:46:21

Жорж
Учасник
Зареєстрований: 2019-02-23
Повідомлень: 14

Re: Система управление резервом воды и котлом отопления в квартире.

щодо системи керування газовим котлом - невідомо який у вас котел, і як він управляється.  але скажу - справа дуже потрібна, сам би хотів щось таке мати, але мій котел таке не дозволяє. є задумка емулювати діяльність термопари котла, але такими речами балуватися не можна, і газовики не думаю що погладать по голові )))
із коду видно, що ви керуєте однією релюшкою. у вашого котла справлі є такий інтерфейс керування, чи ви просто керуєте  циркуляційним насосом?

Неактивний

#13 2020-01-26 22:14:50

Жорж
Учасник
Зареєстрований: 2019-02-23
Повідомлень: 14

Re: Система управление резервом воды и котлом отопления в квартире.

із плюшок, які б можна було реалізувати чисто програмним шляхом - індикація на планшеті і оповіщення на емейл про те, що в системі централізованого водопостачання вже довгий період не було води, і водозабор здійснюється тільки із баків. в такий момент якщо хтось вирішить прийняти ванну, то не факт що вистачить води )))
в нас відключають воду 1-2 рази на рік, і на сім'ю з 5 чоловік з двома ваннами і туалетами держу запас 300 л, на добу вистачає. можливо трохи в притик, але не треба постійно робити процедуру оновлення води у баках.

Неактивний

#14 2020-01-27 08:42:20

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Система управление резервом воды и котлом отопления в квартире.

Спасибо Вам за детальный подход и рассмотрение моего проекта.

Когда я искал датчик уровня, то тоже думал об использовании ультразвукового, но из-за того, что расстояние и между потолком ниши и баком было менее 50 см, то понял, что не получится из-за конструктивных особенностей.
Судя по виду готовых ультразвуковых датчиков https://aliexpress.ru/item/4000068282809.html?spm=a2g0o.detail.1000013.6.2fb875cbycinrY&gps-id=pcDetailBottomMoreThisSeller&scm=1007.14977.145068.0&scm_id=1007.14977.145068.0&scm-url=1007.14977.145068.0&pvid=a80f0c5b-18be-407f-85e9-93c45837f90d&_t=gps-id:pcDetailBottomMoreThisSeller,scm-url:1007.14977.145068.0,pvid:a80f0c5b-18be-407f-85e9-93c45837f90d думаю они не боятся влажности.

В то время, когда я дела первую систему управления я не знал о существовании ардуино вообще. Поэтому все городил на готовых датчиках, которые мог купить.
Первая версия управления баками показана на последних трех фото в первом сообщении.
Датчик и индикатор использовал этот https://aliexpress.ru/item/32789731231.html?spm=a2g0s.9042311.0.0.264d33edEgHuTd
Сам датчик https://aliexpress.ru/item/32916864489.html?spm=a2g0s.9042311.0.0.264d33edQfE9HX
Если есть необходимость просто в обнаружении воды на каких-то уровнях то можно использовать такие датчики, которые работают через пластиковые стенки https://aliexpress.ru/item/32953022835.html?spm=a2g0s.9042311.0.0.264d33edll7RLc.

Серийный индикатор показывает точность - один знак после запятой. Этой же точности я постарался добиться при разработке второй версии. Реально именно этот знак и "плавает". Сам датчик, как будто может измерять от 1 метра и чуть ли не до 500!!!

Аварийный перелив в канализацию достался "по наследству" и выполнен на 32 мм трубе с сифон - мягкая гофра. Трубу канализации 50 мм я сразу выводил в нишу где стоят баки, когда делал ремонт. В самый верх одного бака сбоку просто врезана сливная труба. Нормальный уровень воды всегда ниже. За многие годы использования баков была только одна проблема один раз - высохла вода в сифоне и появился запах. Сейчас, т.к. в эту же канализацию происходит слив конденсата, думаю осушения не будет. Один раз в старой системе отказал поплавковый клапан и вода тела в канализацию (не сразу услышали).

Как я писал ранее вторую версию системы строил на базе датчиков первой. Для измерения потока использовались эти датчики https://aliexpress.ru/item/32811943136.html?spm=a2g0s.9042311.0.0.264d33edZ68m0u . Они уже имели встроенный датчик температуры, от которого я решил не отказываться. Если зимой произошел обмен воды в баке, то температура воды на выходе показывает как в целом прогрелся бак. Иногда интересно посмотреть температуру на воды входе. На самом деле датчик температуры воды не очень нужен.

Датчики протечки размещены на "полу" ниши. Всего три датчика - два от системы в разных местах и один запасной типа этого как контрольный https://aliexpress.ru/item/32835205907.html?spm=a2g0s.9042311.0.0.264d33edPHmBvU

Неактивний

#15 2020-01-27 10:09:59

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Система управление резервом воды и котлом отопления в квартире.

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

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

На входе квартиры у меня установлен 100 мк фильтр. Может со временем и забьются датчики давления, но не думаю - время покажет. Мотор гидрафора не может перегреться. В нем установлена защита от сухого хода и он просто отключится, если из баков не будет поступать вода. Если вода есть, то гидрофор создаст давление около 4 бар и отключиться по давлению. В нем свой автомат.

Когда система создавалась на столе, то реально подключить нагрузку на все элементы у меня не было возможности. Читал на форумах, что реле может сильно греться и советуют даже ставить на дополнительный радиатор. Для подстраховки установил датчики температуры DS18В20. Программно это не сложно, а по затратам всего 17 грн. один датчик. Первое время даже строил график температуры, но потом отказался, т.к. действительно не перегревается. Это же касается и преобразователя напряжения для зарядки аккумулятора.

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

Неактивний

#16 2020-01-27 10:16:17

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Система управление резервом воды и котлом отопления в квартире.

Это второй котле, который установлен у меня. До этого был Юнкерс (проработал около 18 лет). Сейчас Saunier Duval. Также имел опыт работы с Аристоном. Все они имею возможность подключать комнатный  термостат. Для этого убирается перемычка и вместо не подключается реле термостата.
Установка комнатного термостата позволила существенно экономить потребление газа и влажность в комнате стало в пределах нормы.

Если у Вас не старое АОГВ, то думаю такая функция должна быть и у Вашего котла.
При включении термостата включается режим отопления. Температурой теплоносителя котле управляет сам. Также котел сам управляет циркулярным насосом. Режим приготовления горячей воды при этом не меняется.
По потребляемой мощности можно проследить режим работы котла: ожидание (4 Вт), подача теплоносителя (60 Вт), горелка включена (110 Вт), горячая вода (120 Вт).
Мой старый термостат был программируемый недельный, но в таком режиме я его практически не использовал. Если я уезжал на долго зимой в отпуск, то выставлял температуру в комнате на термостате 16 градусов. Расход газа меньше и цветам легче.
Немного передела дополнил интерфейс, добавив время работы котла в режиме отопления и ожидания. Также была добавлена кнопка включения котла.  Установлена температура 19,7 градуса дельта +/- 0,2.
Добавил еще один таймер - включение перед приходом с работы за 1 час и утром перед подъемом на работу и отключение  перед сном.
Этот таймер просто включает или отключает режим отопления в котле, а работа по температуре происходит независимо.
28ebf54820f1c8f5a4c606b9f379b113.jpg

Остання редакція NickVectra (2020-01-27 11:02:52)

Неактивний

#17 2020-01-27 10:43:48

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Система управление резервом воды и котлом отопления в квартире.

Режим оповещения (индикация и Email) у меня реализован для аварийной ситуации - перелив. Добавить индикацию и отправку по Email сообщения о низком уровне воды в течении заданного времени не сложно - спасибо подумаю. Просто пока такая задача у меня не стояла.

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

Надеюсь ничего не упустил и по возможности ответил на все Ваши вопросы.
Удачной Вам реализации задуманного.

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

Остання редакція NickVectra (2020-01-27 10:47:00)

Неактивний

#18 2020-01-30 00:38:02

Жорж
Учасник
Зареєстрований: 2019-02-23
Повідомлень: 14

Re: Система управление резервом воды и котлом отопления в квартире.

NickVectra пише:

Надеюсь ничего не упустил и по возможности ответил на все Ваши вопросы.

дякую, особливо за посилання на датчики. так здається на всі мої питання детально відповіли, буде над чим поміркувати )))

NickVectra пише:

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

не думав що все так просто, вважав що там якийсь "мудрий" протокол обміну, і кожної фірми свій )))
котел у мене на невбиваємій пасивній італійській автоматиці, працює вже під 20 років, і, ясно, можливості підключення стороннього термостату немає.
а у батьків є виносний датчик температури, причому безпроводний, що дуже зручно. так у них запрограмовано, щоб температура вночі падала до 16 градусів - приємно спати і серьозна економія газу.

NickVectra пише:

Один раз в старой системе отказал поплавковый клапан и вода тела в канализацию (не сразу услышали).

на такий випадок і планую класти перелив. так що ваша система прекрасно відпрацювала нештатну ситуацію.

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

так, у мене тиск із баку прекрасний щоб помити руки холодною водою, на теплу не вистачає, для пральної машини доречі також. але я вирішив підключити гідрофор на постійно - бо це питання комфорту: в нас багато точок водорозбору і багато користувачів, і всюди класти дистанційні кнопки не варіант. а так дуже зручно постійно мати в системі один тиск, наприклад, коли жінка миє посуду а хтось на іншому поверсі захоче прийняти душ, то будь-ласка, струмінь води  ні в кого не зміниться, навіть якщо хтось ще спустить унітаз чи помиє руки. я взяв автоматичну насосну станцію grundfos scala2, там використовується економний мотор на постійних магнітах і частотний перетворювач. станція підтримує постійний заданий тиск незалежно від кількості точок водозабора, що використовуються в даний момент (одночасно до 8-ми). частотний перетворювач автоматично регулює обороти двигуна і тиск зовсім не плаває, і завдяки цьому мотор дуже економний і тихий. якщо одночасно відкритий один кран, то бере десь 200 Вт. максимальна потужність 550 Вт.
спочатку я теж не планував тримати насосну станцію постійно включеною, і вмикати її по годинам чи по падінню тиску нижче 2-х барів. бо три рази в день в центральному водопостачанні є нормальний тиск під 3 бари. але коли повністю відкрити кран то тиск із 2,8 бара паде до 2-х, а якщо відкрити інший, то ще нижче. при включеній насосній станції ж тиск постійно тримається на відмітці 2,9 бар, як би сильно не включати крани. (насправді із збільшенням відкритих кранів тиск трохи паде до 2,8-2,7 бар) так що я вирішив не ускладнювати систему, і на даний момент єдиним активним "розумним" компонентом є сама насосна станція.

NickVectra пише:

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

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

Неактивний

#19 2020-01-30 02:17:24

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Система управление резервом воды и котлом отопления в квартире.

Жорж пише:

але я вирішив підключити гідрофор на постійно - бо це питання комфорту: в нас багато точок водорозбору і багато користувачів

У меня первоначально так и было - гидрофор был включён на постоянно, а вода набралась с водопровода в баки, но мой гидрофор был без гидроаккумулятора. Из-за  этого гидрофор часто срабатывает.
Сейчас я дополнительно установил гидроаккумулятор на 80 л.
Для Вышего случая я думаю гидроаккумулятора тоже нужен большой ёмкости, а не стандартный. Хотя с таким мотором как Вы описали он работает немного по-другому.


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

Остання редакція NickVectra (2020-01-30 19:27:48)

Неактивний

#20 2020-01-30 22:29:29

Жорж
Учасник
Зареєстрований: 2019-02-23
Повідомлень: 14

Re: Система управление резервом воды и котлом отопления в квартире.

NickVectra пише:

мой гидрофор был без гидроаккумулятора. Из-за  этого гидрофор часто срабатывает.
Сейчас я дополнительно установил гидроаккумулятор на 80 л.
Для Вышего случая я думаю гидроаккумулятора тоже нужен большой ёмкости, а не стандартный. Хотя с таким мотором как Вы описали он работает немного по-другому.

гідроакумулятор на 80л та ще й на двох чоловік це мегакомфортно для мотора. я теж розглядав варіант установки такого гідроакумулятора, але із наносною станцією scala 2 він як окремий елемент не потрібний. там є встроєний об'ємом всього 0,65 л. але постійний заданий тиск підтримується за допомогою інтелектуального регулювання продуктивності мотора в залежності від показів датчика тису.
правда мотор постійно включається по кожному чиху (відкриттю крана), але це його штатний режим роботи. більше того, після припинення водозабору він на долю секунди виключається і ще раз включається на 8 секунд для плавного добору тиску із 2,8-2,9 (що підтимується при включеному водозаборі) до 3,1 бар (при закритих кранах).
ну а з свого боку я постарався зробити "тепличні" умови для зменшення нагрузки на мотор: подача води під тиском (бак встановлено на підвищенні), подача і відведення води виконані 32-ю трубою. таким чином я сподіваюся зменшити електроспоживання насосною станцією і збільшити її довговічність. працює вона трохи голосніше старої морозилки, що стоїть поруч.

NickVectra пише:

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

мікроконтроллер для контроля температури буде використовуватися полюбе)))
бак досить добре утеплений, зараз у ньому 11 градусів тепла (на дворі трохи вище нуля), при -10 у баку було +6 градусів. причому у грудні при короткочасних 10-ти градусних морозах бак ще був не утеплений (тільки труби до нього) і не заморозився. при -15-ти замерзла вхідна труба, але через добу розмерзла. вихідна труба, що йде до мотору не замерзла напевно із-за своєї товщини DN 32 (вхідна DN15) і більшої швидкості руху води в ній. 
про пенопластовий домик теж думав, але очевидно, що слабим місцем є труби, вони проходять по відкритому простору метрів чотири. буду колхозити їх обігрів кабелем при температурах нижче 10 градусів або збільшувати шар їх утеплення.

NickVectra пише:

На входе квартиры у меня установлен 100 мк фильтр.

все забуваю спитати, а які фільтри встановлені у вас перед баком? у мене встановлено один 10-20 мікронний, але забивається досить швидко. міняю кожен місяць.

і ще, не могли б ви поділитися посиланням на кран, що управляється сервоприводом.

Остання редакція Жорж (2020-01-30 22:32:51)

Неактивний

#21 2020-01-30 23:52:35

NickVectra
Учасник
Зареєстрований: 2020-01-09
Повідомлень: 33

Re: Система управление резервом воды и котлом отопления в квартире.

Жорж пише:

все забуваю спитати, а які фільтри встановлені у вас перед баком? у мене встановлено один 10-20 мікронний, але забивається досить швидко. міняю кожен місяць.

і ще, не могли б ви поділитися посиланням на кран, що управляється сервоприводом.

Я использую 5 или 10 микрон. Моя первая модель фильтра обратного осмоса была построена самостоятельно на двух колбах. Так как они остались то их и поставил. Фильтры меняю не чаще чем раз в пол года. На фото видно первый фильтр грязный второй практически как новый.
По расчету если я использую баки раз в неделю, в месяц 4 раза, то за 6 месяце будет около 7200 л при ресурсе самого дешёвого 10000 л.

Эту модель крана https://aliexpress.ru/item/32825291411.html?spm=a2g0s.9042311.0.0.264d33ednLZtoD  в то время я выбрал т.к она имела возможность ручного управления и была на 220 В.
Потом из-за  такой конструкции долго пришлось ломать голову как сделать отслеживание за положением крана (см. Фото выше) на базе датчика Холла. Сейчас бы брал не на 220, а на более низкое напряжение. Ручным управлением сейчас не пользуюсь.

Неактивний

Швидке повідомлення

Введіть повідомлення і натисніть Надіслати

Підвал форуму