Як зробити таймер для поливу на базі Arduino або реле часу своїми руками
Зміст статті
- Навіщо потрібна автоматична система поливу
- Типи систем автоматичного поливу
- Необхідні компоненти та інструменти
- Система на базі простого реле часу
- Arduino система з датчиками вологості
- Розширена система з Wi-Fi управлінням
- Монтаж водопровідної частини
- Програмування та налаштування
- Тестування та калібрування
- Обслуговування та поради експлуатації
Навіщо потрібна автоматична система поливу
Автоматизація поливу стає все більш актуальною для сучасних городників та любителів кімнатних рослин. За статистикою Міжнародної асоціації садівництва, використання систем автоматичного поливу зросло на 150% за останні п’ять років. Комерційні системи коштують від $200 до $1500, тоді як автоматичний полив DIY можна створити за $30-80.
Основні проблеми ручного поливу включають нерегулярність, перелив або недолив рослин, неможливість поливу під час відпустки та значні витрати часу. Автоматична система вирішує всі ці проблеми, забезпечуючи оптимальний режим зволоження ґрунту відповідно до потреб кожної рослини.
Цікавий факт: Правильно налаштована система поливу зроби сам може зекономити до 40% води порівняно з ручним поливом завдяки точному дозуванню та врахуванню погодних умов.
Переваги автоматизації поливу особливо помітні для теплиць, де підтримання стабільної вологості критично важливе для врожайності. Система може працювати цілодобово, реагуючи на зміни температури та вологості повітря, що неможливо забезпечити при ручному поливі.
Крім економії часу та води, автоматична система забезпечує рівномірний розподіл вологи, що покращує розвиток кореневої системи рослин. Це особливо важливо для розсади та молодих рослин, які чутливі до коливань вологості ґрунту.
Типи систем автоматичного поливу
Класифікація за принципом управління
1. Системи на основі таймерів: Найпростіший тип систем, що працює за заданим розкладом незалежно від зовнішніх умов. Включає механічні та електронні таймери з програмуванням часу запуску та тривалості поливу.
Переваги:
- Простота конструкції та налаштування
- Низька вартість компонентів ($15-30)
- Надійність та довговічність
- Автономна робота без живлення
Недоліки:
- Не враховує погодні умови
- Можливий перелив при дощовій погоді
- Потребує ручного коригування за сезонами
- Обмежені можливості програмування
2. Системи з датчиками вологості ґрунту: Розумні системи, що моніторять фактичний стан ґрунту та включають полив тільки при необхідності. Використовують резистивні або ємнісні датчики вологості.
Переваги:
- Точне дозування води відповідно до потреб
- Економія води до 50% порівняно з таймерними системами
- Адаптація до погодних умов
- Запобігання переливу та недоливу
Недоліки:
- Вища складність конструкції
- Потреба в калібруванні датчиків
- Можлива корозія датчиків у ґрунті
- Залежність від стабільного живлення
3. Багатопараметричні системи: Найсучасніші системи, що враховують множину факторів: вологість ґрунту та повітря, температуру, освітленість, прогноз погоди через інтернет.
Порівняння типів систем:
Характеристика | Таймер | Arduino + датчики | Wi-Fi система |
---|---|---|---|
Вартість | $15-30 | $40-70 | $80-150 |
Складність | Проста | Середня | Складна |
Точність поливу | Низька | Висока | Дуже висока |
Економія води | 0-10% | 30-50% | 40-60% |
Віддалене управління | Ні | Ні | Так |
Моніторинг | Мінімальний | Базовий | Повний |
Класифікація за способом подачі води
Крапельний полив: Найефективніший спосіб для більшості культур. Вода подається безпосередньо до кореневої зони через систему трубок з крапельницями.
Технічні характеристики:
- Витрата води: 2-8 л/год на крапельницю
- Робочий тиск: 0.5-2 атм
- Довжина магістралі: до 100м від джерела
- Кількість точок поливу: до 50 на одну лінію
Спринклерний полив: Імітує природний дощ через розпилення води в повітрі. Підходить для газонів та великих площ з однотипними рослинами.
Переваги спринклерів:
- Рівномірне покриття великих площ
- Додаткове зволоження повітря
- Можливість внесення рідких добрив
- Простота монтажу магістральних ліній
Підґрунтовий полив: Вода подається безпосередньо в кореневу зону через заглиблені перфоровані трубки. Найекономічніший спосіб використання води.
Необхідні компоненти та інструменти
Електронні компоненти для базової системи
Для системи на реле часу:
- Реле часу електронне ADD717 або аналог ($8-12) – програмування до 20 циклів
- Електромагнітний клапан 12V 1/2″ ($15-20) – нормально закритий
- Блок живлення 12V 2A ($8-10) – стабілізований з захистом
- Контактор модульний 25A ($12-15) – для керування потужним насосом
- Запобіжник автомат 10A ($3-5) – захист від короткого замикання
Для Arduino системи:
- Arduino Uno R3 або Nano ($12-18) – основний контролер
- Реле модуль 4-канальний 5V ($6-8) – керування клапанами
- Датчик вологості ґрунту YL-69 ($3-5) – резистивний тип
- Датчик температури DS18B20 ($2-3) – водостійкий варіант
- RTC модуль DS3231 ($4-6) – годинник реального часу
- LCD дисплей 16×2 з I2C ($5-8) – відображення інформації
- Модуль SD карти ($3-4) – логування даних
Для Wi-Fi системи:
- ESP32 DevKit V1 ($8-12) – контролер з Wi-Fi та Bluetooth
- Датчик DHT22 ($4-6) – температура та вологість повітря
- Ємнісний датчик вологості ґрунту v1.2 ($5-7) – не підлягає корозії
- Модуль живлення HLK-PM01 ($6-8) – 220V AC в 5V DC
- Реле твердотільне SSR-25DA ($8-12) – безшумне керування
- Датчик тиску води ($12-15) – контроль стану системи
Водопровідні компоненти
Основна магістраль:
- Труба ПНД 25мм ($1.5-2 за метр) – основна подача води
- Фітинги компресійні 25мм ($2-4 за штуку) – з’єднання труб
- Кран кульовий 1″ ($8-12) – головне перекриття системи
- Фільтр механічний 100 мкм ($15-20) – захист від забруднень
Система розподілу:
- Трубка ПВХ 16мм для крапельного поливу ($0.8-1.2 за метр)
- Крапельниці регульовані 2-8 л/год ($0.3-0.5 за штуку)
- Тройники та кутники 16мм ($0.5-1 за штуку)
- Заглушки кінцеві ($0.2-0.3 за штуку)
Обладнання для контролю:
- Манометр 0-6 атм ($5-8) – контроль тиску системи
- Редуктор тиску з манометром ($25-35) – стабілізація тиску
- Зворотний клапан 1/2″ ($3-5) – запобігання зворотному потоку
- Дренажний клапан ($8-12) – зимове обслуговування
Інструменти та витратні матеріали
Електромонтажні інструменти:
- Паяльник 40W з регулюванням температури
- Припій ПОС-61 діаметром 1мм з каніфоллю
- Кримпер для обтискання клем ($15-20)
- Мультиметр з функцією вимірювання ємності
- Термопістолет для термозусадки ($20-30)
Сантехнічні інструменти:
- Труборіз для ПНД труб ($25-35)
- Ключ розвідний 300мм для фітингів
- Дриль з набором свердел по металу та дереву
- Рулетка 5м для планування магістралей
- Лопата штикова для прокладання траншей
Важливо знати: При виборі електромагнітних клапанів обов’язково враховуйте робочий тиск вашої водопровідної системи. Для низького тиску (менше 1 атм) потрібні спеціальні клапани з підтримкою роботи від 0.2 атм.
Система на базі простого реле часу
Принцип роботи та схема підключення
Найпростіша система автоматичного поливу базується на програмованому реле часу, яке керує електромагнітним клапаном за заданим розкладом. Така система ідеально підходить для стабільних умов, коли потреби в поливі передбачувані.
Базова схема підключення:
[220V мережа] → [Автомат захисту 10A] → [Реле часу ADD717] → [Контактор 25A]
↓
[Електромагнітний клапан 12V] ← [Блок живлення 12V 2A] ← [Контактор вихід]
Детальний опис підключення реле часу ADD717:
-
Живлення реле часу:
- L (фаза) → автомат → клема 1 реле часу
- N (нуль) → клема 2 реле часу
- PE (земля) → корпус щитка
-
Вихідні контакти реле:
- Клема 3 (NO – нормально відкритий) → А1 контактора
- Клема 4 (Common) → нуль мережі
- При спрацьовуванні таймера замикається ланцюг А1-А2 контактора
-
Силова частина через контактор:
- Контакти контактора комутують 220V для блоку живлення клапана
- Блок живлення перетворює 220V в 12V для електромагнітного клапана
Програмування реле часу ADD717
Основні функції програмування:
- 20 незалежних програм увімкнення/вимкнення
- Точність до 1 секунди
- Недільний та річний календар
- Резервне живлення від літієвої батареї
- Ручний режим для тестування
Покрокове програмування системи поливу:
Крок 1: Встановлення поточного часу
1. Натисніть кнопку CLOCK
2. Встановіть рік (20xx)
3. Встановіть місяць (01-12)
4. Встановіть день (01-31)
5. Встановіть години (00-23)
6. Встановіть хвилини (00-59)
7. Натисніть CLOCK для збереження
Крок 2: Програмування циклів поливу
Програма 1 - Ранковий полив:
1. Натисніть PROG → відобразиться "1 ON"
2. Встановіть час увімкнення: 06:00
3. Натисніть PROG → відобразиться "1 OFF"
4. Встановіть час вимкнення: 06:15
5. Натисніть PROG для переходу до програми 2
Програма 2 - Вечірній полив:
1. "2 ON" - встановіть 19:00
2. "2 OFF" - встановіть 19:20
3. Повторіть для інших днів тижня
Крок 3: Вибір днів тижня
Для кожної програми можна вибрати дні:
- Mo Tu We Th Fr Sa Su (всі дні)
- Mo We Fr (через день)
- Sa Su (тільки вихідні)
- Або будь-яку іншу комбінацію
Розрахунок часу поливу
Формула розрахунку тривалості поливу:
Час поливу (хв) = (Об'єм ґрунту × Потрібна вологість) / Продуктивність системи
Де:
- Об'єм ґрунту - літри ґрунту під рослиною
- Потрібна вологість - % від об'єму (зазвичай 60-80%)
- Продуктивність - л/хв вашої системи поливу
Практичний приклад розрахунку:
Теплиця 3×6м з помідорами:
- 30 рослин у горщиках по 10л
- Цільова вологість: 70%
- Крапельниці 4 л/год (30 штук)
Розрахунок:
Об'єм для зволоження: 30 × 10л × 0.7 = 210л
Продуктивність системи: 30 × 4 л/год = 120 л/год
Час поливу: 210л ÷ 120 л/год = 1.75 години = 105 хвилин
Корекція за сезонами:
- Весна: базовий час × 0.8
- Літо: базовий час × 1.2
- Осінь: базовий час × 0.6
- Зима (для теплиць): базовий час × 0.4
Система захисту та індикації
Додаткові елементи безпеки:
- Датчик дощу – автоматичне відключення при опадах
- Датчик тиску води – захист насоса від сухого ходу
- Сигнальна лампа – індикація роботи системи
- Лічильник води – контроль витрат
Схема підключення датчика дощу:
Датчик дощу (нормально замкнений контакт) встановлюється
послідовно з живленням реле часу:
[220V] → [Автомат] → [Датчик дощу] → [Реле часу] → [Контактор]
При попаданні води на датчик його контакти розмикаються, вимикаючи всю систему поливу.
Рекомендація експерта: Для надійної роботи системи обов’язково встановіть УЗО (пристрій захисного відключення) номіналом 30мА для захисту від ураження електричним струмом при пошкодженні ізоляції у вологому середовищі.
Arduino система з датчиками вологості
Переваги розумної системи поливу
Arduino-система представляє наступний рівень автоматизації, де рішення про полив приймаються на основі реальних показників датчиків. Це дозволяє досягти оптимального балансу вологості для кожного типу рослин та суттєво економити воду.
Ключові можливості Arduino системи:
- Моніторинг вологості ґрунту в реальному часі
- Адаптивне коригування режиму поливу
- Ведення журналу поливів на SD карті
- Різні режими для різних зон поливу
- Захист від переливу та аварійних ситуацій
- Можливість ручного управління через кнопки
Вибір та калібрування датчиків вологості
Типи датчиків вологості ґрунту:
1. Резистивні датчики (YL-69, FC-28): Переваги:
- Низька вартість ($2-4)
- Простота підключення
- Широкий діапазон вимірювань
Недоліки:
- Корозія електродів через 6-12 місяців
- Потреба в захисному покритті
- Вплив солей у ґрунті на показники
2. Ємнісні датчики (v1.2, v2.0): Переваги:
- Довговічність 3-5 років
- Стабільні показники
- Стійкість до корозії
Недоліки:
- Вища вартість ($5-8)
- Потреба в індивідуальному калібруванні
- Чутливість до температури
Схема підключення Arduino системи
Повне підключення компонентів:
// Підключення датчиків
#define SOIL_SENSOR_1 A0 // Датчик вологості зона 1
#define SOIL_SENSOR_2 A1 // Датчик вологості зона 2
#define SOIL_SENSOR_3 A2 // Датчик вологості зона 3
#define TEMP_SENSOR 2 // DS18B20 температура
// Підключення реле
#define RELAY_ZONE_1 3 // Реле клапана зона 1
#define RELAY_ZONE_2 4 // Реле клапана зона 2
#define RELAY_ZONE_3 5 // Реле клапана зона 3
#define RELAY_PUMP 6 // Реле водяного насоса
// Підключення індикації
#define LED_POWER 7 // Індикатор живлення
#define LED_WATERING 8 // Індикатор поливу
#define BUZZER 9 // Звукова сигналізація
// Підключення управління
#define BUTTON_MANUAL 10 // Кнопка ручного режиму
#define BUTTON_SETTINGS 11 // Кнопка налаштувань
#define EMERGENCY_STOP 12 // Аварійна зупинка
// I2C підключення
// SDA (A4) - дисплей LCD, RTC модуль
// SCL (A5) - дисплей LCD, RTC модуль
Програмний код для Arduino системи
Основна структура програми:
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <SD.h>
// Ініціалізація об'єктів
LiquidCrystal_I2C lcd(0x27, 16, 2);
RTC_DS3231 rtc;
OneWire oneWire(TEMP_SENSOR);
DallasTemperature sensors(&oneWire);
// Структура для зберігання налаштувань зони
struct WateringZone {
int soilPin; // Пін датчика вологості
int relayPin; // Пін реле клапана
int minMoisture; // Мінімальний рівень вологості (%)
int maxMoisture; // Максимальний рівень вологості (%)
int wateringDuration; // Тривалість поливу (секунди)
bool isActive; // Чи активна зона
unsigned long lastWatering; // Час останнього поливу
};
// Налаштування зон поливу
WateringZone zones[3] = {
{A0, 3, 30, 70, 600, true, 0}, // Зона 1: помідори
{A1, 4, 25, 65, 450, true, 0}, // Зона 2: огірки
{A2, 5, 35, 75, 300, true, 0} // Зона 3: зелень
};
// Глобальні змінні
int currentMoisture[3];
float temperature;
bool manualMode = false;
bool systemEnabled = true;
unsigned long lastSensorRead = 0;
unsigned long lastDisplayUpdate = 0;
void setup() {
Serial.begin(9600);
// Ініціалізація компонентів
lcd.init();
lcd.backlight();
sensors.begin();
if (!rtc.begin()) {
Serial.println("RTC не знайдено!");
while (1);
}
if (!SD.begin(10)) {
Serial.println("SD карта не знайдена!");
}
// Налаштування пінів
for (int i = 0; i < 3; i++) {
pinMode(zones[i].relayPin, OUTPUT);
digitalWrite(zones[i].relayPin, HIGH); // Вимкнути реле (активний LOW)
}
pinMode(RELAY_PUMP, OUTPUT);
pinMode(LED_POWER, OUTPUT);
pinMode(LED_WATERING, OUTPUT);
pinMode(BUZZER, OUTPUT);
pinMode(BUTTON_MANUAL, INPUT_PULLUP);
pinMode(BUTTON_SETTINGS, INPUT_PULLUP);
pinMode(EMERGENCY_STOP, INPUT_PULLUP);
// Початкова індикація
digitalWrite(LED_POWER, HIGH);
lcd.setCursor(0, 0);
lcd.print("Система поливу");
lcd.setCursor(0, 1);
lcd.print("Ініціалізація...");
delay(2000);
Serial.println("Система поливу запущена");
logEvent("SYSTEM_START");
}
void loop() {
// Перевірка аварійної зупинки
if (digitalRead(EMERGENCY_STOP) == LOW) {
emergencyStop();
return;
}
// Читання датчиків кожні 30 секунд
if (millis() - lastSensorRead >= 30000) {
readSensors();
lastSensorRead = millis();
}
// Оновлення дисплея кожні 5 секунд
if (millis() - lastDisplayUpdate >= 5000) {
updateDisplay();
lastDisplayUpdate = millis();
}
// Перевірка кнопок
checkButtons();
// Основна логіка поливу
if (systemEnabled && !manualMode) {
for (int i = 0; i < 3; i++) {
checkZoneWatering(i);
}
}
delay(1000); // Основний цикл кожну секунду
}
void readSensors() {
// Читання температури
sensors.requestTemperatures();
temperature = sensors.getTempCByIndex(0);
// Читання вологості ґрунту для всіх зон
for (int i = 0; i < 3; i++) {
int rawValue = analogRead(zones[i].soilPin);
currentMoisture[i] = map(rawValue, 1023, 0, 0, 100); // Калібрування
currentMoisture[i] = constrain(currentMoisture[i], 0, 100);
}
// Логування даних
String logEntry = String(rtc.now().unixtime()) + ",";
logEntry += String(temperature) + ",";
for (int i = 0; i < 3; i++) {
logEntry += String(currentMoisture[i]) + ",";
}
File dataFile = SD.open("sensors.csv", FILE_WRITE);
if (dataFile) {
dataFile.println(logEntry);
dataFile.close();
}
}
void checkZoneWatering(int zoneIndex) {
WateringZone &zone = zones[zoneIndex];
if (!zone.isActive) return;
// Перевірка мінімального інтервалу між поливами (2 години)
if (millis() - zone.lastWatering < 7200000) return;
// Перевірка необхідності поливу
if (currentMoisture[zoneIndex] < zone.minMoisture) {
startWatering(zoneIndex);
}
}
void startWatering(int zoneIndex) {
WateringZone &zone = zones[zoneIndex];
Serial.print("Початок поливу зони ");
Serial.println(zoneIndex + 1);
// Увімкнення насоса та клапана
digitalWrite(RELAY_PUMP, LOW); // Увімкнути насос
delay(2000); // Дати час набрати тиск
digitalWrite(zone.relayPin, LOW); // Відкрити клапан
// Індикація поливу
digitalWrite(LED_WATERING, HIGH);
// Полив протягом заданого часу
unsigned long wateringStart = millis();
while (millis() - wateringStart < zone.wateringDuration * 1000) {
// Перевірка аварійної зупинки під час поливу
if (digitalRead(EMERGENCY_STOP) == LOW) {
emergencyStop();
return;
}
// Оновлення дисплея під час поливу
lcd.setCursor(0, 0);
lcd.print("Полив зони ");
lcd.print(zoneIndex + 1);
lcd.setCursor(0, 1);
lcd.print("Залишилось: ");
int remaining = zone.wateringDuration - (millis() - wateringStart) / 1000;
lcd.print(remaining);
lcd.print("с ");
delay(1000);
}
// Вимкнення системи поливу
digitalWrite(zone.relayPin, HIGH); // Закрити клапан
delay(1000);
digitalWrite(RELAY_PUMP, HIGH); // Вимкнути насос
digitalWrite(LED_WATERING, LOW); // Вимкнути індикацію
// Звуковий сигнал завершення
tone(BUZZER, 1000, 500);
// Збереження часу поливу
zone.lastWatering = millis();
// Логування події
String logEntry = "WATERING_ZONE_" + String(zoneIndex + 1) +
"_DURATION_" + String(zone.wateringDuration);
logEvent(logEntry);
Serial.print("Завершено полив зони ");
Serial.println(zoneIndex + 1);
}
void updateDisplay() {
static int displayMode = 0;
lcd.clear();
switch (displayMode) {
case 0: // Основна інформація
lcd.setCursor(0, 0);
lcd.print("T:");
lcd.print(temperature, 1);
lcd.print("C ");
DateTime now = rtc.now();
lcd.print(now.hour());
lcd.print(":");
if (now.minute() < 10) lcd.print("0");
lcd.print(now.minute());
lcd.setCursor(0, 1);
lcd.print("З1:");
lcd.print(currentMoisture[0]);
lcd.print("% З2:");
lcd.print(currentMoisture[1]);
lcd.print("%");
break;
case 1: // Деталі зони 3 та статус
lcd.setCursor(0, 0);
lcd.print("Зона 3: ");
lcd.print(currentMoisture[2]);
lcd.print("%");
lcd.setCursor(0, 1);
if (systemEnabled) {
lcd.print("Система: ВКЛ");
} else {
lcd.print("Система: ВИКЛ");
}
break;
}
displayMode = (displayMode + 1) % 2; // Перемикання режимів відображення
}
void checkButtons() {
static unsigned long lastButtonPress = 0;
// Debounce для кнопок
if (millis() - lastButtonPress < 200) return;
// Кнопка ручного режиму
if (digitalRead(BUTTON_MANUAL) == LOW) {
manualMode = !manualMode;
if (manualMode) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Ручний режим");
Serial.println("Увімкнено ручний режим");
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Авто режим");
Serial.println("Увімкнено автоматичний режим");
}
lastButtonPress = millis();
delay(1000);
}
// Кнопка налаштувань (циклічний тест всіх зон)
if (digitalRead(BUTTON_SETTINGS) == LOW && manualMode) {
testAllZones();
lastButtonPress = millis();
}
}
void testAllZones() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Тест всіх зон");
for (int i = 0; i < 3; i++) {
if (zones[i].isActive) {
lcd.setCursor(0, 1);
lcd.print("Тест зони ");
lcd.print(i + 1);
digitalWrite(RELAY_PUMP, LOW);
delay(1000);
digitalWrite(zones[i].relayPin, LOW);
delay(5000); // 5 секунд тестового поливу
digitalWrite(zones[i].relayPin, HIGH);
delay(1000);
digitalWrite(RELAY_PUMP, HIGH);
delay(2000); // Пауза між зонами
}
}
tone(BUZZER, 1500, 1000); // Сигнал завершення тесту
}
void emergencyStop() {
// Вимкнення всіх реле
for (int i = 0; i < 3; i++) {
digitalWrite(zones[i].relayPin, HIGH);
}
digitalWrite(RELAY_PUMP, HIGH);
digitalWrite(LED_WATERING, LOW);
// Аварійна індикація
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("АВАРІЙНА");
lcd.setCursor(0, 1);
lcd.print("ЗУПИНКА!");
// Звукова сигналізація
for (int i = 0; i < 5; i++) {
tone(BUZZER, 2000, 200);
delay(300);
}
logEvent("EMERGENCY_STOP");
Serial.println("АВАРІЙНА ЗУПИНКА АКТИВОВАНА!");
systemEnabled = false;
// Очікування скидання кнопки
while (digitalRead(EMERGENCY_STOP) == LOW) {
delay(100);
}
// Перезапуск системи після зняття аварійної зупинки
delay(2000);
systemEnabled = true;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Система");
lcd.setCursor(0, 1);
lcd.print("відновлена");
delay(2000);
logEvent("SYSTEM_RESTORED");
}
void logEvent(String event) {
DateTime now = rtc.now();
String timestamp = String(now.year()) + "-" +
String(now.month()) + "-" +
String(now.day()) + " " +
String(now.hour()) + ":" +
String(now.minute()) + ":" +
String(now.second());
File logFile = SD.open("events.log", FILE_WRITE);
if (logFile) {
logFile.print(timestamp);
logFile.print(" - ");
logFile.println(event);
logFile.close();
}
Serial.print(timestamp);
Serial.print(" - ");
Serial.println(event);
}
Калібрування датчиків вологості
Процедура калібрування резистивних датчиків:
-
Калібрування в сухому стані:
// Помістіть датчик у повністю сухий ґрунт int dryValue = analogRead(SOIL_SENSOR_PIN); Serial.print("Сухий ґрунт: "); Serial.println(dryValue);
-
Калібрування у вологому стані:
// Помістіть датчик у насичений водою ґрунт int wetValue = analogRead(SOIL_SENSOR_PIN); Serial.print("Вологий ґрунт: "); Serial.println(wetValue);
-
Функція перетворення в відсотки:
int getMoisturePercent(int sensorPin, int dryVal, int wetVal) { int rawValue = analogRead(sensorPin); int moisture = map(rawValue, dryVal, wetVal, 0, 100); return constrain(moisture, 0, 100); }
Рекомендовані пороги вологості для різних культур:
- Помідори: 30-70% (полив при 30%, зупинка при 70%)
- Огірки: 25-65% (більше води, частіший полив)
- Перець: 35-75% (помірна вологість)
- Зелень (салат, кріп): 40-80% (постійна вологість)
- Квіти (петунія, бегонія): 25-60% (менше води)
Розширена система з Wi-Fi управлінням
Архітектура IoT системи поливу
Сучасна система поливу з Wi-Fi управлінням представляє повноцінне IoT рішення, що дозволяє моніторити та керувати поливом з будь-якої точки світу через інтернет. Така система особливо корисна для дачників та власників теплиць, які не можуть щодня перевіряти стан рослин.
Компоненти розумної системи:
- ESP32 як основний контролер з Wi-Fi
- Хмарний сервіс для зберігання даних та управління
- Мобільний додаток або веб-інтерфейс
- Датчики для комплексного моніторингу
- Виконавчі пристрої (насоси, клапани)
- Система сповіщень
Налаштування ESP32 та підключення до Wi-Fi
Базова конфігурація ESP32:
#include <WiFi.h>
#include <WebServer.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <PubSubClient.h>
#include <ESPAsyncWebServer.h>
#include <SPIFFS.h>
// Wi-Fi налаштування
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
// MQTT налаштування для IoT платформи
const char* mqtt_server = "iot.eclipse.org";
const int mqtt_port = 1883;
const char* mqtt_user = "your_username";
const char* mqtt_password = "your_password";
// Веб-сервер та MQTT клієнт
AsyncWebServer server(80);
WiFiClient espClient;
PubSubClient mqttClient(espClient);
// Структура системи поливу
struct SmartWateringSystem {
struct {
float temperature;
float humidity;
int soilMoisture[4];
float waterPressure;
bool rainDetected;
unsigned long lastUpdate;
} sensors;
struct {
bool zones[4];
bool mainPump;
int wateringDuration[4];
unsigned long zoneStartTime[4];
} actuators;
struct {
int moistureThresholds[4][2]; // min, max для кожної зони
int wateringSchedule[4][7][4]; // зона, день тижня, години
bool autoMode;
bool systemEnabled;
String weatherAPI;
} settings;
struct {
unsigned long totalWaterUsed;
int wateringCycles;
float powerConsumption;
String lastError;
} statistics;
};
SmartWateringSystem irrigationSystem;
void setup() {
Serial.begin(115200);
// Ініціалізація файлової системи
if (!SPIFFS.begin(true)) {
Serial.println("Помилка монтування SPIFFS");
return;
}
// Ініціалізація пінів
initializePins();
// Підключення до Wi-Fi
connectToWiFi();
// Налаштування веб-сервера
setupWebServer();
// Підключення до MQTT брокера
mqttClient.setServer(mqtt_server, mqtt_port);
mqttClient.setCallback(onMqttMessage);
// Завантаження налаштувань з пам'яті
loadSettings();
// Ініціалізація датчиків
initializeSensors();
Serial.println("Розумна система поливу запущена");
Serial.print("IP адреса: ");
Serial.println(WiFi.localIP());
}
void loop() {
// Підтримка Wi-Fi з'єднання
if (WiFi.status() != WL_CONNECTED) {
reconnectWiFi();
}
// Підтримка MQTT з'єднання
if (!mqttClient.connected()) {
reconnectMQTT();
}
mqttClient.loop();
// Читання датчиків кожні 30 секунд
static unsigned long lastSensorRead = 0;
if (millis() - lastSensorRead >= 30000) {
readAllSensors();
publishSensorData();
lastSensorRead = millis();
}
// Основна логіка поливу
processWateringLogic();
// Перевірка розкладу поливу
checkWateringSchedule();
// Моніторинг стану системи
systemHealthCheck();
delay(1000);
}
void connectToWiFi() {
WiFi.begin(ssid, password);
Serial.print("Підключення до Wi-Fi");
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 30) {
delay(1000);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println();
Serial.println("Wi-Fi підключено!");
Serial.print("IP адреса: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("Не вдалося підключитися до Wi-Fi");
// Запуск в режимі точки доступу для налаштування
startAPMode();
}
}
void setupWebServer() {
// Головна сторінка
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/index.html", "text/html");
});
// API для отримання даних датчиків
server.on("/api/sensors", HTTP_GET, [](AsyncWebServerRequest *request){
DynamicJsonDocument doc(1024);
doc["temperature"] = irrigationSystem.sensors.temperature;
doc["humidity"] = irrigationSystem.sensors.humidity;
doc["waterPressure"] = irrigationSystem.sensors.waterPressure;
doc["rainDetected"] = irrigationSystem.sensors.rainDetected;
JsonArray moisture = doc.createNestedArray("soilMoisture");
for (int i = 0; i < 4; i++) {
moisture.add(irrigationSystem.sensors.soilMoisture[i]);
}
String response;
serializeJson(doc, response);
request->send(200, "application/json", response);
});
// API для управління поливом
server.on("/api/watering", HTTP_POST, [](AsyncWebServerRequest *request){},
NULL, [](AsyncWebServerRequest *request, uint8_t *data, size_t len,
size_t index, size_t total){
DynamicJsonDocument doc(512);
deserializeJson(doc, (char*)data);
if (doc.containsKey("zone") && doc.containsKey("action")) {
int zone = doc["zone"];
String action = doc["action"];
if (action == "start" && zone >= 0 && zone < 4) {
startWateringZone(zone);
request->send(200, "application/json", "{\"status\":\"ok\"}");
} else if (action == "stop" && zone >= 0 && zone < 4) {
stopWateringZone(zone);
request->send(200, "application/json", "{\"status\":\"ok\"}");
} else {
request->send(400, "application/json", "{\"error\":\"invalid_request\"}");
}
}
});
// API для налаштувань
server.on("/api/settings", HTTP_GET, [](AsyncWebServerRequest *request){
DynamicJsonDocument doc(2048);
doc["autoMode"] = irrigationSystem.settings.autoMode;
doc["systemEnabled"] = irrigationSystem.settings.systemEnabled;
JsonArray thresholds = doc.createNestedArray("moistureThresholds");
for (int i = 0; i < 4; i++) {
JsonArray threshold = thresholds.createNestedArray();
threshold.add(irrigationSystem.settings.moistureThresholds[i][0]);
threshold.add(irrigationSystem.settings.moistureThresholds[i][1]);
}
String response;
serializeJson(doc, response);
request->send(200, "application/json", response);
});
// Статичні файли
server.serveStatic("/", SPIFFS, "/");
server.begin();
Serial.println("Веб-сервер запущено");
}
void readAllSensors() {
// Читання температури та вологості повітря
// (використовуючи DHT22 або аналогічний датчик)
irrigationSystem.sensors.temperature = readTemperature();
irrigationSystem.sensors.humidity = readHumidity();
// Читання вологості ґрунту для всіх зон
for (int i = 0; i < 4; i++) {
irrigationSystem.sensors.soilMoisture[i] = readSoilMoisture(i);
}
// Читання тиску води
irrigationSystem.sensors.waterPressure = readWaterPressure();
// Перевірка датчика дощу
irrigationSystem.sensors.rainDetected = isRainDetected();
// Отримання прогнозу погоди через API
if (millis() % 3600000 == 0) { // Кожну годину
getWeatherForecast();
}
irrigationSystem.sensors.lastUpdate = millis();
}
void publishSensorData() {
if (mqttClient.connected()) {
DynamicJsonDocument doc(1024);
doc["deviceId"] = WiFi.macAddress();
doc["timestamp"] = millis();
doc["temperature"] = irrigationSystem.sensors.temperature;
doc["humidity"] = irrigationSystem.sensors.humidity;
doc["waterPressure"] = irrigationSystem.sensors.waterPressure;
doc["rainDetected"] = irrigationSystem.sensors.rainDetected;
JsonArray moisture = doc.createNestedArray("soilMoisture");
for (int i = 0; i < 4; i++) {
moisture.add(irrigationSystem.sensors.soilMoisture[i]);
}
String payload;
serializeJson(doc, payload);
mqttClient.publish("irrigation/sensors", payload.c_str());
}
}
void processWateringLogic() {
if (!irrigationSystem.settings.systemEnabled ||
!irrigationSystem.settings.autoMode) {
return;
}
// Перевірка дощу - зупинити полив
if (irrigationSystem.sensors.rainDetected) {
stopAllWatering();
return;
}
// Перевірка тиску води
if (irrigationSystem.sensors.waterPressure < 0.5) {
logError("Низький тиск води");
stopAllWatering();
return;
}
// Логіка поливу для кожної зони
for (int zone = 0; zone < 4; zone++) {
int currentMoisture = irrigationSystem.sensors.soilMoisture[zone];
int minThreshold = irrigationSystem.settings.moistureThresholds[zone][0];
int maxThreshold = irrigationSystem.settings.moistureThresholds[zone][1];
// Почати полив якщо вологість нижче мінімального порогу
if (currentMoisture < minThreshold && !irrigationSystem.actuators.zones[zone]) {
// Перевірити інтервал між поливами (мінімум 2 години)
if (millis() - irrigationSystem.actuators.zoneStartTime[zone] > 7200000) {
startWateringZone(zone);
}
}
// Зупинити полив якщо досягнуто максимального порогу
else if (currentMoisture > maxThreshold && irrigationSystem.actuators.zones[zone]) {
stopWateringZone(zone);
}
// Автоматична зупинка після максимального часу поливу (30 хвилин)
else if (irrigationSystem.actuators.zones[zone] &&
millis() - irrigationSystem.actuators.zoneStartTime[zone] > 1800000) {
stopWateringZone(zone);
logError("Zone " + String(zone) + " - максимальний час поливу перевищено");
}
}
}
void startWateringZone(int zone) {
if (zone < 0 || zone >= 4) return;
Serial.print("Початок поливу зони ");
Serial.println(zone);
// Увімкнути насос якщо він ще не працює
if (!irrigationSystem.actuators.mainPump) {
digitalWrite(PUMP_RELAY_PIN, LOW);
irrigationSystem.actuators.mainPump = true;
delay(2000); // Дати час набрати тиск
}
// Відкрити клапан зони
digitalWrite(ZONE_RELAY_PINS[zone], LOW);
irrigationSystem.actuators.zones[zone] = true;
irrigationSystem.actuators.zoneStartTime[zone] = millis();
// Відправити сповіщення
sendNotification("Початок поливу зони " + String(zone + 1));
// Публікувати подію в MQTT
publishWateringEvent(zone, "started");
// Логування
logEvent("WATERING_STARTED_ZONE_" + String(zone));
}
void stopWateringZone(int zone) {
if (zone < 0 || zone >= 4) return;
Serial.print("Зупинка поливу зони ");
Serial.println(zone);
// Закрити клапан зони
digitalWrite(ZONE_RELAY_PINS[zone], HIGH);
irrigationSystem.actuators.zones[zone] = false;
// Розрахувати тривалість поливу
unsigned long duration = (millis() - irrigationSystem.actuators.zoneStartTime[zone]) / 1000;
irrigationSystem.actuators.wateringDuration[zone] = duration;
// Перевірити чи можна вимкнути насос
bool anyZoneActive = false;
for (int i = 0; i < 4; i++) {
if (irrigationSystem.actuators.zones[i]) {
anyZoneActive = true;
break;
}
}
if (!anyZoneActive && irrigationSystem.actuators.mainPump) {
digitalWrite(PUMP_RELAY_PIN, HIGH);
irrigationSystem.actuators.mainPump = false;
}
// Оновити статистику
irrigationSystem.statistics.wateringCycles++;
irrigationSystem.statistics.totalWaterUsed += estimateWaterUsage(zone, duration);
// Відправити сповіщення
sendNotification("Завершено полив зони " + String(zone + 1) +
" (тривалість: " + String(duration) + "с)");
// Публікувати подію в MQTT
publishWateringEvent(zone, "stopped");
// Логування
logEvent("WATERING_STOPPED_ZONE_" + String(zone) + "_DURATION_" + String(duration));
}
void getWeatherForecast() {
if (irrigationSystem.settings.weatherAPI.length() == 0) return;
HTTPClient http;
http.begin(irrigationSystem.settings.weatherAPI);
int httpResponseCode = http.GET();
if (httpResponseCode == 200) {
String payload = http.getString();
DynamicJsonDocument doc(2048);
deserializeJson(doc, payload);
// Аналіз прогнозу дощу на найближчі 24 години
if (doc.containsKey("forecast")) {
JsonArray forecast = doc["forecast"];
bool rainExpected = false;
for (JsonVariant hour : forecast) {
if (hour["precipitation"] > 2.0) { // >2мм дощу
rainExpected = true;
break;
}
}
if (rainExpected) {
// Скоригувати пороги вологості (зменшити полив)
for (int i = 0; i < 4; i++) {
irrigationSystem.settings.moistureThresholds[i][0] -= 10;
irrigationSystem.settings.moistureThresholds[i][0] =
max(10, irrigationSystem.settings.moistureThresholds[i][0]);
}
sendNotification("Очікується дощ - скорочено програму поливу");
}
}
}
http.end();
}
void sendNotification(String message) {
// Push-сповіщення через MQTT
if (mqttClient.connected()) {
DynamicJsonDocument doc(256);
doc["message"] = message;
doc["timestamp"] = millis();
doc["deviceId"] = WiFi.macAddress();
String payload;
serializeJson(doc, payload);
mqttClient.publish("irrigation/notifications", payload.c_str());
}
// Email сповіщення (через зовнішній сервіс)
sendEmailNotification(message);
Serial.println("Сповіщення: " + message);
}
Веб-інтерфейс системи управління
HTML сторінка (збережена в SPIFFS як /index.html):
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Розумна система поливу</title>
<style>
body {font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5;}
.container {max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);}
.header {text-align: center; color: #2c5aa0; margin-bottom: 30px;}
.sensors-grid {display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 30px;}
.sensor-card {background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 10px; text-align: center;}
.sensor-value {font-size: 2em; font-weight: bold; margin: 10px 0;}
.zones-grid {display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 30px;}
.zone-card {background: white; border: 2px solid #ddd; padding: 15px; border-radius: 8px; text-align: center;}
.zone-active {border-color: #4CAF50; background: #f1f8e9;}
.zone-dry {border-color: #ff9800; background: #fff3e0;}
.control-panel {background: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 20px;}
.btn {padding: 10px 20px; margin: 5px; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;}
.btn-primary {background: #007bff; color: white;}
.btn-success {background: #28a745; color: white;}
.btn-danger {background: #dc3545; color: white;}
.btn-warning {background: #ffc107; color: black;}
.status-indicator {display: inline-block; width: 12px; height: 12px; border-radius: 50%; margin-right: 8px;}
.status-online {background: #4CAF50;}
.status-offline {background: #f44336;}
.moisture-bar {width: 100%; height: 20px; background: #eee; border-radius: 10px; overflow: hidden; margin: 10px 0;}
.moisture-fill {height: 100%; background: linear-gradient(90deg, #ff4444 0%, #ffaa00 50%, #44ff44 100%); transition: width 0.3s;}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🌱 Розумна система поливу</h1>
<p>Статус системи: <span class="status-indicator status-online"></span>Онлайн</p>
</div>
<div class="sensors-grid">
<div class="sensor-card">
<h3>🌡️ Температура</h3>
<div class="sensor-value" id="temperature">--°C</div>
</div>
<div class="sensor-card">
<h3>💧 Вологість повітря</h3>
<div class="sensor-value" id="humidity">--%</div>
</div>
<div class="sensor-card">
<h3>📊 Тиск води</h3>
<div class="sensor-value" id="pressure">-- бар</div>
</div>
<div class="sensor-card">
<h3>🌧️ Дощ</h3>
<div class="sensor-value" id="rain">--</div>
</div>
</div>
<div class="control-panel">
<h3>Управління системою</h3>
<button class="btn btn-primary" onclick="toggleSystem()">Увімкнути/Вимкнути систему</button>
<button class="btn btn-warning" onclick="toggleAutoMode()">Авто/Ручний режим</button>
<button class="btn btn-danger" onclick="stopAllWatering()">Зупинити всі поливи</button>
<button class="btn btn-success" onclick="testAllZones()">Тест всіх зон</button>
</div>
<h3>Зони поливу</h3>
<div class="zones-grid" id="zonesContainer">
<!-- Зони будуть додані динамічно -->
</div>
<div id="logContainer">
<h3>Журнал подій</h3>
<div id="eventLog" style="height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; background: #f9f9f9;"></div>
</div>
</div>
<script>
let systemData = {};
// Оновлення даних кожні 5 секунд
setInterval(updateData, 5000);
updateData(); // Перше оновлення
async function updateData() {
try {
const response = await fetch('/api/sensors');
const data = await response.json();
systemData = data;
updateDisplay();
} catch (error) {
console.error('Помилка отримання даних:', error);
document.querySelector('.status-indicator').className = 'status-indicator status-offline';
}
}
function updateDisplay() {
// Оновлення показників датчиків
document.getElementById('temperature').textContent = systemData.temperature?.toFixed(1) + '°C';
document.getElementById('humidity').textContent = systemData.humidity?.toFixed(1) + '%';
document.getElementById('pressure').textContent = systemData.waterPressure?.toFixed(2) + ' бар';
document.getElementById('rain').textContent = systemData.rainDetected ? 'Так' : 'Ні';
// Оновлення зон поливу
updateZones();
}
function updateZones() {
const container = document.getElementById('zonesContainer');
container.innerHTML = '';
const zoneNames = ['Помідори', 'Огірки', 'Перець', 'Зелень'];
for (let i = 0; i < 4; i++) {
const moisture = systemData.soilMoisture?.[i] || 0;
const isActive = false; // Отримувати з API стану зон
const zoneCard = document.createElement('div');
zoneCard.className = `zone-card ${isActive ? 'zone-active' : ''} ${moisture < 30 ? 'zone-dry' : ''}`;
zoneCard.innerHTML = `
<h4>${zoneNames[i]} (Зона ${i + 1})</h4>
<div class="moisture-bar">
<div class="moisture-fill" style="width: ${moisture}%"></div>
</div>
<p>Вологість: ${moisture}%</p>
<p>Статус: ${isActive ? 'Поливається' : 'Очікування'}</p>
<button class="btn ${isActive ? 'btn-danger' : 'btn-success'}"
onclick="toggleZone(${i})">
${isActive ? 'Зупинити' : 'Запустити'}
</button>
`;
container.appendChild(zoneCard);
}
}
async function toggleZone(zoneIndex) {
const isActive = false; // Отримати реальний стан зони
const action = isActive ? 'stop' : 'start';
try {
const response = await fetch('/api/watering', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({zone: zoneIndex, action: action})
});
if (response.ok) {
addLogEntry(`${action === 'start' ? 'Запущено' : 'Зупинено'} полив зони ${zoneIndex + 1}`);
updateData();
} else {
addLogEntry(`Помилка управління зоною ${zoneIndex + 1}`);
}
} catch (error) {
console.error('Помилка:', error);
addLogEntry(`Помилка з'єднання при управлінні зоною ${zoneIndex + 1}`);
}
}
async function toggleSystem() {
// Реалізувати перемикання системи
addLogEntry('Перемикання стану системи');
}
async function toggleAutoMode() {
// Реалізувати перемикання режиму
addLogEntry('Перемикання режиму авто/ручний');
}
async function stopAllWatering() {
for (let i = 0; i < 4; i++) {
await toggleZone(i); // Зупинити якщо активна
}
addLogEntry('Зупинено всі поливи');
}
async function testAllZones() {
addLogEntry('Запуск тестування всіх зон');
for (let i = 0; i < 4; i++) {
setTimeout(() => {
addLogEntry(`Тест зони ${i + 1}`);
}, i * 2000);
}
}
function addLogEntry(message) {
const logContainer = document.getElementById('eventLog');
const timestamp = new Date().toLocaleString('uk-UA');
const entry = document.createElement('div');
entry.innerHTML = `<strong>${timestamp}:</strong> ${message}`;
logContainer.appendChild(entry);
logContainer.scrollTop = logContainer.scrollHeight;
}
// Отримання журналу подій при завантаженні
window.addEventListener('load', () => {
addLogEntry('Веб-інтерфейс завантажено');
});
</script>
</body>
</html>
Рекомендація експерта: Для зовнішнього доступу до системи через інтернет використовуйте VPN або хмарні сервіси як Blynk, ThingSpeak. Пряме відкриття портів у домашньому роутері створює серйозні ризики безпеки.
Монтаж водопровідної частини {#water-system-installation}
Планування системи водопостачання
Правильне планування водопровідної частини критично важливе для ефективної роботи автоматичного поливу. Система повинна забезпечувати рівномірний тиск та розподіл води по всіх зонах з можливістю індивідуального регулювання.
Основні принципи планування:
- Зонування за типами рослин – різні культури потребують різної інтенсивності поливу
- Врахування рельєфу – вода тече згори вниз, це впливає на тиск у системі
- Доступність для обслуговування – всі компоненти повинні бути легко доступні
- Можливість розширення – залиште запас потужності для майбутніх зон
- Зимове обслуговування – система повинна легко спорожнюватися
Розрахунок потужності системи
Формула розрахунку необхідної продуктивності насоса:
Q (л/хв) = Σ(qi × ni)
Де:
Q - загальна продуктивність насоса
qi - витрата одного споживача (крапельниця, спринклер)
ni - кількість споживачів i-го типу
Практичний приклад розрахунку:
Теплиця 6×12м з змішаними культурами:
Зона 1 - Помідори: 40 крапельниць по 4 л/год = 160 л/год
Зона 2 - Огірки: 30 крапельниць по 6 л/год = 180 л/год
Зона 3 - Перець: 25 крапельниць по 3 л/год = 75 л/год
Зона 4 - Зелень: 50 крапельниць по 2 л/год = 100 л/год
Загальна потреба (при одночасній роботі всіх зон): 515 л/год ≈ 8.6 л/хв
Рекомендований насос: 12-15 л/хв (з запасом 40-70%)
Розрахунок тиску в системі:
P = P0 + ΔPh + ΔPf + ΔPl
Де:
P - необхідний тиск насоса
P0 - робочий тиск споживачів (0.5-2 бар)
ΔPh - втрати на підйом (0.1 бар на 1м висоти)
ΔPf - втрати на тертя в трубах
ΔPl - втрати в фільтрах та арматурі (0.2-0.5 бар)
Вибір компонентів водопровідної системи
Магістральні труби:
-
ПНД (поліетилен низького тиску) 25-32мм – основна магістраль
- Переваги: гнучкість, стійкість до морозу, довговічність 50+ років
- Недоліки: потребує спеціального інструменту для з’єднання
- Рекомендований тиск: до 10 бар
-
ППР (поліпропілен) 25-32мм – альтернатива для стаціонарного монтажу
- Переваги: простота з’єднання, висока міцність
- Недоліки: жорсткість, потреба в ізоляції від морозу
- Рекомендований тиск: до 25 бар
Розподільні труби:
-
ПВХ трубка 16мм – для крапельного поливу
- Витримує тиск до 4 бар
- Легко обрізається та з’єднується
- UV-стійка для зовнішнього використання
-
Поліетиленова трубка 12мм – для точкового поливу
- Більша гнучкість
- Підходить для складних траєкторій
- Нижча вартість
Покрокова інструкція монтажу
Крок 1: Підготовка траси та копання траншей
-
Розмітка траси:
- Використовуйте аерозольну фарбу для позначення траси - Позначте місця встановлення клапанів та розгалужень - Врахуйте відстань до дерев (коріння) мінімум 1м - Передбачте схил 2-3мм на метр для дренажу
-
Копання траншей:
Глибина траншеї: - Основна магістраль: 60-80см (нижче глибини промерзання) - Розподільні лінії: 40-50см - Ширина: 20-25см для зручності монтажу Дно траншеї: - Рівне, без каміння - Піщана подушка 5-10см - Схил для дренажу
Крок 2: Монтаж основної магістралі
-
Прокладання труби ПНД:
// Порядок з'єднання компресійними фітингами: 1. Обрізати трубу рівно труборізом 2. Зняти фаску напилком 3. Розібрати фітинг (гайка, цанга, кільце, корпус) 4. Надіти гайку та цангу на трубу 5. Вставити трубу в корпус фітинга до упору 6. Затягнути гайку моментом 40-60 Нм
-
Встановлення запірної арматури:
Послідовність установки від джерела води: 1. Головний кран (кульовий 1") 2. Зворотний клапан 3. Фільтр грубого очищення 100мкм 4. Манометр 5. Редуктор тиску (налаштування 2-3 бар) 6. Розгалуження на зони
Крок 3: Монтаж електромагнітних клапанів
-
Вибір місця встановлення:
- На відстані 30-50см від поверхні землі
- У захисному колодязі або боксі
- З доступом для обслуговування
- На прямолінійній ділянці труби
-
Підключення клапана:
Гідравлічне підключення: - Вхід клапана: від основної магістралі - Вихід клапана: до розподільної мережі зони - Стрілка на корпусі вказує напрямок потоку Електричне підключення: - Червоний провід: +12V від блоку керування - Чорний провід: загальний (GND) - Використовуйте кабель сечением 1.5мм² - Герметизуйте з'єднання термозусадкою
Крок 4: Облаштування крапельного поливу
-
Прокладання розподільних ліній:
Схема розводки: [Клапан] → [Фільтр 120 мкм] → [Редуктор тиску 1.5 бар] → → [Розподільна трубка 16мм] → [Крапельниці] Максимальна довжина лінії: 50м Максимальна кількість крапельниць на лінії: 30 шт Відстань між крапельницями: 30-50см залежно від культури
-
Встановлення крапельниць:
// Процедура встановлення крапельниці: 1. Проколоти отвір у трубці спеціальним дирколом 2. Вставити з'єднувач крапельниці 3. Приєднати трубку подачі до рослини 4. Налаштувати витрату обертанням регулятора 5. Закріпити крапельницю біля кореневої зони
Тестування системи та усунення неполадок
Початкове тестування системи:
-
Перевірка герметичності:
Процедура тестування: 1. Заповнити систему водою при закритих клапанах 2. Піднати тиск до 4 бар 3. Залишити на 2 години 4. Перепад тиску не повинен перевищувати 0.2 бар 5. Оглянути всі з'єднання на предмет витоків
-
Тестування зон поливу:
void testWateringZones() { Serial.println("Початок тестування зон поливу"); for (int zone = 0; zone < 4; zone++) { Serial.print("Тестування зони "); Serial.println(zone + 1); // Відкрити клапан зони digitalWrite(ZONE_RELAY_PINS[zone], LOW); // Включити насос digitalWrite(PUMP_RELAY_PIN, LOW); delay(2000); // Час на набір тиску // Тестування протягом 30 секунд unsigned long testStart = millis(); while (millis() - testStart < 30000) { // Моніторинг тиску float pressure = readWaterPressure(); Serial.print("Тиск зони "); Serial.print(zone + 1); Serial.print(": "); Serial.println(pressure); if (pressure < 1.0) { Serial.println("УВАГА: Низький тиск!"); } delay(5000); } // Закрити клапан digitalWrite(ZONE_RELAY_PINS[zone], HIGH); delay(2000); // Вимкнути насос якщо це остання зона if (zone == 3) { digitalWrite(PUMP_RELAY_PIN, HIGH); } Serial.print("Зона "); Serial.print(zone + 1); Serial.println(" - тест завершено"); delay(5000); // Пауза між зонами } Serial.println("Тестування всіх зон завершено"); }
Типові проблеми та їх вирішення:
Проблема | Можливі причини | Рішення |
---|---|---|
Низький тиск у зоні | Засмічення фільтра | Промити/замінити фільтр |
Нерівномірний полив | Різниця висот | Встановити компенсуючі крапельниці |
Клапан не відкривається | Низька напруга живлення | Перевірити блок живлення |
Витік на з’єднанні | Пошкоджене ущільнення | Перезібрати з’єднання |
Насос працює без води | Відсутність зворотного клапана | Встановити зворотний клапан |
Важливо: Після завершення монтажу обов’язково складіть схему розташування всіх компонентів із зазначенням глибини залягання. Це допоможе при майбутньому обслуговуванні та ремонті системи.
Програмування та налаштування
Розширене програмування Arduino системи
Професійна система автоматичного поливу потребує складного алгоритму, що враховує множину параметрів та сценаріїв роботи. Розглянемо детально всі аспекти програмування.
Структура основної програми з розширеним функціоналом:
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <SD.h>
#include <EEPROM.h>
#include <ArduinoJson.h>
// Розширені налаштування системи
struct SystemConfig {
// Налаштування зон поливу
struct ZoneSettings {
bool enabled; // Чи активна зона
int soilSensorPin; // Пін датчика вологості
int relayPin; // Пін реле клапана
int moistureMin; // Мінімальний поріг вологості (%)
int moistureMax; // Максимальний поріг вологості (%)
int wateringDuration; // Базова тривалість поливу (секунди)
int minInterval; // Мінімальний інтервал між поливами (години)
float flowRate; // Швидкість подачі води (л/хв)
String plantType; // Тип рослини для оптимізації
// Розклад поливу за днями тижня та годинами
bool schedule[7][24]; // [день тижня][година]
// Адаптивні параметри
float temperatureCompensation; // Коефіцієнт корекції за температурою
float humidityCompensation; // Коефіцієнт корекції за вологістю повітря
float seasonalMultiplier; // Сезонний множник
} zones[4];
// Глобальні налаштування системи
bool systemEnabled;
bool autoMode;
bool weatherIntegration;
String weatherApiKey;
float rainThreshold; // Поріг опадів для припинення поливу (мм)
int maxConcurrentZones; // Максимальна кількість зон одночасно
// Налаштування безпеки
float minWaterPressure; // Мінімальний тиск для роботи (бар)
float maxWaterPressure; // Максимальний тиск (бар)
int emergencyStopTimeout; // Таймаут аварійної зупинки (секунди)
int maxWateringTime; // Максимальний час поливу однієї зони (хвилини)
// Енергозбереження
int sleepModeDelay; // Затримка переходу в сон (хвилини)
bool nightModeEnabled; // Нічний режим
int nightModeStart; // Початок нічного режиму (години)
int nightModeEnd; // Кінець нічного режиму (години)
};
SystemConfig config;
// Структура для збереження поточного стану системи
struct SystemState {
struct ZoneState {
bool isWatering;
unsigned long wateringStartTime;
unsigned long lastWateringTime;
int currentMoisture;
float totalWaterUsed;
int wateringCycles;
String lastError;
} zones[4];
struct SensorData {
float temperature;
float humidity;
float waterPressure;
bool rainDetected;
float lightLevel;
unsigned long lastReadTime;
} sensors;
struct SystemStats {
unsigned long uptime;
float totalWaterUsed;
int totalWateringCycles;
int errorCount;
unsigned long lastMaintenanceTime;
} stats;
bool emergencyStop;
bool maintenanceMode;
String currentWeather;
float todayRainfall;
};
SystemState state;
// Ініціалізація компонентів
LiquidCrystal_I2C lcd(0x27, 20, 4); // Більший дисплей для більше інформації
RTC_DS3231 rtc;
OneWire oneWire(TEMP_SENSOR_PIN);
DallasTemperature sensors(&oneWire);
void setup() {
Serial.begin(115200);
// Ініціалізація компонентів
initializeSystem();
// Завантаження конфігурації з EEPROM
loadConfiguration();
// Перевірка цілісності системи
if (!systemSelfTest()) {
Serial.println("ПОМИЛКА: Самотестування не пройдено!");
enterMaintenanceMode();
return;
}
// Ініціалізація датчиків
initializeSensors();
// Завантаження розкладу поливу
loadWateringSchedule();
// Початковий стан
state.stats.uptime = millis();
logEvent("SYSTEM_STARTUP", "Система запущена успішно");
Serial.println("Розширена система поливу готова до роботи");
displayWelcomeMessage();
}
void loop() {
// Оновлення системного часу
state.stats.uptime = millis();
// Перевірка аварійної зупинки
if (checkEmergencyConditions()) {
handleEmergencyStop();
return;
}
// Читання датчиків (кожні 30 секунд)
static unsigned long lastSensorRead = 0;
if (millis() - lastSensorRead >= 30000) {
readAllSensors();
updateSensorHistory();
lastSensorRead = millis();
}
// Отримання прогнозу погоди (кожні 2 години)
static unsigned long lastWeatherUpdate = 0;
if (config.weatherIntegration && millis() - lastWeatherUpdate >= 7200000) {
updateWeatherData();
lastWeatherUpdate = millis();
}
// Основна логіка поливу
if (config.systemEnabled && !state.emergencyStop && !state.maintenanceMode) {
processWateringLogic();
}
// Оновлення дисплея (кожні 5 секунд)
static unsigned long lastDisplayUpdate = 0;
if (millis() - lastDisplayUpdate >= 5000) {
updateDisplay();
lastDisplayUpdate = millis();
}
// Збереження даних на SD карту (кожні 10 хвилин)
static unsigned long lastDataSave = 0;
if (millis() - lastDataSave >= 600000) {
saveDataToSD();
lastDataSave = millis();
}
// Перевірка стану системи
performSystemHealthCheck();
// Енергозбереження
if (shouldEnterSleepMode()) {
enterSleepMode();
}
delay(1000); // Основний цикл
}
void processWateringLogic() {
DateTime now = rtc.now();
// Перевірка нічного режиму
if (config.nightModeEnabled && isNightTime(now)) {
// В нічному режимі тільки екстрений полив
for (int zone = 0; zone < 4; zone++) {
if (state.zones[zone].currentMoisture < config.zones[zone].moistureMin * 0.5) {
logEvent("NIGHT_EMERGENCY_WATERING", "Екстрений полив зони " + String(zone + 1));
startWateringZone(zone, config.zones[zone].wateringDuration * 0.5);
}
}
return;
}
// Перевірка дощу
if (state.sensors.rainDetected || state.todayRainfall > config.rainThreshold) {
stopAllWatering();
logEvent("RAIN_DETECTED", "Полив припинено через дощ");
return;
}
// Підрахунок активних зон
int activeZones = countActiveZones();
// Основна логіка поливу для кожної зони
for (int zone = 0; zone < 4; zone++) {
if (!config.zones[zone].enabled) continue;
// Перевірка розкладу поливу
if (!isScheduledTime(zone, now)) continue;
// Перевірка мінімального інтервалу
if (!checkMinimumInterval(zone)) continue;
// Розрахунок необхідності поливу з адаптивними параметрами
if (shouldStartWatering(zone)) {
// Перевірка обмеження одночасних зон
if (activeZones >= config.maxConcurrentZones) {
logEvent("ZONE_QUEUED", "Зона " + String(zone + 1) + " додана в чергу");
continue;
}
// Розрахунок адаптивної тривалості поливу
int adaptiveDuration = calculateAdaptiveWateringTime(zone);
startWateringZone(zone, adaptiveDuration);
activeZones++;
}
// Перевірка автоматичної зупинки поливу
if (state.zones[zone].isWatering && shouldStopWatering(zone)) {
stopWateringZone(zone);
}
}
}
bool shouldStartWatering(int zone) {
int currentMoisture = state.zones[zone].currentMoisture;
int threshold = config.zones[zone].moistureMin;
// Адаптивна корекція порогу за температурою
if (state.sensors.temperature > 25) {
threshold += (state.sensors.temperature - 25) * config.zones[zone].temperatureCompensation;
}
// Корекція за вологістю повітря
if (state.sensors.humidity < 50) {
threshold += (50 - state.sensors.humidity) * config.zones[zone].humidityCompensation;
}
// Сезонна корекція
DateTime now = rtc.now();
float seasonalFactor = getSeasonalFactor(now.month());
threshold *= seasonalFactor;
return currentMoisture < threshold;
}
int calculateAdaptiveWateringTime(int zone) {
int baseDuration = config.zones[zone].wateringDuration;
// Корекція за температурою (жарче = довше)
float tempFactor = 1.0;
if (state.sensors.temperature > 25) {
tempFactor = 1.0 + (state.sensors.temperature - 25) * 0.02; // +2% на градус
}
// Корекція за вологістю повітря (сухіше = довше)
float humidityFactor = 1.0;
if (state.sensors.humidity < 50) {
humidityFactor = 1.0 + (50 - state.sensors.humidity) * 0.01; // +1% на відсоток
}
// Корекція за поточною вологістю ґрунту
float moistureFactor = 1.0;
int currentMoisture = state.zones[zone].currentMoisture;
int targetMoisture = (config.zones[zone].moistureMin + config.zones[zone].moistureMax) / 2;
if (currentMoisture < targetMoisture) {
moistureFactor = 1.0 + (targetMoisture - currentMoisture) * 0.01;
}
// Сезонна корекція
DateTime now = rtc.now();
float seasonalFactor = config.zones[zone].seasonalMultiplier * getSeasonalFactor(now.month());
// Фінальний розрахунок
int adaptiveDuration = baseDuration * tempFactor * humidityFactor * moistureFactor * seasonalFactor;
// Обмеження мінімальним та максимальним часом
adaptiveDuration = constrain(adaptiveDuration, 60, config.maxWateringTime * 60);
return adaptiveDuration;
}
void startWateringZone(int zone, int duration) {
if (zone < 0 || zone >= 4 || !config.zones[zone].enabled) return;
Serial.print("Початок адаптивного поливу зони ");
Serial.print(zone + 1);
Serial.print(" на ");
Serial.print(duration);
Serial.println(" секунд");
// Перевірка тиску води
if (state.sensors.waterPressure < config.minWaterPressure) {
logEvent("LOW_PRESSURE_ERROR", "Низький тиск води - полив відмінено");
return;
}
// Увімкнення насоса з плавним запуском
if (!isPumpRunning()) {
startPumpWithSoftStart();
}
// Відкриття клапана зони з затримкою
delay(2000); // Час на набір тиску
digitalWrite(config.zones[zone].relayPin, LOW);
// Оновлення стану зони
state.zones[zone].isWatering = true;
state.zones[zone].wateringStartTime = millis();
// Розрахунок очікуваної витрати води
float expectedWaterUsage = (config.zones[zone].flowRate * duration) / 60.0;
// Логування події
DynamicJsonDocument logData(256);
logData["zone"] = zone + 1;
logData["duration"] = duration;
logData["expectedWater"] = expectedWaterUsage;
logData["moisture"] = state.zones[zone].currentMoisture;
logData["temperature"] = state.sensors.temperature;
logData["humidity"] = state.sensors.humidity;
String logString;
serializeJson(logData, logString);
logEvent("ADAPTIVE_WATERING_START", logString);
// Оновлення дисплея
updateWateringDisplay(zone, duration);
}
void performAdvancedDiagnostics() {
Serial.println("=== Розширена діагностика системи ===");
// Тестування всіх датчиків
testAllSensors();
// Тестування електромагнітних клапанів
testAllValves();
// Тестування системи живлення
testPowerSystem();
// Тестування зв'язку з зовнішніми сервісами
testConnectivity();
// Аналіз історичних даних
analyzeHistoricalData();
// Генерація звіту діагностики
generateDiagnosticReport();
}
void testAllSensors() {
Serial.println("Тестування датчиків:");
// Тест датчиків вологості ґрунту
for (int i = 0; i < 4; i++) {
int rawValue = analogRead(config.zones[i].soilSensorPin);
int moisture = map(rawValue, 0, 1023, 0, 100);
Serial.print("Датчик вологості зони ");
Serial.print(i + 1);
Serial.print(": ");
Serial.print(moisture);
Serial.print("% (raw: ");
Serial.print(rawValue);
Serial.println(")");
if (rawValue < 10 || rawValue > 1020) {
Serial.println(" УВАГА: Можлива несправність датчика!");
state.zones[i].lastError = "SENSOR_FAULT";
}
}
// Тест датчика температури
sensors.requestTemperatures();
float temp = sensors.getTempCByIndex(0);
Serial.print("Температура: ");
Serial.println(temp);
if (temp < -40 || temp > 85) {
Serial.println(" УВАГА: Датчик температури поза діапазоном!");
logEvent("TEMP_SENSOR_ERROR", "Температура поза діапазоном: " + String(temp));
}
// Тест тиску води
float pressure = readWaterPressure();
Serial.print("Тиск води: ");
Serial.println(pressure);
if (pressure < 0.1 || pressure > 10) {
Serial.println(" УВАГА: Датчик тиску показує неправдоподібні значення!");
logEvent("PRESSURE_SENSOR_ERROR", "Тиск поза діапазоном: " + String(pressure));
}
}
void optimizeWateringSchedule() {
Serial.println("Оптимізація розкладу поливу на основі історичних даних...");
// Аналіз ефективності поточного розкладу
for (int zone = 0; zone < 4; zone++) {
if (!config.zones[zone].enabled) continue;
// Розрахунок середньої витрати води
float avgWaterUsage = state.zones[zone].totalWaterUsed / max(1, state.zones[zone].wateringCycles);
// Аналіз частоти поливу
int wateringFrequency = calculateWateringFrequency(zone);
// Оптимізація тривалості поливу
if (avgWaterUsage > config.zones[zone].flowRate * 1.5) {
// Зменшити тривалість поливу, збільшити частоту
config.zones[zone].wateringDuration *= 0.9;
Serial.print("Зона ");
Serial.print(zone + 1);
Serial.println(": Зменшено тривалість поливу");
}
// Адаптація до сезонних змін
DateTime now = rtc.now();
float seasonalAdjustment = getSeasonalAdjustment(now.month());
config.zones[zone].seasonalMultiplier = seasonalAdjustment;
Serial.print("Зона ");
Serial.print(zone + 1);
Serial.print(" - Сезонний коефіцієнт: ");
Serial.println(seasonalAdjustment);
}
// Збереження оптимізованих налаштувань
saveConfiguration();
logEvent("SCHEDULE_OPTIMIZED", "Розклад поливу оптимізовано");
}
// Функція машинного навчання для прогнозування потреб у поливі
float predictWateringNeed(int zone) {
// Простий алгоритм лінійної регресії на основі історичних даних
struct DataPoint {
float temperature;
float humidity;
float soilMoisture;
float waterUsed;
};
// Завантаження останніх 30 записів для зони
DataPoint history[30];
int dataPoints = loadHistoricalData(zone, history, 30);
if (dataPoints < 10) {
// Недостатньо даних для прогнозування
return config.zones[zone].wateringDuration;
}
// Розрахунок кореляцій
float tempCorrelation = calculateCorrelation(history, dataPoints, "temperature");
float humidityCorrelation = calculateCorrelation(history, dataPoints, "humidity");
float moistureCorrelation = calculateCorrelation(history, dataPoints, "moisture");
// Прогнозування на основі поточних умов
float predictedNeed = config.zones[zone].wateringDuration;
predictedNeed += (state.sensors.temperature - 25) * tempCorrelation * 10;
predictedNeed += (50 - state.sensors.humidity) * humidityCorrelation * 5;
predictedNeed += (config.zones[zone].moistureMin - state.zones[zone].currentMoisture) * moistureCorrelation * 2;
// Обмеження результату
predictedNeed = constrain(predictedNeed, 30, config.maxWateringTime * 60);
return predictedNeed;
}
void handleMaintenanceMode() {
if (!state.maintenanceMode) return;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("РЕЖИМ ОБСЛУГОВУВАННЯ");
lcd.setCursor(0, 1);
lcd.print("Система призупинена");
// Вимкнення всіх активних поливів
stopAllWatering();
// Меню обслуговування
static int menuItem = 0;
static unsigned long lastButtonPress = 0;
// Перелік доступних операцій
String menuItems[] = {
"1. Тест датчиків",
"2. Тест клапанів",
"3. Калібрування",
"4. Скидання налаштувань",
"5. Вихід з режиму"
};
lcd.setCursor(0, 2);
lcd.print(menuItems[menuItem]);
// Обробка кнопок (спрощено)
if (digitalRead(BUTTON_UP) == LOW && millis() - lastButtonPress > 300) {
menuItem = (menuItem - 1 + 5) % 5;
lastButtonPress = millis();
}
if (digitalRead(BUTTON_DOWN) == LOW && millis() - lastButtonPress > 300) {
menuItem = (menuItem + 1) % 5;
lastButtonPress = millis();
}
if (digitalRead(BUTTON_SELECT) == LOW && millis() - lastButtonPress > 300) {
executeMaintenanceAction(menuItem);
lastButtonPress = millis();
}
}
void saveConfiguration() {
// Збереження конфігурації в EEPROM з контрольною сумою
int addr = 0;
// Магічне число для перевірки валідності
EEPROM.put(addr, 0xABCD);
addr += sizeof(int);
// Збереження основної конфігурації
EEPROM.put(addr, config);
addr += sizeof(SystemConfig);
// Розрахунок та збереження контрольної суми
uint16_t checksum = calculateChecksum((uint8_t*)&config, sizeof(SystemConfig));
EEPROM.put(addr, checksum);
Serial.println("Конфігурація збережена в EEPROM");
logEvent("CONFIG_SAVED", "Конфігурація збережена");
}
bool loadConfiguration() {
int addr = 0;
// Перевірка магічного числа
int magic;
EEPROM.get(addr, magic);
if (magic != 0xABCD) {
Serial.println("EEPROM порожній, завантаження конфігурації за замовчуванням");
loadDefaultConfiguration();
return false;
}
addr += sizeof(int);
// Завантаження конфігурації
SystemConfig tempConfig;
EEPROM.get(addr, tempConfig);
addr += sizeof(SystemConfig);
// Перевірка контрольної суми
uint16_t storedChecksum;
EEPROM.get(addr, storedChecksum);
uint16_t calculatedChecksum = calculateChecksum((uint8_t*)&tempConfig, sizeof(SystemConfig));
if (storedChecksum != calculatedChecksum) {
Serial.println("Помилка контрольної суми EEPROM, завантаження за замовчуванням");
loadDefaultConfiguration();
return false;
}
// Конфігурація валідна
config = tempConfig;
Serial.println("Конфігурація завантажена з EEPROM");
return true;
}
Налаштування адаптивних алгоритмів
Алгоритм динамічної корекції поливу:
float calculateDynamicWateringAdjustment(int zone) {
float adjustment = 1.0; // Базовий коефіцієнт
// Аналіз тренду вологості за останні 24 години
float moistureTrend = analyzeMoistureTrend(zone, 24);
if (moistureTrend < -0.5) {
// Швидке висихання - збільшити полив
adjustment += 0.3;
} else if (moistureTrend > 0.2) {
// Повільне висихання - зменшити полив
adjustment -= 0.2;
}
// Корекція за погодними умовами
if (state.sensors.temperature > 30) {
adjustment += (state.sensors.temperature - 30) * 0.02;
}
if (state.sensors.humidity < 40) {
adjustment += (40 - state.sensors.humidity) * 0.01;
}
// Корекція за типом рослини
if (config.zones[zone].plantType == "tomato") {
// Помідори потребують стабільної вологості
adjustment *= 1.1;
} else if (config.zones[zone].plantType == "cactus") {
// Кактуси потребують менше води
adjustment *= 0.5;
}
return constrain(adjustment, 0.3, 2.0);
}
Рекомендація експерта: Для максимальної ефективності системи веддіть журнал поливу протягом першого сезону та аналізуйте результати. Це дозволить точно налаштувати алгоритми для ваших конкретних умов та культур.
Тестування та калібрування
Системне тестування компонентів
Якісне тестування – основа надійної роботи системи автоматичного поливу. Кожен компонент повинен бути ретельно перевірений перед введенням системи в експлуатацію.
Протокол комплексного тестування:
struct TestResult {
String componentName;
bool passed;
String errorMessage;
float measuredValue;
float expectedValue;
unsigned long testTime;
};
class SystemTester {
private:
TestResult results[20];
int testCount = 0;
public:
void runFullSystemTest() {
Serial.println("=== ПОЧАТОК ПОВНОГО ТЕСТУВАННЯ СИСТЕМИ ===");
testCount = 0;
// Тестування живлення
testPowerSupply();
// Тестування датчиків
testSensorAccuracy();
// Тестування виконавчих механізмів
testActuators();
// Тестування гідравлічної системи
testHydraulicSystem();
// Тестування алгоритмів управління
testControlAlgorithms();
// Тестування системи зв'язку
testCommunication();
// Генерація звіту
generateTestReport();
}
void testPowerSupply() {
Serial.println("Тестування системи живлення...");
// Тест напруги 12V
float voltage12V = measureVoltage(VOLTAGE_12V_PIN);
addTestResult("12V Power Supply",
voltage12V >= 11.5 && voltage12V <= 12.5,
voltage12V < 11.5 ? "Низька напруга" : (voltage12V > 12.5 ? "Висока напруга" : ""),
voltage12V, 12.0);
// Тест напруги 5V
float voltage5V = measureVoltage(VOLTAGE_5V_PIN);
addTestResult("5V Power Supply",
voltage5V >= 4.8 && voltage5V <= 5.2,
voltage5V < 4.8 ? "Низька напруга" : (voltage5V > 5.2 ? "Висока напруга" : ""),
voltage5V, 5.0);
// Тест споживання струму в режимі очікування
float currentIdle = measureCurrent(CURRENT_SENSOR_PIN);
addTestResult("Idle Current",
currentIdle < 100, // мА
currentIdle >= 100 ? "Високе споживання в режимі очікування" : "",
currentIdle, 50);
}
void testSensorAccuracy() {
Serial.println("Тестування точності датчиків...");
// Тест датчиків вологості ґрунту
for (int zone = 0; zone < 4; zone++) {
if (!config.zones[zone].enabled) continue;
// Тест в сухому стані
Serial.print("Помістіть датчик зони ");
Serial.print(zone + 1);
Serial.println(" в сухий ґрунт і натисніть кнопку");
waitForButtonPress();
int dryReading = 0;
for (int i = 0; i < 10; i++) {
dryReading += analogRead(config.zones[zone].soilSensorPin);
delay(100);
}
dryReading /= 10;
// Тест у вологому стані
Serial.println("Помістіть датчик у вологий ґрунт і натисніть кнопку");
waitForButtonPress();
int wetReading = 0;
for (int i = 0; i < 10; i++) {
wetReading += analogRead(config.zones[zone].soilSensorPin);
delay(100);
}
wetReading /= 10;
// Перевірка діапазону
int range = abs(dryReading - wetReading);
String zoneName = "Soil Sensor Zone " + String(zone + 1);
addTestResult(zoneName,
range > 200, // Мінімальна різниця для надійної роботи
range <= 200 ? "Малий діапазон вимірювань" : "",
range, 400);
// Збереження калібрувальних значень
config.zones[zone].dryValue = max(dryReading, wetReading);
config.zones[zone].wetValue = min(dryReading, wetReading);
}
// Тест датчика температури
testTemperatureSensor();
// Тест датчика тиску води
testPressureSensor();
}
void testTemperatureSensor() {
Serial.println("Тестування датчика температури...");
// Множинні вимірювання для перевірки стабільності
float tempReadings[10];
for (int i = 0; i < 10; i++) {
sensors.requestTemperatures();
tempReadings[i] = sensors.getTempCByIndex(0);
delay(1000);
}
// Розрахунок середнього та стандартного відхилення
float avgTemp = 0;
for (int i = 0; i < 10; i++) {
avgTemp += tempReadings[i];
}
avgTemp /= 10;
float stdDev = 0;
for (int i = 0; i < 10; i++) {
stdDev += pow(tempReadings[i] - avgTemp, 2);
}
stdDev = sqrt(stdDev / 10);
// Перевірка стабільності (стандартне відхилення < 1°C)
addTestResult("Temperature Sensor Stability",
stdDev < 1.0,
stdDev >= 1.0 ? "Нестабільні показники температури" : "",
stdDev, 0.5);
// Перевірка реалістичності показників
addTestResult("Temperature Sensor Range",
avgTemp > -10 && avgTemp < 50,
avgTemp <= -10 || avgTemp >= 50 ? "Температура поза реальним діапазоном" : "",
avgTemp, 25);
}
void testActuators() {
Serial.println("Тестування виконавчих механізмів...");
// Тест реле клапанів
for (int zone = 0; zone < 4; zone++) {
if (!config.zones[zone].enabled) continue;
Serial.print("Тестування клапана зони ");
Serial.println(zone + 1);
// Увімкнення клапана
digitalWrite(config.zones[zone].relayPin, LOW);
delay(1000);
// Перевірка зворотного зв'язку (якщо є датчик потоку)
float flowRate = measureFlowRate(zone);
// Вимкнення клапана
digitalWrite(config.zones[zone].relayPin, HIGH);
delay(1000);
// Перевірка зупинки потоку
float flowRateOff = measureFlowRate(zone);
String valveName = "Valve Zone " + String(zone + 1);
addTestResult(valveName,
flowRate > 0.1 && flowRateOff < 0.1,
flowRate <= 0.1 ? "Клапан не відкривається" :
(flowRateOff >= 0.1 ? "Клапан не закривається" : ""),
flowRate, 2.0);
}
// Тест основного насоса
testMainPump();
}
void testMainPump() {
Serial.println("Тестування основного насоса...");
// Вимірювання тиску без насоса
float pressureOff = readWaterPressure();
// Увімкнення насоса
digitalWrite(PUMP_RELAY_PIN, LOW);
delay(5000); // Час на розгін
// Вимірювання тиску з насосом
float pressureOn = readWaterPressure();
// Вимкнення насоса
digitalWrite(PUMP_RELAY_PIN, HIGH);
// Перевірка підвищення тиску
float pressureIncrease = pressureOn - pressureOff;
addTestResult("Main Pump",
pressureIncrease > 0.5, // Мінімальне підвищення тиску 0.5 бар
pressureIncrease <= 0.5 ? "Насос не створює достатнього тиску" : "",
pressureIncrease, 1.5);
}
void testHydraulicSystem() {
Serial.println("Тестування гідравлічної системи...");
// Тест герметичності системи
testSystemLeakage();
// Тест рівномірності розподілу
testWaterDistribution();
// Тест часу відгуку
testSystemResponseTime();
}
void testSystemLeakage() {
Serial.println("Тест герметичності системи...");
// Заповнення системи та створення тиску
digitalWrite(PUMP_RELAY_PIN, LOW);
delay(10000); // 10 секунд розгону
float initialPressure = readWaterPressure();
// Вимкнення насоса та спостереження за тиском
digitalWrite(PUMP_RELAY_PIN, HIGH);
delay(60000); // Очікування 1 хвилину
float finalPressure = readWaterPressure();
float pressureDrop = initialPressure - finalPressure;
// Допустима втрата тиску: 0.1 бар за хвилину
addTestResult("System Leakage",
pressureDrop < 0.1,
pressureDrop >= 0.1 ? "Виявлено витік в системі" : "",
pressureDrop, 0.05);
}
void testWaterDistribution() {
Serial.println("Тест рівномірності розподілу води...");
// Активація всіх зон по черзі та вимірювання витрати
float flowRates[4];
digitalWrite(PUMP_RELAY_PIN, LOW);
delay(3000);
for (int zone = 0; zone < 4; zone++) {
if (!config.zones[zone].enabled) {
flowRates[zone] = 0;
continue;
}
// Відкриття клапана зони
digitalWrite(config.zones[zone].relayPin, LOW);
delay(5000); // Стабілізація потоку
// Вимірювання витрати
flowRates[zone] = measureFlowRate(zone);
// Закриття клапана
digitalWrite(config.zones[zone].relayPin, HIGH);
delay(2000);
}
digitalWrite(PUMP_RELAY_PIN, HIGH);
// Аналіз рівномірності
float avgFlow = 0;
int activeZones = 0;
for (int i = 0; i < 4; i++) {
if (flowRates[i] > 0) {
avgFlow += flowRates[i];
activeZones++;
}
}
if (activeZones > 0) {
avgFlow /= activeZones;
// Перевірка відхилення від середнього
bool uniformDistribution = true;
for (int i = 0; i < 4; i++) {
if (flowRates[i] > 0) {
float deviation = abs(flowRates[i] - avgFlow) / avgFlow;
if (deviation > 0.2) { // Відхилення більше 20%
uniformDistribution = false;
break;
}
}
}
addTestResult("Water Distribution Uniformity",
uniformDistribution,
!uniformDistribution ? "Нерівномірний розподіл води між зонами" : "",
avgFlow, avgFlow);
}
}
void generateTestReport() {
Serial.println("\n=== ЗВІТ ТЕСТУВАННЯ СИСТЕМИ ===");
int passedTests = 0;
int totalTests = testCount;
for (int i = 0; i < testCount; i++) {
Serial.print(results[i].componentName);
Serial.print(": ");
if (results[i].passed) {
Serial.print("ПРОЙДЕНО");
passedTests++;
} else {
Serial.print("НЕ ПРОЙДЕНО - ");
Serial.print(results[i].errorMessage);
}
Serial.print(" (");
Serial.print(results[i].measuredValue);
Serial.print(" з очікуваних ");
Serial.print(results[i].expectedValue);
Serial.println(")");
}
Serial.println("\n=== ПІДСУМОК ===");
Serial.print("Пройдено тестів: ");
Serial.print(passedTests);
Serial.print(" з ");
Serial.println(totalTests);
float successRate = (float)passedTests / totalTests * 100;
Serial.print("Відсоток успішності: ");
Serial.print(successRate);
Serial.println("%");
if (successRate >= 90) {
Serial.println("СИСТЕМА ГОТОВА ДО ЕКСПЛУАТАЦІЇ");
} else if (successRate >= 70) {
Serial.println("СИСТЕМА ПОТРЕБУЄ НАЛАШТУВАННЯ");
} else {
Serial.println("СИСТЕМА ПОТРЕБУЄ СЕРЙОЗНОГО РЕМОНТУ");
}
// Збереження звіту на SD карту
saveTestReportToSD();
}
private:
void addTestResult(String name, bool passed, String error, float measured, float expected) {
if (testCount < 20) {
results[testCount] = {name, passed, error, measured, expected, millis()};
testCount++;
}
}
};
Процедури калібрування датчиків
Детальна калібрування датчиків вологості ґрунту:
class SensorCalibrator {
public:
struct CalibrationData {
int dryValue;
int wetValue;
float temperatureCompensation;
float salinityCompensation;
bool isCalibrated;
};
CalibrationData calibrationData[4];
void calibrateAllSoilSensors() {
Serial.println("=== КАЛІБРУВАННЯ ДАТЧИКІВ ВОЛОГОСТІ ҐРУНТУ ===");
for (int zone = 0; zone < 4; zone++) {
if (!config.zones[zone].enabled) continue;
Serial.print("\nКалібрування датчика зони ");
Serial.println(zone + 1);
calibrateSoilSensor(zone);
}
// Збереження калібрувальних даних
saveCalibrationData();
Serial.println("Калібрування завершено!");
}
void calibrateSoilSensor(int zone) {
// Крок 1: Калібрування в повітрі (сухий стан)
Serial.println("Крок 1: Помістіть датчик у повітря і натисніть кнопку");
waitForButtonPress();
int airReading = getStableReading(config.zones[zone].soilSensorPin, 20);
Serial.print("Показник у повітрі: ");
Serial.println(airReading);
// Крок 2: Калібрування в воді (максимально вологий стан)
Serial.println("Крок 2: Помістіть датчик у воду і натисніть кнопку");
waitForButtonPress();
int waterReading = getStableReading(config.zones[zone].soilSensorPin, 20);
Serial.print("Показник у воді: ");
Serial.println(waterReading);
// Крок 3: Калібрування в сухому ґрунті
Serial.println("Крок 3: Помістіть датчик у сухий ґрунт і натисніть кнопку");
waitForButtonPress();
int drySoilReading = getStableReading(config.zones[zone].soilSensorPin, 20);
Serial.print("Показник у сухому ґрунті: ");
Serial.println(drySoilReading);
// Крок 4: Калібрування в мокрому ґрунті
Serial.println("Крок 4: Помістіть датчик у мокрий ґрунт і натисніть кнопку");
waitForButtonPress();
int wetSoilReading = getStableReading(config.zones[zone].soilSensorPin, 20);
Serial.print("Показник у мокрому ґрунті: ");
Serial.println(wetSoilReading);
// Розрахунок калібрувальних коефіцієнтів
calibrationData[zone].dryValue = drySoilReading;
calibrationData[zone].wetValue = wetSoilReading;
// Перевірка валідності калібрування
int range = abs(drySoilReading - wetSoilReading);
if (range < 100) {
Serial.println("УВАГА: Малий діапазон калібрування. Перевірте датчик!");
calibrationData[zone].isCalibrated = false;
} else {
calibrationData[zone].isCalibrated = true;
Serial.println("Калібрування зони успішне");
}
// Тестова перевірка
Serial.println("Тестова перевірка калібрування:");
for (int i = 0; i < 5; i++) {
int rawReading = analogRead(config.zones[zone].soilSensorPin);
int moisture = convertToMoisture(zone, rawReading);
Serial.print("Raw: ");
Serial.print(rawReading);
Serial.print(" -> Вологість: ");
Serial.print(moisture);
Serial.println("%");
delay(2000);
}
}
int convertToMoisture(int zone, int rawValue) {
if (!calibrationData[zone].isCalibrated) {
// Використання значень за замовчуванням
return map(rawValue, 1023, 0, 0, 100);
}
// Конвертація з урахуванням калібрування
int moisture = map(rawValue,
calibrationData[zone].dryValue,
calibrationData[zone].wetValue,
0, 100);
// Обмеження діапазону
moisture = constrain(moisture, 0, 100);
// Температурна компенсація
float tempCompensation = (state.sensors.temperature - 20) *
calibrationData[zone].temperatureCompensation;
moisture += tempCompensation;
return constrain(moisture, 0, 100);
}
void performAdvancedCalibration(int zone) {
Serial.println("Розширене калібрування датчика...");
// Калібрування температурної компенсації
calibrateTemperatureCompensation(zone);
// Калібрування впливу солей у ґрунті
calibrateSalinityCompensation(zone);
// Калібрування довгострокової стабільності
calibrateLongTermStability(zone);
}
void calibrateTemperatureCompensation(int zone) {
Serial.println("Калібрування температурної компенсації...");
// Вимірювання при різних температурах
struct TempCalibPoint {
float temperature;
int moistureReading;
};
TempCalibPoint points[5];
int pointCount = 0;
// Збір даних при різних температурах
Serial.println("Помістіть датчик у ґрунт середньої вологості");
Serial.println("Натискайте кнопку при різних температурах:");
while (pointCount < 5) {
Serial.print("Точка ");
Serial.print(pointCount + 1);
Serial.print(" - поточна температура: ");
sensors.requestTemperatures();
float currentTemp = sensors.getTempCByIndex(0);
Serial.print(currentTemp);
Serial.println("°C. Натисніть кнопку...");
waitForButtonPress();
points[pointCount].temperature = currentTemp;
points[pointCount].moistureReading = getStableReading(config.zones[zone].soilSensorPin, 10);
Serial.print("Записано: ");
Serial.print(currentTemp);
Serial.print("°C -> ");
Serial.println(points[pointCount].moistureReading);
pointCount++;
delay(2000);
}
// Розрахунок температурного коефіцієнта методом найменших квадратів
float temperatureCoeff = calculateTemperatureCoefficient(points, pointCount);
calibrationData[zone].temperatureCompensation = temperatureCoeff;
Serial.print("Температурний коефіцієнт: ");
Serial.println(temperatureCoeff);
}
private:
int getStableReading(int pin, int samples) {
long sum = 0;
int validSamples = 0;
// Відкидання аномальних значень
int readings[samples];
for (int i = 0; i < samples; i++) {
readings[i] = analogRead(pin);
delay(100);
}
// Сортування для видалення викидів
sortArray(readings, samples);
// Використання середніх 60% значень
int startIdx = samples * 0.2;
int endIdx = samples * 0.8;
for (int i = startIdx; i < endIdx; i++) {
sum += readings[i];
validSamples++;
}
return validSamples > 0 ? sum / validSamples : 0;
}
float calculateTemperatureCoefficient(TempCalibPoint* points, int count) {
// Лінійна регресія для знаходження коефіцієнта температурної залежності
float sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
for (int i = 0; i < count; i++) {
sumX += points[i].temperature;
sumY += points[i].moistureReading;
sumXY += points[i].temperature * points[i].moistureReading;
sumX2 += points[i].temperature * points[i].temperature;
}
float slope = (count * sumXY - sumX * sumY) / (count * sumX2 - sumX * sumX);
// Конвертація в коефіцієнт компенсації (зворотний знак)
return -slope / 10.0; // Нормалізація
}
};
Рекомендація експерта: Проводьте повне калібрування системи щонайменше двічі на рік – на початку та в кінці поливального сезону. Це забезпечить стабільну точність вимірювань протягом усього періоду експлуатації.
Обслуговування та поради експлуатації
Планове технічне обслуговування
Регулярне обслуговування системи автоматичного поливу критично важливе для її довговічної та ефективної роботи. Правильно організоване ТО може продовжити термін служби системи до 15-20 років.
Щотижневе обслуговування (5-10 хвилин):
-
Візуальний огляд системи:
- Перевірка на витоки води біля клапанів та з’єднань
- Огляд електричних з’єднань на предмет корозії
- Перевірка чистоти сонячних панелей (якщо є)
- Контроль рівня води в резервуарі
-
Перевірка показників системи:
void weeklySystemCheck() { Serial.println("=== ЩОТИЖНЕВА ПЕРЕВІРКА СИСТЕМИ ==="); // Перевірка основних параметрів float pressure = readWaterPressure(); Serial.print("Тиск води: "); Serial.print(pressure); Serial.println(" бар"); if (pressure < config.minWaterPressure) { logEvent("LOW_PRESSURE_WARNING", "Низький тиск води: " + String(pressure)); sendAlert("Увага: Низький тиск в системі поливу"); } // Перевірка датчиків for (int zone = 0; zone < 4; zone++) { if (!config.zones[zone].enabled) continue; int moisture = readSoilMoisture(zone); Serial.print("Зона "); Serial.print(zone + 1); Serial.print(" - вологість: "); Serial.print(moisture); Serial.println("%"); // Перевірка на аномальні показники if (moisture < 5 || moisture > 95) { logEvent("SENSOR_ANOMALY", "Аномальні показники зоні " + String(zone + 1)); } } // Перевірка витрат води float weeklyConsumption = calculateWeeklyWaterUsage(); Serial.print("Витрати води за тиждень: "); Serial.print(weeklyConsumption); Serial.println(" літрів"); // Порівняння з попереднім тижнем float previousWeekConsumption = getPreviousWeekConsumption(); if (weeklyConsumption > previousWeekConsumption * 1.5) { logEvent("HIGH_WATER_USAGE", "Підвищені витрати води"); sendAlert("Підвищені витрати води. Можливий витік."); } }
Щомісячне обслуговування (30-45 хвилин):
-
Очищення фільтрів:
void cleanFilters() { Serial.println("Процедура очищення фільтрів..."); // Зупинка системи stopAllWatering(); delay(5000); // Збереження поточного тиску float pressureBeforeClean = readWaterPressure(); // Інструкції для користувача lcd.clear(); lcd.setCursor(0, 0); lcd.print("ОБСЛУГОВУВАННЯ"); lcd.setCursor(0, 1); lcd.print("Очистіть фільтри"); lcd.setCursor(0, 2); lcd.print("Натисніть OK"); waitForButtonPress(); // Перевірка ефективності очищення delay(10000); // Час на очищення float pressureAfterClean = readWaterPressure(); float pressureImprovement = pressureAfterClean - pressureBeforeClean; if (pressureImprovement > 0.1) { Serial.println("Фільтри очищено успішно"); logEvent("FILTERS_CLEANED", "Поліпшення тиску: " + String(pressureImprovement)); } else { Serial.println("Можлива потреба в заміні фільтрів"); logEvent("FILTER_REPLACEMENT_NEEDED", "Очищення не покращило тиск"); } }
-
Калібрування датчиків: Проводьте швидке калібрування одного датчика на місяць для контролю дрейфу показників.
-
Перевірка електричних з’єднань:
- Затяжка клемних з’єднань
- Перевірка ізоляції проводів
- Тестування роботи реле та контакторів
Сезонне обслуговування (2-3 години):
Весняний запуск системи:
void springSystemStartup() {
Serial.println("=== ВЕСНЯНИЙ ЗАПУСК СИСТЕМИ ===");
// Крок 1: Перевірка зимових пошкоджень
if (!inspectWinterDamage()) {
Serial.println("Виявлено пошкодження. Ремонт потрібен перед запуском.");
return;
}
// Крок 2: Поступове заповнення системи
gradualSystemFilling();
// Крок 3: Тестування всіх зон
testAllZonesSpring();
// Крок 4: Калібрування датчиків після зими
recalibrateAfterWinter();
// Крок 5: Оновлення сезонних налаштувань
updateSeasonalSettings("spring");
// Крок 6: Перевірка резервного живлення
testBackupPower();
Serial.println("Весняний запуск завершено успішно");
logEvent("SPRING_STARTUP", "Система готова до поливального сезону");
}
void gradualSystemFilling() {
Serial.println("Поступове заповнення системи водою...");
// Початкове заповнення на низькому тиску
digitalWrite(PUMP_RELAY_PIN, LOW);
for (int pressure = 0; pressure < 20; pressure++) {
delay(10000); // 10 секунд на кожну ітерацію
float currentPressure = readWaterPressure();
Serial.print("Тиск: ");
Serial.print(currentPressure);
Serial.println(" бар");
// Перевірка на витоки під час заповнення
if (currentPressure < pressure * 0.1) {
Serial.println("УВАГА: Можливий великий витік!");
digitalWrite(PUMP_RELAY_PIN, HIGH);
logEvent("MAJOR_LEAK_DETECTED", "Витік виявлено під час заповнення");
return;
}
if (currentPressure >= config.minWaterPressure) {
break;
}
}
digitalWrite(PUMP_RELAY_PIN, HIGH);
Serial.println("Заповнення завершено");
}
Осіння підготовка до зими:
void winterSystemPreparation() {
Serial.println("=== ПІДГОТОВКА СИСТЕМИ ДО ЗИМИ ===");
// Крок 1: Збереження статистики сезону
saveSeasonStatistics();
// Крок 2: Створення резервної копії налаштувань
backupSystemSettings();
// Крок 3: Спорожнення водопровідної системи
drainWaterSystem();
// Крок 4: Консервація електроніки
preserveElectronics();
// Крок 5: Захист датчиків
protectSensorsFromFrost();
Serial.println("Підготовка до зими завершена");
logEvent("WINTER_PREPARATION", "Система законсервована на зиму");
}
void drainWaterSystem() {
Serial.println("Спорожнення водопровідної системи...");
// Відкриття всіх дренажних клапанів
for (int zone = 0; zone < 4; zone++) {
if (config.zones[zone].enabled) {
digitalWrite(config.zones[zone].relayPin, LOW);
}
}
// Включення насоса для повного спорожнення
digitalWrite(PUMP_RELAY_PIN, LOW);
delay(30000); // 30 секунд роботи
digitalWrite(PUMP_RELAY_PIN, HIGH);
// Продування системи стисненим повітрям (якщо можливо)
Serial.println("Рекомендується продути систему стисненим повітрям");
Serial.println("Натисніть кнопку після продування...");
waitForButtonPress();
// Закриття всіх клапанів
for (int zone = 0; zone < 4; zone++) {
digitalWrite(config.zones[zone].relayPin, HIGH);
}
Serial.println("Система спорожнена");
}
Діагностика та усунення неполадок
Система автоматичної діагностики:
class AutoDiagnostic {
private:
struct DiagnosticTest {
String testName;
bool (*testFunction)();
String failureMessage;
String solutionSteps;
int priority; // 1-критичний, 2-важливий, 3-рекомендований
};
DiagnosticTest tests[15];
int testCount = 0;
public:
void initializeDiagnostics() {
// Критичні тести
addTest("Power Supply", testPowerSupply,
"Проблеми з живленням",
"1.Перевірити запобіжники 2.Перевірити блок живлення 3.Перевірити з'єднання", 1);
addTest("Water Pressure", testWaterPressure,
"Недостатній тиск води",
"1.Перевірити насос 2.Очистити фільтри 3.Перевірити витоки", 1);
addTest("Main Controller", testMainController,
"Несправність основного контролера",
"1.Перезавантагити систему 2.Перевірити програмне забезпечення", 1);
// Важливі тести
addTest("Soil Sensors", testSoilSensors,
"Несправність датчиків ґрунту",
"1.Очистити датчики 2.Перевірити з'єднання 3.Рекалібрувати", 2);
addTest("Valve Operation", testValveOperation,
"Проблеми з клапанами",
"1.Перевірити електричні з'єднання 2.Очистити клапани 3.Замінити мембрани", 2);
// Додати інші тести...
}
void runFullDiagnostic() {
Serial.println("=== ПОВНА ДІАГНОСТИКА СИСТЕМИ ===");
int criticalFailures = 0;
int importantFailures = 0;
int recommendedFailures = 0;
for (int i = 0; i < testCount; i++) {
Serial.print("Тестування: ");
Serial.println(tests[i].testName);
bool result = tests[i].testFunction();
if (result) {
Serial.println(" ✓ ПРОЙДЕНО");
} else {
Serial.println(" ✗ НЕ ПРОЙДЕНО");
Serial.print(" Проблема: ");
Serial.println(tests[i].failureMessage);
Serial.print(" Рішення: ");
Serial.println(tests[i].solutionSteps);
// Підрахунок помилок за пріоритетом
if (tests[i].priority == 1) criticalFailures++;
else if (tests[i].priority == 2) importantFailures++;
else recommendedFailures++;
// Логування проблеми
logEvent("DIAGNOSTIC_FAILURE", tests[i].testName + ": " + tests[i].failureMessage);
}
delay(1000);
}
// Підсумок діагностики
Serial.println("\n=== ПІДСУМОК ДІАГНОСТИКИ ===");
Serial.print("Критичні помилки: ");
Serial.println(criticalFailures);
Serial.print("Важливі помилки: ");
Serial.println(importantFailures);
Serial.print("Рекомендації: ");
Serial.println(recommendedFailures);
if (criticalFailures > 0) {
Serial.println("СИСТЕМА ПОТРЕБУЄ НЕГАЙНОГО РЕМОНТУ!");
enterMaintenanceMode();
} else if (importantFailures > 0) {
Serial.println("Рекомендується усунути проблеми найближчим часом");
} else {
Serial.println("Система працює нормально");
}
}
private:
void addTest(String name, bool (*func)(), String failure, String solution, int priority) {
if (testCount < 15) {
tests[testCount] = {name, func, failure, solution, priority};
testCount++;
}
}
static bool testPowerSupply() {
float voltage12V = measureVoltage(VOLTAGE_12V_PIN);
float voltage5V = measureVoltage(VOLTAGE_5V_PIN);
return (voltage12V >= 11.5 && voltage12V <= 12.5) &&
(voltage5V >= 4.8 && voltage5V <= 5.2);
}
static bool testWaterPressure() {
float pressure = readWaterPressure();
return pressure >= config.minWaterPressure && pressure <= config.maxWaterPressure;
}
static bool testMainController() {
// Тест основних функцій контролера
DateTime now = rtc.now();
if (now.year() < 2020) return false; // Проблеми з RTC
// Тест пам'яті
if (!SD.begin(10)) return false;
// Тест зв'язку з датчиками
sensors.requestTemperatures();
float temp = sensors.getTempCByIndex(0);
if (temp < -40 || temp > 85) return false;
return true;
}
static bool testSoilSensors() {
for (int zone = 0; zone < 4; zone++) {
if (!config.zones[zone].enabled) continue;
int reading = analogRead(config.zones[zone].soilSensorPin);
// Перевірка на короткі замикання або обриви
if (reading < 10 || reading > 1020) return false;
// Перевірка стабільності показників
int readings[5];
for (int i = 0; i < 5; i++) {
readings[i] = analogRead(config.zones[zone].soilSensorPin);
delay(200);
}
// Розрахунок стандартного відхилення
float avg = 0;
for (int i = 0; i < 5; i++) avg += readings[i];
avg /= 5;
float stdDev = 0;
for (int i = 0; i < 5; i++) {
stdDev += pow(readings[i] - avg, 2);
}
stdDev = sqrt(stdDev / 5);
// Занадто великі коливання
if (stdDev > 50) return false;
}
return true;
}
static bool testValveOperation() {
// Тест кожного клапана окремо
for (int zone = 0; zone < 4; zone++) {
if (!config.zones[zone].enabled) continue;
// Вимірювання тиску до відкриття
float pressureBefore = readWaterPressure();
// Відкриття клапана
digitalWrite(config.zones[zone].relayPin, LOW);
delay(3000);
// Вимірювання тиску після відкриття
float pressureAfter = readWaterPressure();
// Закриття клапана
digitalWrite(config.zones[zone].relayPin, HIGH);
delay(1000);
// Перевірка зміни тиску (клапан повинен змінювати тиск)
if (abs(pressureAfter - pressureBefore) < 0.1) {
return false; // Клапан не працює
}
}
return true;
}
};
Оптимізація роботи системи
Адаптивна оптимізація на основі машинного навчання:
class SystemOptimizer {
private:
struct OptimizationData {
unsigned long timestamp;
float temperature;
float humidity;
int soilMoisture[4];
int wateringDuration[4];
float waterUsed[4];
float plantGrowthRate[4]; // Якщо є датчики росту
int userSatisfactionScore; // Ручний ввід користувача
};
OptimizationData history[100]; // Кільцевий буфер історії
int currentIndex = 0;
bool bufferFull = false;
public:
void collectOptimizationData() {
OptimizationData& current = history[currentIndex];
current.timestamp = millis();
current.temperature = state.sensors.temperature;
current.humidity = state.sensors.humidity;
for (int zone = 0; zone < 4; zone++) {
current.soilMoisture[zone] = state.zones[zone].currentMoisture;
current.wateringDuration[zone] = state.zones[zone].wateringStartTime > 0 ?
(millis() - state.zones[zone].wateringStartTime) / 1000 : 0;
current.waterUsed[zone] = calculateZoneWaterUsage(zone);
current.plantGrowthRate[zone] = measurePlantGrowth(zone);
}
current.userSatisfactionScore = getUserSatisfactionScore();
// Переміщення індексу
currentIndex = (currentIndex + 1) % 100;
if (currentIndex == 0) bufferFull = true;
}
void optimizeWateringSchedule() {
if (!bufferFull && currentIndex < 20) {
Serial.println("Недостатньо даних для оптимізації");
return;
}
Serial.println("=== ОПТИМІЗАЦІЯ РЕЖИМУ ПОЛИВУ ===");
// Аналіз ефективності поточних налаштувань
analyzeCurrentEfficiency();
// Пошук оптимальних параметрів для кожної зони
for (int zone = 0; zone < 4; zone++) {
if (!config.zones[zone].enabled) continue;
OptimalParams optimal = findOptimalParameters(zone);
Serial.print("Зона ");
Serial.print(zone + 1);
Serial.println(" - оптимальні параметри:");
Serial.print(" Поріг вологості: ");
Serial.println(optimal.moistureThreshold);
Serial.print(" Тривалість поливу: ");
Serial.println(optimal.wateringDuration);
Serial.print(" Інтервал поливу: ");
Serial.println(optimal.wateringInterval);
// Застосування оптимізованих параметрів
applyOptimalParameters(zone, optimal);
}
// Збереження оптимізованих налаштувань
saveConfiguration();
logEvent("SYSTEM_OPTIMIZED", "Параметри системи оптимізовано");
}
private:
struct OptimalParams {
int moistureThreshold;
int wateringDuration;
int wateringInterval;
float efficiency;
};
OptimalParams findOptimalParameters(int zone) {
OptimalParams best = {
config.zones[zone].moistureMin,
config.zones[zone].wateringDuration,
config.zones[zone].minInterval,
0.0
};
// Перебір різних комбінацій параметрів
for (int threshold = 20; threshold <= 50; threshold += 5) {
for (int duration = 300; duration <= 1800; duration += 300) {
for (int interval = 2; interval <= 24; interval += 2) {
float efficiency = calculateParameterEfficiency(zone, threshold, duration, interval);
if (efficiency > best.efficiency) {
best.moistureThreshold = threshold;
best.wateringDuration = duration;
best.wateringInterval = interval;
best.efficiency = efficiency;
}
}
}
}
return best;
}
float calculateParameterEfficiency(int zone, int threshold, int duration, int interval) {
float totalEfficiency = 0;
int samples = 0;
int dataCount = bufferFull ? 100 : currentIndex;
for (int i = 0; i < dataCount; i++) {
OptimizationData& data = history[i];
// Симуляція роботи з новими параметрами
bool wouldWater = data.soilMoisture[zone] < threshold;
if (wouldWater) {
// Розрахунок ефективності
float waterEfficiency = data.plantGrowthRate[zone] / max(1.0f, data.waterUsed[zone]);
float userSatisfaction = data.userSatisfactionScore / 10.0; // Нормалізація
float moistureStability = 1.0 - abs(data.soilMoisture[zone] - 60) / 60.0; // Оптимум 60%
// Комплексна оцінка ефективності
float efficiency = (waterEfficiency * 0.4 + userSatisfaction * 0.3 + moistureStability * 0.3);
totalEfficiency += efficiency;
samples++;
}
}
return samples > 0 ? totalEfficiency / samples : 0;
}
void analyzeCurrentEfficiency() {
Serial.println("Аналіз поточної ефективності системи:");
for (int zone = 0; zone < 4; zone++) {
if (!config.zones[zone].enabled) continue;
float avgWaterUsage = calculateAverageWaterUsage(zone);
float avgPlantHealth = calculateAveragePlantHealth(zone);
float moistureVariability = calculateMoistureVariability(zone);
Serial.print("Зона ");
Serial.print(zone + 1);
Serial.println(":");
Serial.print(" Середня витрата води: ");
Serial.print(avgWaterUsage);
Serial.println(" л/день");
Serial.print(" Здоров'я рослин: ");
Serial.print(avgPlantHealth);
Serial.println("/10");
Serial.print(" Стабільність вологості: ");
Serial.print(10 - moistureVariability);
Serial.println("/10");
}
}
int getUserSatisfactionScore() {
// Інтерактивний запит оцінки від користувача
static unsigned long lastRequest = 0;
static int lastScore = 7; // Значення за замовчуванням
// Запитувати оцінку раз на тиждень
if (millis() - lastRequest > 604800000) { // 7 днів
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Оцініть роботу");
lcd.setCursor(0, 1);
lcd.print("системи (1-10):");
int score = 5;
bool scoreEntered = false;
while (!scoreEntered) {
lcd.setCursor(0, 2);
lcd.print("Поточна оцінка: ");
lcd.print(score);
if (digitalRead(BUTTON_UP) == LOW) {
score = min(10, score + 1);
delay(300);
}
if (digitalRead(BUTTON_DOWN) == LOW) {
score = max(1, score - 1);
delay(300);
}
if (digitalRead(BUTTON_SELECT) == LOW) {
scoreEntered = true;
lastScore = score;
lastRequest = millis();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Дякуємо!");
lcd.setCursor(0, 1);
lcd.print("Оцінка: ");
lcd.print(score);
delay(2000);
}
delay(100);
}
}
return lastScore;
}
};
Енергоефективність та екологічність
Система управління енергоспоживанням:
class EnergyManager {
private:
struct EnergyData {
float dailyConsumption;
float peakPowerUsage;
unsigned long operatingTime;
float solarGeneration; // Якщо є сонячні панелі
float batteryLevel; // Якщо є акумулятори
};
EnergyData energyStats;
public:
void optimizeEnergyUsage() {
Serial.println("=== ОПТИМІЗАЦІЯ ЕНЕРГОСПОЖИВАННЯ ===");
// Аналіз патернів енергоспоживання
analyzeEnergyPatterns();
// Оптимізація розкладу роботи
optimizeOperatingSchedule();
// Управління режимами сну
optimizeSleepModes();
// Балансування навантаження
balancePowerLoads();
}
void enterEcoMode() {
Serial.println("Перехід в економічний режим");
// Зменшення частоти зчитування датчиків
config.sensorReadInterval = 60000; // 1 хвилина замість 30 секунд
// Зменшення яскравості дисплея
lcd.setBacklight(50); // 50% яскравості
// Оптимізація алгоритмів поливу
for (int zone = 0; zone < 4; zone++) {
// Збільшення порогів для менш частого поливу
config.zones[zone].moistureMin += 5;
// Зменшення тривалості поливу з компенсацією частотою
config.zones[zone].wateringDuration *= 0.8;
}
// Активація режиму глибокого сну
config.deepSleepEnabled = true;
logEvent("ECO_MODE_ACTIVATED", "Система переведена в економічний режим");
}
void monitorSolarPower() {
if (!hasSolarPanels()) return;
float solarVoltage = readSolarVoltage();
float solarCurrent = readSolarCurrent();
float solarPower = solarVoltage * solarCurrent;
energyStats.solarGeneration += solarPower * (millis() / 3600000.0); // Wh
// Оптимізація роботи залежно від сонячної генерації
if (solarPower > 10) { // Достатньо сонячної енергії
// Інтенсивний режим роботи
increasePumpSpeed();
activateAdditionalSensors();
} else if (solarPower < 2) { // Мало сонячної енергії
// Економічний режим
reducePumpSpeed();
enterEcoMode();
}
Serial.print("Сонячна генерація: ");
Serial.print(solarPower);
Serial.println(" Вт");
}
private:
void analyzeEnergyPatterns() {
// Аналіз споживання енергії по годинах доби
float hourlyConsumption[24] = {0};
// Збір статистики з історії
// ... код аналізу ...
// Знаходження пікових годин споживання
int peakHour = findPeakConsumptionHour(hourlyConsumption);
Serial.print("Пік споживання о ");
Serial.print(peakHour);
Serial.println(":00");
// Рекомендації по оптимізації
if (peakHour >= 18 && peakHour <= 22) {
Serial.println("Рекомендація: Перенести полив на ранкові години");
}
}
void balancePowerLoads() {
// Розподіл навантаження між зонами
int activeZones = countActiveZones();
if (activeZones > config.maxConcurrentZones) {
// Створення черги поливу
createWateringQueue();
}
// Плавний запуск насоса для зменшення пікових навантажень
implementSoftStart();
}
};
Створення таймера для поливу своїми руками – це захоплюючий проект, який поєднує практичну користь з технічним розвитком. Від простого реле часу до складної IoT системи – кожен рівень автоматизації має свої переваги та застосування.
Правильно спроектована та налаштована система автоматичного поливу може працювати багато років, забезпечуючи оптимальні умови для рослин та значно економлячи ваш час і воду. Головне – почати з простого та поступово нарощувати функціонал відповідно до ваших потреб та навичок.
Більше проектів для автоматизації дому ви знайдете в нашому блозі. Успіхів у створенні власної системи поливу!