КОРЗИНА
магазина
8 (499) 500-14-56 | ПН. - ПТ. 12:00-18:00
ЛЕСНОРЯДСКИЙ ПЕРЕУЛОК, 18С2, БЦ "ДМ-ПРЕСС"

Автоматизированная гидропоника с автоматической корректировкой параметров.

Введение

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

Если вы впервые работаете с контроллером Piranha ESP-32, ознакомьтесь с нашей инструкцией. Также, если вы впервые работаете в среде Arduino IDE, вам поможет статья. Мы постарались описать процесс сборки максимально подробно и дать все необходимые ссылки, чтобы вы могли повторить данный проект, даже если у вас нет опыта программирования.

Видео

Редактируется...

Нам понадобится

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

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

Основной блок (на базе Piranha ESP-32)

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

Принцип работы

В основе установки - управляющий блок на основе Piranha ESP-32, к которому подключены датчики и исполнительные механизмы. Контроллер, благодаря встроенному модулю Wifi, подключается к интернету и получает от панели управления необходимые настройки и отправляет на неё показатели кислотности, концентрации, расхода электроэнергии, число прошедших дней с момента посадка.

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

(картинка кликабельна)

Блок-схема гидропонной установки

Измерение параметров питательно раствора производится каждые пол часа: pH в 0 минут каждого часа, и ppm в 30 минут каждого часа. При запуск измерения вручную через панель ioControl оба параметра измеряются одновременно. При отклонении параметров от заданного диапазона, включается автоматическая корректировка с помощью модулей долива.

Работа модулей долива

Модули долива могут работать как в режиме дозирования порции (по времени или по объёму жидкости), так и в режиме прямого долива (пока есть управляющий сигнал). Модуль начинает подачу жидкости, когда получает управляющий сигнал от главного контроллера, или при нажатии кнопки. 

  • Если кнопка нажата кратковременно, то происходит подача порции. Размер порции и способ её измерения задаётся в коде программы модуля долива. Например, можно установить время подачи 2 секунды, или, например, объём подачи 20мл. Задание объёма возможно в случае, если установлен расходомер. Соответственно, если вы не собираетесь использовать задание порции по расходу, расходомер можно не ставить.
  • Если кнопка удерживается, то происходит подача жидкости до момента, пока кнопка не будет отпущена. 

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

Схема управляющего контроллера

Перед сборкой установите модулям I2C необходимые адреса (раздел адресации устройств I2C)

(картинка кликабельна)

Схема управляющего блока

Обновлено 22.02.22

Перед сборкой настройте напряжение 5В на выходе понижающего преобразователя!

Обратите внимание, что устройства I2C могут подключаться к шине I2C в любые колодки, главное соблюдать полярность. Например, на схеме видим, что модуль реле подключен к модулю силовых ключей. Его можно было бы подключить и к I2C Hub'у, если бы так было удобнее.

Также возможно подключение щупа TDS-метра через реле (вместо двух реле для подключения самого модуля). 

Подробное описание подключения элементов

Понижающий преобразователь

Вывод модуляПодключается к...
IN++ 12В
IN-- 12В
OUT+Vcc (красная колодка) Piranha ESP-32
OUT-GND (черная колодка) Piranha ESP-32

RGB-светодиод

Вывод модуляВывод Piranha ESP-32
B1
GNDGND (черная колодка)
VVcc (красная колодка)
R13
G12

Pull Switch UP/DOWN (модуль подтяжки для уровнемеров)

Вывод модуляВывод Piranha ESP-32
114
GNDGND (черная колодка)
VVcc (красная колодка)
215
326

Нижний уровнемер (полярности нет)

Вывод модуляКолодка модуля подтяжки для уровнемеров
13
2G

Средний уровнемер (полярности нет)

Вывод модуляКолодка модуля подтяжки для уровнемеров
12
2G

Верхний уровнемер (полярности нет)

Вывод модуляКолодка модуля подтяжки для уровнемеров
11
2G

Модули долива (подключаются к колодке GVS)

МодульКолодка Piranha ESP-32
pH Down17
pH Up16
Концентрат А23
Концентрат В18
Вода19

Твердотельное реле

Вывод модуляВывод Piranha ESP-32
GGND (черная колодка)
VVcc (красная колодка)
S25

Циркуляционный насос подключается к сети 220В через твердотельное реле.

I2C Hub

ВыводВывод Piranha ESP-32
GNDGND
Vcc+5V
SCLSCL
SDASDA

pH-метр, модуль силовых ключей, модуль реле

Вывод модуляВывод I2C Hub
GNDGND
Vcc+5V
SCLSCL
SDASDA

TDS/EC-метр

Вывод модуляВывод I2C Hub или модуля реле
GNDNO канала 1 реле 1
VccNO канала 2 реле 1
SCLSCL  канала 1 реле 2
SDASDA канала 2 реле 2

Также на модуле реле 1 соединить:

GNDK1 (канал 1)
VccK2 (канал 2)

Также на модуле реле 2 соединить:

SCL
K1 (канал 1)
SDAK2 (канал 2)

Особенности схемы

1) TDS-метр, как и прочие устройства I2C, подключается к шине I2C. Однако, для устранения влияния pH-метра на работу TDS-метра, последний включен через реле. Во время измерения контакты реле замкнутся, на TDS-метр подастся напряжение и произойдет измерение.

Как мы говори, некоторые модули можно не ставить. Например, если вы используете только pH-метр или только TDS-метр, то модуль реле можно не ставить, а TDS-метр подключить напрямую к шине I2C (просто шлейфом к I2C Hub'у).

2) Обратите внимание, что на модуле подтяжки переключатель подтяжки находится в положении "UP". 

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

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

5) Также мы снимаем таймлапс на Raspberry Pi, поэтому дополнительно к выходу понижающего преобразователя 5В мы подключили еще два USB гнезда. Так можно удобно подключать устройства USB при необходимости.  

Установка библиотек

Скачайте и установите библиотеки, указанные ниже. Они понадобятся для установки I2C аресов модулей и для работы скетча. Вам может пригодиться инструкция по установке библиотек.

Перечень библиотек:

Адресация устройств I2C

Для устройств I2C перед началом сборки необходимо задать адреса:

МодульАдрес
Модуль силовых ключей0x09
pH-метр0x0A
TDS/EC-метр0x0B
Двухканальное реле (разрыв GND и Vcc TDS-метра)0x0C
Двухканальное реле (разрыв SCL и SDA TDS-метра)0x0D

Также у нас в скетче вы можете видеть ещё одно реле с адресом 0x0E. Оно необходимо для коммутации 220В освещения. Вы можете обойтись без него (редактировать код не нужно) или использовать его для включения ламп освещения.

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

1) Установите библиотеку iarduino_I2C_Address. Она необходима для установки адресов модулей серии I2C-flash.

2) Подключите модуль, у которого хотите изменить адрес, к имеющемуся у вас контроллеру. Если контроллер имеет колодку I2C, то подключаемся к ней напрямую. Если нет (на рисунке это Arduino Nano), то подключаемся к выводам A4 и A5 (см. рисунок).

(картинка кликабельна)

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

// ПРИМЕР СМЕНЫ АДРЕСА МОДУЛЯ I2C-FLASH:          // * Строки со звёздочкой являются необязательными
                                                  //
uint8_t newAddress = 0x09;                        //   Назначаемый модулю адрес (0x07 < адрес < 0x7F)
                                                  //
#include <Wire.h>                                 // * Подключаем библиотеку для работы с аппаратной шиной I2C
#include <iarduino_I2C_Address.h>                 //   Подключаем библиотеку для работы с адресами модулей линейки I2C-flash
iarduino_I2C_Address j;                           //   Объявляем объект j для работы с модулем I2C-flash. Адрес модуля будет определен автоматически
                                                  //   Если адрес модуля известен, то его можно указать при создании объекта, например, iarduino_I2C_Address j(0xA0);
void setup(){                                     //
     delay(500);                                  // * Ждём завершения переходных процессов, связанных с подачей питания
     Serial.begin(9600);                          //
     while(!Serial){;}                            // * Ждём завершения инициализации шины UART
     j.begin();                                   //   Инициируем работу с модулем
     while(!j){;}                                 // * Запрещаем дальнейшую работу, если модуль не инициализирован
     Serial.print("Найден модуль 0x");            //
     Serial.println( j, HEX );                    //   Выводим текущий адрес модуля
     j=newAddress;                                //   Меняем адрес модуля на newAddress
     if(j==newAddress){                           // * Проверяем новый адрес модуля
         Serial.println("Адрес модуля изменён");  // * Успех записи нового адреса можно проверить по результату присвоения: if( j=newAddress ){/*успешно*/;}else{/*провал*/;}
     }                                            // * 
     Serial.print("Текущий адрес модуля 0x");     //
     Serial.println( j, HEX );                    //   Выводим текущий адрес модуля
}                                                 //
                                                  //
void loop(){                                      //
}                                                 //

Если открыть монитор порта, то можно будет увидеть информационные сообщение о ходе процесса.

4) Установите необходимые адреса всем модулям I2C (таблица в начале раздела).

Создание и настройка панели ioControl

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

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

1) Выполните действия из раздела "Начало работы". Так вы поймёте принцип работы сервиса и подготовитесь к работе.

2) Создайте панель. Её название должно быть уникальным.

3) Теперь добавляем переменные. О том, как их создавать, подробно написано в разделе "Начало работы". Здесь мы приводим их названия и типы. Если вы хотите задать переменным и другие имена, необходимо будет изменить их название и в скетче.

Название переменнойТип переменнойВид карточки
timeLedOnЦелочисленнаяВвод/вывод значения
timeLedOffЦелочисленнаяВвод/вывод значения
alertModeТекстоваяВывод значения
switchЦелочисленнаяКнопка
totalPowerС плавающей точкойВывод значения
measureModeЦелочисленнаяКнопка
sensorPHС плавающей точкойВывод значения
sensorTDSС плавающей точкойВывод значения
setPhС плавающей точкойВвод/вывод значения
setTDSС плавающей точкойВвод/вывод значения

Отлично, панель создана!

Скетч и настройки управляющего контроллера (Piranha ESP-32)

Если вы впервые работаете с контроллером Piranha ESP-32, ознакомьтесь с нашей инструкцией

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

Желательно отключить все модули от контроллера во время прошивки. Подключённые к некоторым пинам модули могут сделать загрузку кода в контроллер невозможной (из-а подтяжки выводов контроллера шинам питания).

Также напоминаем про необходимость предварительной установки адресов I2C-модулей. 

Настройки

Рассмотрим основные настройки, которые требуют комментария:

  • io_timeLedOn - время включения освещения. Этот параметр также можно будет изменить из панели облачного сервиса. 
  • io_timeLedOff - время выключения освещения. Аналогичен предыдущему. 
  • nightMode - ночной режим: активен или нет. Если режим активен, что в ночное время (интервал времени, когда свет выключен) нормализация параметров не будет производиться. Актуально для домашней установки, чтобы ночью не включались насосы модулей долива.
  • delayTime - время задержки перед началом измерения параметров питательного раствора. Измерение должно производиться на неподвижной жидкости, поэтому на время измерения циркуляционный насос выключается. delayTime - это время выдержки паузы после выключения насоса, чтобы жидкость в системе успела остановиться.
  • moduleMixInterval - интервал времени между добавлениями компонентов концентрата. Выдержка необходима, чтобы первый добавленный компонент успел распределиться в системе и концентраты не смешались друг с другом.
  • minPh, maxPh - минимальное и максимальное значение pH, при которых будет включаться автокорректировка. По сути, это диапазон, в рамках которого следует поддерживать кислотность.
  • minTDS, maxTDS - аналогично предыдущему, это значение ppm (концентрация).
  • limitValuePhLow, limitValuePhHigh - граничные (аварийные) значения кислотности, при получении которых будем считать, что произошла ошибка и данные не корректны, соответственно нормализовывать раствор не будем.
  • limitValueTDSLow, limitValueTDSHigh - аналогично предыдущему, значения концентрации.

    Не забудьте указать необходимую информацию для подключения к Wifi:
const char* ssid = "iarduino_2.4G";               // SSID (название точки доступа)
const char* password = "sxz32ds2";                // Пароль

Скетч для управляющего контроллера (Piranha ESP-32)

Обновлено 22.02.2022

#include <iocontrol.h>                            // Подключаем библиотека для работы с сервисом IoT iocontrol.ru
#include <WiFi.h>                                 // Библиотека для работы с Wifi
#include <Wire.h>                                 // Подключаем библиотеку для работы с аппаратной шиной I2C.
#include <iarduino_I2C_Relay.h>                   // Подключаем библиотеку для работы с реле и силовыми ключами.
#include <iarduino_I2C_TDS.h>                     // Подключаем библиотеку для работы с модулем.
#include <iarduino_I2C_pH.h>                      // Подключаем библиотеку   для работы с pH-метром I2C-flash.
 
iarduino_I2C_Relay pwrkey(0x09);                  // Объявляем объект pwrkey для работы с функциями и методами библиотеки iarduino_I2C_Relay, указывая адрес модуля на шине I2C.
iarduino_I2C_pH sensorPH(0x0A);                   // Объявляем объект sensor для работы с функциями и методами библиотеки iarduino_I2C_pH, указывая адрес модуля на шине I2C.
iarduino_I2C_TDS tds(0x0B);                       // Объявляем объект tds    для работы с функциями и методами библиотеки iarduino_I2C_TDS, указывая адрес модуля на шине I2C.   
iarduino_I2C_Relay relay(0x0C);                   // Объявляем объект relay  для работы с функциями и методами библиотеки iarduino_I2C_Relay, указывая адрес модуля на шине I2C.        
iarduino_I2C_Relay relay_2(0x0D);                 // Второе реле разрывателя для TDS-метра
iarduino_I2C_Relay relayLamp(0x0E);               // (доп. реле для ламп освещения 220В) Объявляем объект relayLamp  для работы с функциями и методами библиотеки iarduino_I2C_Relay, указывая адрес модуля на шине I2C.        
 
////-------------ПОЛЬЗОВАТЕЛЬСКИЕ НАСТРОЙКИ-------------////
long io_timeLedOn = 7;                            // Время включения освещения (позже можно будет изменить на панели io_Control)
long io_timeLedOff = 23;                          // Время выключения освещения (позже можно будет изменить на панели io_Control)
#define nightMode 1                               // Ночной режим: 1 - когда свет не горит, не нормализовывать параметры, 0 - всегда нормализовывать. Режим полезен, если установка находится дома: насосы не будут включаться ночью
const float ledPower = 45.0;                      // Данные для статистики: Мощность в Вт для освещения
const float pompPower = 11.0;                     // Данные для статистики: Мощность в Вт для насоса
const long  gmtOffset_sec = 3600 * 3;             // Смещение часового пояса (для Москвы +3)
const int   daylightOffset_sec = 0;               // Переменная определяет число секунд для перехода на летнее время. Для возможности перехода значение должно быть 3600 (1 час)
const uint32_t delayTime = 3;                     // Время паузы перед началом измерения после выключения насоса, сек. Необходимо для того, чтобы измерение Ph и TDS производилось на неподвижной жидкости
#define moduleMixInterval 5                       // Интервал между добавлениями компонентов концентрата, минут
                                                  
const uint8_t variationPh = 8;                    // Отклонение pH, %, от среднего значения, при котором корректировка раствора не будет производиться (считается нормой)
const uint8_t variationTDS = 17;                  // Отклонение TDS, %, от среднего значения, при котором корректировка раствора не будет производиться (считается нормой)
float io_middlePh = 6.0;                          // Среднее значение pH при отклонении +-variationPh включится долив компонентов (позже можно будет изменить на панели io_Control)
float io_middleTDS = 1000.0;                      // Среднее значение TDS при отклонении +-variationTDS включится долив компонентов (позже можно будет изменить на панели io_Control)
                                                  
const float limitValuePhLow = 3.0;                // Значение Ph, ниже которого автоматическая корректировка не будет производиться. Резкое падение вероятнее всего связано с ошибкой работы.
const float limitValuePhHigh = 8.0;               // Значение Ph, выше которого автоматическая корректировка не будет производиться. Резкий рост вероятнее всего связано с ошибкой работы.
const float limitValueTDSLow = 10.0;              // Значение TDS, ниже которого автоматическая корректировка не будет производиться. Резкое падение вероятнее всего связано с ошибкой работы.
const float limitValueTDSHigh = 2000.0;           // Значение TDS, выше которого автоматическая корректировка не будет производиться. Резкий рост вероятнее всего связано с ошибкой работы.
                                                  
                                                  // Данные для подключения к Wifi:
const char* ssid = "iarduino_2.4G";               // SSID (название точки доступа)
const char* password = "sxz32ds2";                // Пароль
const char* myPanelName = "hydroponic";           // Название панели на сайте iocontrol.ru
const char* key = "********************";         // Ключ доступа к панели
////---------------------------------------------------////
                                                  // ПИНЫ ДЛЯ ПОДКЛЮЧЕНИЯ МОДУЛЕЙ
#define pompPin 25                                // Циркуляционный насос (модуль твердотельное реле)
uint8_t moduleWater = 19;                         // Модуль долива чистой воды
uint8_t moduleMix_1 = 18;                         // Модуль долива состава 1
uint8_t moduleMix_2 = 23;                         // Модуль долива состава 2
uint8_t modulePh_UP = 16;                         // Модуль долива Ph UP
uint8_t modulePh_Down = 17;                       // Модуль долива Ph DOWN
#define levelLowPin 26                            // Нижний датчик уровня 
#define levelMidPin 15                            // Средний датчик уровня
#define levelHighPin 14                           // Верхний датчик уровня
#define redPin 13                                 // RGB-светодиод индикатор, красный
#define greenPin 12                               // RGB-светодиод индикатор, зеленый
#define bluePin 5                                 // RGB-светодиод индикатор, синий
 
//////////////////////////////////////////////////////
                                                  // Вводим переменные 
bool ledState = 0;                                // Состояние подсветки (вкл/выкл)
bool pompState = 0;                               // Состояние насоса (вкл/выкл)
bool alertState = 0;                              // Состояние красного светодиода индикации
bool levelLowState, levelMidState, levelHighState;// Состояние уровнемеров: нижнего, среднего, верхнего
bool lastMeasureMode  = false;                    // Переменная процесса измерения параметров воды
uint8_t measureSign = 0;                          // Признак измерения, указывающий, измерение чего будет производиться
unsigned long timeUpdate=0, timeStartMeasure=0;   // Переменная обновления времени и переменная времени начала измерения
uint8_t alertMode = 0;                            // Режим мигания индикаторного светодиода
bool dayChangeFlag = false;                       // Флаг изменения дня (меняется в полночь)
uint8_t lastMin;                                  // Предыдущая минута
float sensorPhValue;                              // Значение с датчика Ph
float sensorTDSValue;                             // Значения с датчика TDS
uint32_t alertOnTime;                             // Переменная для мигания светодиодом-индикатором
bool firstMeasureFlag = true;                     // Флаг первого измерения (необходим для ситуации, когда пауза перед замером меньше минуты)
bool moduleMixFlag = false;                       // Флаг необходимости долива второго компонента концентрата (для избежания смешения концентратов при их одновременном добавлении)
uint32_t moduleMixTime;                           // Время залива первого компонента концентрата
bool dryMode = false;                             // Режим показывает, что долив воды производится из-за низкого уровня
bool moduleWaterState = false;                    // Вводим переменную состояния насоса долива воды
uint8_t days = 0;                                 // Переменная, хранящая число дней, прошедших с момента посадки
                                                 
                                                  // Переменные на панели iocontrol
int status;                                       // Переменная чтения значений переменных из сервиса iocontrol.ru
long io_switch;                                   // Выключатель системы на панели ioControl
float io_totalPower;                              // Переменная, хранящая количество израсходованной электроэнергии на панели ioControl
long io_measureMode;                              // Режим измерения на панели ioControl
                                                  // Названия переменной на сайте iocontrol.ru:
const char* VarName_timeLedOn = "timeLedOn";      // Время включения освещения
const char* VarName_timeLedOff = "timeLedOff";    // Время выключения освещения
const char* VarName_alertMode = "alertMode";      // Уровень воды
const char* VarName_switch = "switch";            // Включение/выключение всей системы
const char* VarName_totalPower = "totalPower";    // Суммарная потреблённая мощность
const char* VarName_measureMode = "measureMode";  // Режим измерения
const char* VarName_sensorPH = "sensorPH";        // Показания с датчика кислотности (Ph)
const char* VarName_sensorTDS = "sensorTDS";      // Показания с солемера (TDS)
const char* VarName_setPh = "setPh";              // Значение поддерживаемого pH
const char* VarName_setTDS = "setTDS";            // Значение поддерживаемого TDS
 
 
const char* ntpServer = "pool.ntp.org";           // Сервер времени
struct tm timeinfo;                               // Создаём структуру времени (содержит время и дату, разбитую на компоненты)
 
WiFiClient client;                                // Создаём объект клиента класса EthernetClient
iocontrol mypanel(myPanelName, key, client);      // Создаём объект iocontrol, передавая в конструктор название панели и клиента
 
void setup()
{                                                 
  delay(500);                                     // Задержка для завершения переходных процессов
                                                  // Конфигурируем выводы
  pinMode(pompPin, OUTPUT);                       // Твердотельное реле (НАСОС) - выход
  pinMode(redPin, OUTPUT);                        // Индикаторный СВЕТОДИОД - выход
  pinMode(greenPin, OUTPUT);                      // Индикаторный СВЕТОДИОД - выход
  pinMode(bluePin, OUTPUT);                       // Индикаторный СВЕТОДИОД - выход
  pinMode(levelLowPin, INPUT_PULLUP);             // Нижний уровнемер - вход, включена подтяжка к плюсу
  pinMode(levelMidPin, INPUT_PULLUP);             // Средний уровнемер - вход, включена подтяжка к плюсу
  pinMode(levelHighPin, INPUT_PULLUP);            // Верхний уровнемер - вход, включена подтяжка к плюсу
  pinMode(moduleWater, OUTPUT);                   // Сигнал на модуль чистой воды - выход
  pinMode(moduleMix_1, OUTPUT);                   // Сигнал на модуль питательного состава 1 - выход
  pinMode(moduleMix_2, OUTPUT);                   // Сигнал на модуль питательного состава 2 - выход
  pinMode(modulePh_UP, OUTPUT);                   // Сигнал на модуль повышения PH- выход
  pinMode(modulePh_Down, OUTPUT);                 // Сигнал на модуль понижения Ph - выход
                                                  
  digitalWrite(moduleWater, LOW);                 // Убираем сигналы со всех пинов модулей
  digitalWrite(moduleMix_1, LOW);
  digitalWrite(moduleMix_2, LOW);
  digitalWrite(modulePh_UP, LOW);
  digitalWrite(modulePh_Down, LOW);
 
  Serial.begin(115200);                           // Инициируем последовательный порт
  if (!pwrkey.begin()){                           // Инициируем работу с модулем силовых ключей
    Serial.println("ОШИБКА ИНИЦИАЛИЗАЦИИ модуля силовых ключей. Проверьте его адрес на шине I2C, а также убедитесь в правильности его подключения."); // Инициируем датчик работу с датчиком Ph
  }
  if (!relay.begin()){                            // Инициируем работу с модулем силовых ключей
    Serial.println("ОШИБКА ИНИЦИАЛИЗАЦИИ модуля реле. Проверьте его адрес на шине I2C, а также убедитесь в правильности его подключения."); // Инициируем датчик работу с датчиком Ph
  }
  if (!relay_2.begin()){                            // Инициируем работу с модулем силовых ключей
    Serial.println("ОШИБКА ИНИЦИАЛИЗАЦИИ модуля реле. Проверьте его адрес на шине I2C, а также убедитесь в правильности его подключения."); // Инициируем датчик работу с датчиком Ph
  }
  if (!relayLamp.begin()){                        // Инициируем работу с модулем силовых ключей
    Serial.println("ОШИБКА ИНИЦИАЛИЗАЦИИ модуля реле ламп. Проверьте его адрес на шине I2C, а также убедитесь в правильности его подключения."); // Инициируем датчик работу с датчиком Ph
  }
  if (!sensorPH.begin()){                         // Инициируем работу с датчиком Ph
    Serial.println("ОШИБКА ИНИЦИАЛИЗАЦИИ Ph-метра. Проверьте его адрес на шине I2C, а также убедитесь в правильности его подключения."); // Инициируем датчик работу с датчиком Ph
  }
  delay(500);
  pwrkey.digitalWrite(ALL_CHANNEL, LOW);          // Выключаем все каналы модуля силовых ключей
  relayLamp.digitalWrite(ALL_CHANNEL, LOW);       // Выключаем все каналы модуля силовых ключей
  
                                                  // ПОДКЛЮЧЕНИЕ К Wifi
  Serial.printf("Connecting to %s ", ssid);       // Выводим в монитор порта данные о подключении к сети
  WiFi.begin(ssid, password);                     // Подключение к Wifi
  while (WiFi.status() != WL_CONNECTED) {         // Ждём подключения
      delay(500);                                 // Задержка
      Serial.print(".");                          // Продолжаем выводить точку пока идёт подключение
  }
  Serial.println(" CONNECTED");                   // Выводим сообщение в монитор порта
  mypanel.begin();                                // Вызываем функцию первого запроса к сервису iocontrol
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // Конфигурируем время с заранее заданными настройками
  printLocalTime();                               // Вызываем функцию для получения времени и записи его в монитор порта
}
 
void loop()
{
  
  if (timeUpdate + 1000 < millis()){                            // Если прошла секунда
    timeUpdate = millis();                                      // Обновляем время начала выполнения этого блока кода
    printLocalTime();                                           // Вызываем функцию получения текущего времени
 
    status = mypanel.readUpdate();                              // Чтение значений переменных из сервиса
    if (status == OK && mypanel.readFloat(VarName_setPh)) {     // Если статус равен константе OK и на панели записано значение pH, т.е. панель не пустая
                                                                // Записываем считанные из сервиса значения в переменные
      io_timeLedOn = mypanel.readInt(VarName_timeLedOn);        // Считываем время включения
      io_timeLedOff = mypanel.readInt(VarName_timeLedOff);      // Считываем время выключения
      io_switch = mypanel.readInt(VarName_switch);              // Считываем состояние выключателя установки
      io_totalPower = mypanel.readFloat(VarName_totalPower);    // Считываем значение потреблённой мощности
      io_measureMode = mypanel.readInt(VarName_measureMode);    // Считываем значение режима измерения
      io_middlePh = mypanel.readFloat(VarName_setPh);           // Считываем значение среднего уровня pH
      io_middleTDS = mypanel.readFloat(VarName_setTDS);         // Считываем значение среднего уровня TDS
    }
 
    if (timeinfo.tm_hour == 0 && timeinfo.tm_min == 0 && dayChangeFlag == false){  // Прибавление дней после посадки в полночь
      days += 1;                                                // Увеличиваем день
      dayChangeFlag = true;                                     // Устанавливаем флаг, чтобы не увеличивать число дней в новом цикле
    }
    if (timeinfo.tm_hour >= 1) dayChangeFlag = false;           // В следующий час сбрасываем флаг увеличения дня
     
    if (io_switch){                                             // Если система включена
                                                                // ОБРАБОТКА УРОВНЕМЕРОВ
      levelLowState = digitalRead(levelLowPin);                 // Считываем значение нижнего уровнемера
      levelMidState = digitalRead(levelMidPin);                 // Считываем значение среднего уровнемера
      levelHighState = digitalRead(levelHighPin);               // Считываем значение верхнего уровнемера
                                                                // ИЗМЕРЕНИЕ ПАРАМЕТРОВ ЖИДКОСТИ
      if (!lastMeasureMode && !levelLowState){                  // Если измерение не производится и уровень воды выше минимального
        if (io_measureMode != 0){                               // Измерение Ph и TDS если нажата кнопка измерения на панели
          measureSign = 2;                                      // Признак измерения 2 - измерение вызвано вручную
          io_measureMode = 1;                                   // Записываем, что режим измерения активен
          lastMeasureMode = true;                               // Начинаем измерение
          Serial.println("START MEASURE  ");                    // Выводим сообщение о начале измерения в монитор порта
          timeStartMeasure = millis();                          // Записываем в переменную время начала измерения
          pompState = 0;                                        // Выключаема насос, чтобы измерение производилось на неподвижной жидкости
        }
        else if (timeinfo.tm_min == 0 && firstMeasureFlag){     // Измерение параметра Ph каждый час в 00 минут
          firstMeasureFlag = false;                             // Сбрасываем флаг
          measureSign = 0;                                      // Признак измерения 0 - сработал таймер на измерения Ph
          io_measureMode = 1;                                   // Записываем, что режим измерения активен
          lastMeasureMode = true;                               // Начинаем измерение
          Serial.println("START MEASURE Ph  ");                 // Выводим сообщение о начале измерения в монитор порта
          timeStartMeasure = millis();                          // Записываем в переменную время начала измерения
          pompState = 0;                                        // Выключаема насос, чтобы измерение производилось на неподвижной жидкости
        }
        else if (timeinfo.tm_min == 30 && firstMeasureFlag){    // Измерение параметра TDS каждый час в 30 минут
          firstMeasureFlag = false;                             // Сбрасываем флаг
          measureSign = 1;                                      // Признак измерения 1 - сработал таймер на измерения TDS
          io_measureMode = 1;                                   // Записываем, что режим измерения активен
          lastMeasureMode = true;                               // Начинаем измерение
          Serial.println("START MEASURE TDS  ");                // Выводим сообщение о начале измерения в монитор порта
          timeStartMeasure = millis();                          // Записываем в переменную время начала измерения
          pompState = 0;                                        // Выключаема насос, чтобы измерение производилось на неподвижной жидкости
        }
      if(timeinfo.tm_min != 0 && timeinfo.tm_min != 30) firstMeasureFlag = true; // Восстанавливаем флаг когда прошла минута начала замера
    }
    if (io_measureMode && ((timeStartMeasure + delayTime*1000) < millis())){ // Если в данный момент проводится подготовка к измерению и прошло время зажержки
      if (measureSign == 0){                                    // Если должно произойти измерение Ph
        measurePH();                                            // Вызываем функцию измерения кислотности
      }
      if (measureSign == 1){                                    // Если должно произойти измерение TDS
        measureTDS();                                           // Вызываем функцию измерения электропроводности (кол-ва солей)  
      }
      if (measureSign == 2){                                    // Если измерение было вызвано вручную, вызываем обе функции
         measurePH();                                           // Вызываем функцию измерения кислотности
         measureTDS();                                          // Вызываем функцию измерения электропроводности (кол-ва солей)  
      }
      io_measureMode = 0;                                       // Записываем, что режим измерения не активен
      Serial.println("MEASURE FINISHED ");                      // Выводим сообщение об окончании измерения в монитор порта
    }
 
    if (moduleMixFlag && (moduleMixTime + moduleMixInterval*60000 <= millis())) { // Если нужно добавить второй компонент концентрата и прошло заданное время после замера
      uniCorrect(moduleMix_2);                                  // Добавляем второй компонент концентрата
      moduleMixFlag = false;                                    // Возвращаем значение флага
    }
    if (io_measureMode) alertMode = 3;                          // Если производится изменение показателей воды, устанавливаем режим индикации "3"
    else {
      if (!levelLowState && levelMidState) {                    // Если нижний уровнемер сработал (замкнулся), а средний - нет (разомкнулся), значит уровень недостаточный
        alertMode = 0;                                          // Включаем индикацию режима "0"
        pompState = 1;                                          // Насос (циркуляционный) включен
        if (levelHighState) moduleWaterState = true;            // Включаем долив воды до момента, пока уровень не поднимется (на всякий случай убеждаемся, что уровень не выше верхнего уровнемера, т.е. уровнемеры работают исправно)
        dryMode = true;                                         // Вводим флаг, чтобы знать, что долив воды производится из-за низкого уровня
      }
      else if (!levelLowState && !levelMidState) {              // Если нижний уровнемер сработал (замкнулся),средний уровнемер сработал (замкнулся), значит уровень нормальный,
        alertMode = 0;                                          // Включаем индикацию режима "0"
        pompState = 1;                                          // Насос (циркуляционный) включен
      }
      if (!levelHighState){                                     // Если сработал (замкнулся) верхний уровнемер, уровень высокий
        alertMode = 1;                                          // Включаем индикацию режима "2"
        pompState = 1;                                          // Насос (циркуляционный) включен
        moduleWaterState = false;                               // Выключаем долив воды, если по какой-то причине он включен
      }
      if (levelLowState) {                                      // Если нижний уровнемер true, значит уровень низкий,
        alertMode = 2;                                          // Включаем индикацию режима "2"
        pompState = 0;                                          // Выключаем насос (циркуляционный)
      }
    }
    if (!levelMidState && dryMode) {                            // Если сработал средний уровнемер и флаг долива из-за низкого уровня
      dryMode = false;                                          // Сбрасываем флаг
      moduleWaterState = false;
    }
 
    if (WiFi.status() != WL_CONNECTED)  {                       // Проверяем, что Wifi подключен. Если нет, то
      WiFi.begin(ssid, password);                               // Вызываем функцию для повторного подключения 
      delay(5000);                                              // Задержка 5 секунд
    }
                                                                // ПОДСЧЁТ ПОТРЕБЛЁННОЙ МОЩНОСТИ
    if ((int)timeinfo.tm_min != lastMin){                       // Если прошла одна минута
      lastMin = (int)timeinfo.tm_min;                           // Перезаписываем значение предыдущеё минуты
      if (ledState){                                            // Если свет включен
        io_totalPower += (float)ledPower/60000.0;               // Увеличиваем значение суммарной мощности в пересчёте потреблённой электроэнергии за 1 минуту
      }
      if (pompState){                                           // Если включен насос
        io_totalPower += (float)pompPower/60000.0;              // Увеличиваем значение суммарной мощности в пересчёте потреблённой электроэнергии за 1 минуту
      }
    }
                                                                // УПРАВЛЕНИЕ ОСВЕЩЕНИЕМ
    if (io_timeLedOn < io_timeLedOff){                          // Если заданное время включения освещения меньше времени выключения (нет перехода через полночь)
        if ((int)timeinfo.tm_hour >= (int)io_timeLedOn && (int)timeinfo.tm_hour < (int)io_timeLedOff) ledState = 1; // Если пришло время включить свет, записываем в переменную 1
        else ledState = 0;                                      // Если пришло время выключить свет, записываем в переменную 0
      }
      else {                                                    // Если при задании времени включения освещения есть переход через полночь
        if ((int)timeinfo.tm_hour >= (int)io_timeLedOn || (int)timeinfo.tm_hour < (int)io_timeLedOff) ledState = 1; // Если пришло время включить свет, записываем в переменную 1
        else ledState = 0;                                      // Если пришло время выключить свет, записываем в переменную 0
      }
    }                                                           
                                                                
    else {                                                      // ИНАЧЕ, ЕСЛИ СИСТЕМА ВЫКЛЮЧЕНА ПРОГРАММНО
      ledState = 0;                                             // Выключаем освещение
      pompState = 0;                                            // Выключаем циркуляционный насос,
      alertMode = 4;                                            // Включаем индикацию режима "4"     
      moduleWaterState = false;                                 // Выключаем долив воды
      digitalWrite (moduleMix_1, LOW);                          // Выключаем все модули долива
      digitalWrite (moduleMix_2, LOW);
      digitalWrite (modulePh_UP, LOW);
      digitalWrite (modulePh_Down, LOW);
    }
                                                                // ЗАПИСЬ ЗНАЧЕНИЙ ДЛЯ ОТПРАВКИ НА СЕРВИС
    String message;                                             // Вводим переменную, в которую запишем значение в зависимости от состояния системы:
    switch(alertMode){
      case 0: message = (String)"OK_day_" + days; break;
      case 1: message = (String)"The_level_is_MAX"; break;
      case 2: message = (String)"The_pump_is_OFF"; break;
      case 3: message = (String)"Measure"; break;
      default: message = (String)"OFF"; break;
    }
    mypanel.write(VarName_alertMode, message);
    mypanel.write(VarName_totalPower, io_totalPower);           // Записываем значение суммарной потреблённой мощности 
    mypanel.write(VarName_measureMode, io_measureMode);         // Записываем значение выключателя режима измерения
    if (sensorPhValue) mypanel.write(VarName_sensorPH, sensorPhValue);    // Записываем значение датчика Ph если значение не равно 0
    if (sensorTDSValue) mypanel.write(VarName_sensorTDS, sensorTDSValue); // Записали значение солемера, если значение не равно 0
    mypanel.write(VarName_setPh, io_middlePh);                  // Записываем значение среднего значения pH
    mypanel.write(VarName_setTDS, io_middleTDS);                // Записываем значение среднего значения pH
    status = mypanel.writeUpdate();                             // Отправляем переменные из контроллера в сервис
    if (status == OK) {                                         // Если статус равен константе OK...
      if (!io_measureMode) lastMeasureMode = false;             // Если не производится процесс измерения, останавливаем измерение
    }
  }
 
  pwrkey.digitalWrite(ALL_CHANNEL, ledState);                   // Устанавливаем всем каналам силового ключа значение (включить или выключить свет)                                                        
  relayLamp.digitalWrite(ALL_CHANNEL, ledState);                // Устанавливаем всем каналам силового ключа значение (включить или выключить свет)                                                        
  digitalWrite(pompPin, pompState);                             // Включаем или выключаем циркуляционный насос
  if (!nightMode || (nightMode && ledState)) digitalWrite(moduleWater, moduleWaterState); // Записываем сигнал на долив воды в случае, если ночной режим выключен, или включен и сейчас день
  alertBlink();                                                 // Передаём в функцию управления светодиодом-индикатором, текущее состояние
}
 
void printLocalTime(){                                          // Функция получения текущего времени
  if(!getLocalTime(&timeinfo)){                                 // Если не удалось получить время
    Serial.println("ОШИБКА: НЕ УДАЛОСЬ ПОЛУЧИТЬ ВРЕМЯ.");       // Выводим в монитор порта сообщение об ошибке
  }
  else {
    Serial.println(&timeinfo, "%H:%M:%S");                      // Иначе записываем в последовательный порт значения времени из структуры
    //Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");       // Второй (расширенный) вариант записи времени
  }
}
 
void alertBlink(){                                              // Функция мигания светодиодом-индикатором
  if (alertMode == 0){                                          // Если в функцию передан 0
    digitalWrite(greenPin, HIGH);                               // Зеленый горит
    digitalWrite(redPin, LOW);                                  // Красный не горит
    digitalWrite(bluePin, LOW);                                 // Синий не горит
  }
  else if (alertMode == 1){                                     // Если в функцию передана 1 
    digitalWrite(greenPin, LOW);                                // Зеленый не горит
    if (alertOnTime + 150 < millis()){                          // Красный и синий мигают:
      alertOnTime = millis();                                   // Записываем время включения
      alertState  = !alertState;                                // Инвертируем значение
    }
    digitalWrite(bluePin, alertState);                          // Записываем состояние
    digitalWrite(redPin, !alertState);                          // Записываем состояние
  }
  else if (alertMode == 2){                                     // Если в функцию передана 2
    digitalWrite(bluePin, LOW);                                 // Синий не горит
    digitalWrite(greenPin, LOW);                                // Зеленый не горит
    if (alertOnTime + 150 < millis()){                          // Красный мигает
      alertOnTime = millis();                                   
      alertState  = !alertState;
    }
    digitalWrite(redPin, alertState);                           // Записываем состояние
  } 
  else if (alertMode == 3){                                     // Если в функцию передана 3
    digitalWrite(greenPin, LOW);                                // Зеленый не горит
    digitalWrite(redPin, LOW);                                  // Красный не горит
    if (alertOnTime + 500 < millis()){                          // Синий мигает
      alertOnTime = millis();
      alertState  = !alertState;
    }
    digitalWrite(bluePin, alertState);                          // Записываем состояние
  } 
  else if (alertMode == 4){                                     // Если в функцию передана 4
    digitalWrite(greenPin, LOW);                                // Зеленый не горит
    digitalWrite(redPin, LOW);                                  // Красный не горит
    digitalWrite(bluePin, HIGH);                                // Синий горит
  }
}
 
void measurePH(){                                               // Функция измерения кислотности (Ph)
  sensorPH.begin();                                             // Инициируем работу с pH-метром (уже выполняли в setup, не обязательно)
  delay(10000);                                                 // Задержка для завершения переходных процессов. Можно уменьшить до нескольких секунд.
  sensorPhValue = sensorPH.getPH();
  Serial.println((String)"sensorPH: "+sensorPhValue);           // Выводим считанное значение в монитор порта
  if (sensorPhValue <= limitValuePhLow) Serial.println("Ph ниже установленной границы. Автокорректировка кислотности не произведётся. Проверьте работу системы или отрегулируйте кислотность вручную.");
  else if (sensorPhValue >= limitValuePhHigh) Serial.println("Ph выше установленной границы. Автокорректировка кислотности не произведётся. Проверьте работу системы или отрегулируйте кислотность вручную.");
  else if (!nightMode || (nightMode && ledState)) correctPh();  // Запускаем функцию регулировки Ph, если ночной режим выключен, или если он включен и сейчас день
}
 
void measureTDS(){                                              // Функция солемера
  relay.digitalWrite(1, 1);                                     // Включаем первое реле (тем самым подаём питание на солемер)
  delay(200);                                                   // Задержка для стабилизации работы между включениями реле
  relay.digitalWrite(2, 1);                                     // Включаем второе реле (тем самым подаём питание на солемер)
  delay(200);                                                   // Задержка для завершения переходных процессов, связанных с переключением реле
  relay_2.digitalWrite(1, 1);                                     // Включаем первое реле (тем самым подаём питание на солемер)
  delay(200);                                                   // Задержка для стабилизации работы между включениями реле
  relay_2.digitalWrite(2, 1);                                     // Включаем первое реле (тем самым подаём питание на солемер)
  delay(500);                                                   // Задержка для стабилизации работы между включениями реле
  for (uint8_t i = 0; i < 3; i++){                              // Проводим измерение несколько раз, если с первого раза не получим корректный результат
    tds.begin();                                                // Инициируем работу с TDS/EC-метром I2C-flash и если успешно
    delay(10000);                                               // Задержка для завершения переходных процессов. Можно уменьшить до нескольких секунд. 
    tds.set_t(22.0);                                            // Указываем текущую температуру жидкости (указываем фиксированную, т.к. отклонения  температуры в нашей системе не велики)
    sensorTDSValue = tds.getTDS();                              // Производим измерение
    if (sensorTDSValue) break;                                  // Если получили отличное от 0 значение, заканчиваем измерение
  }
  if (!sensorTDSValue) Serial.print("Солемер показывает 0. Проверьте его адрес на шине I2C, а также убедитесь в правильности его подключения."); // Выводим сообщение об ошибке, если считанное значение равно 0
  Serial.println((String)"sensorTds: "+sensorTDSValue);         // Выводим считанное значение в монитор порта                                           
  relay.digitalWrite( ALL_CHANNEL, 0);                          // Выключаем оба реле
  relay_2.digitalWrite( ALL_CHANNEL, 0);                          // Выключаем оба реле
  if (sensorTDSValue <= limitValueTDSLow) Serial.println("TDS ниже минимально разрешенной границы. Автокорректировка концентрации не произведётся. Проверьте работу системы или отрегулируйте концентрацию вручную.");
  else if (sensorTDSValue >= limitValueTDSHigh) Serial.println("TDS выше максимально разрешенной границы. Автокорректировка концентрации не произведётся. Проверьте работу системы или отрегулируйте концентрацию вручную.");
  else if (!nightMode || (nightMode && ledState)) correctTDS(); // Запускаем функцию регулировки TDS, если ночной режим выключен, или если он включен и сейчас день
}
 
void correctPh(){                                               // Функция корректировки Ph
  if (sensorPhValue < (io_middlePh - (io_middlePh/100.0*(float)variationPh))) {   // Если измеренный Ph меньше минимально заданного
    if (digitalRead(levelHighPin)) uniCorrect(modulePh_UP);     // Включаем долив компонента Ph_UP если уровень не выше верхнего уровнемера
  }
  else if (sensorPhValue > (io_middlePh + (io_middlePh/100.0*(float)variationPh))) {  // Если измеренный Ph больше максимально заданного
    if (digitalRead(levelHighPin)) uniCorrect(modulePh_Down);   // Включаем долив компонента Ph_Down, если уровень не выше верхнего уровнемера
  }
}
 
void correctTDS(){                                              // Функция корректировки TDS
  if (sensorTDSValue < (io_middleTDS - (io_middleTDS/100.0*(float)variationTDS))) { // Если измеренный TDS меньше минимально заданного
    if (digitalRead(levelHighPin)) uniCorrect(moduleMix_1);     // Добавляем первый компонент концентрата, если уровень не выше верхнего уровнемера
    moduleMixFlag = true;                                       // Отмечаем, что после нужно добавить еще один компонент (разделяем во времени, чтобы концентраты не смешались при одновременном добавлении)
    moduleMixTime = millis();                                   // Отмечаем время добавления первого компонента концентрата
  }
  else if (sensorTDSValue > (io_middleTDS + (io_middleTDS/100.0*(float)variationTDS))) { // Если  измеренный TDS больше максимально заданного
    if (digitalRead(levelHighPin)) uniCorrect(moduleWater);     // Разбавляем водой, если уровень воды не выше верхнего уровнемера
  }
}
 
void uniCorrect(uint8_t pin){                                   // Функция добавления жидкости (принимает пин, на который будет подан сигнал [клик])
  digitalWrite(pin, HIGH);                                      // Подаём сигнал (управление модулями производится низким логическим импульсом)
  delay(50);                                                    // Короткая задержка
  digitalWrite(pin, LOW);                                       // Убираем сигнал
}

Режимы защиты и световой индикации

Основной блок

СигналТрактовкаЗащита
Синий горитУстановка выключена программно, через панель ioControl-
Синий мигаетПроизводится измерение параметров жидкости-
Зеленый горитУровень нормальный, штатная работа-
Красный мигаетУровень жидкости ниже минимально допустимогоОстанов циркуляционного насоса
Красный и синий поочередно мигаютУровень жидкости максимально возможныйЗапрет на добавление любых компонентов

Модуль долива

СигналТрактовкаЗащита
Короткая вспышка раз в несколько секундШтатная работа-
Светодиод горитПроизводится подача жидкости-
Светодиод мигаетОшибка: превышено время работы насосаВыключение насоса подачи компонента

Ссылки




Обсуждение

Гарантии и возврат Используя сайт Вы соглашаетесь с условями