↩️
Назад
Категории
Умный полив клубники на Raspberry Pi
08.08.2026 | коды из категории: IOT умный дом
🧠 Логика работы скрипта полива
PHP-скрипт на Raspberry Pi · Запуск по cron каждые 10 минут
🌱
Влажность почвы
soil_moisture + dry
Ниже 40% → полив
40–70% → норма
Выше 70% → стоп
Основной параметр
☔️
Защита от дождя
humidity — влажность воздуха
Если > 80% → полив отключён
Даже если почва сухая
Приоритет высокий
❄️
Защита от холода
temperature — температура почвы
5–15°C → полив ограничен
< 5°C → полив отключён
Приоритет высокий
🔧
Лечение залипшего крана
stall_time + status: STUCK
ON → OFF → проверка
Автоматическое восстановление
Самодиагностика
⏰
Защита от частого полива
Не чаще 1 раза в 30 минут
Предотвращает перелив
Даже если датчик говорит "СУХО"
Безопасность
🔋
Контроль батареи
battery — заряд датчика
< 20% → email уведомление
Замена батарейки вовремя
Мониторинг
✅
Подтверждение команд
5 попыток с задержкой
Проверка state и status
Если не подтверждено → email
Надёжность
📧
Email уведомления
Включение/выключение полива
Ошибки и критические ситуации
Не чаще 5 минут (антиспам)
Оповещение
🔄 Приоритет проверок:
1. STUCK
2. Температура
3. Дождь
4. Влажность почвы
5. Интервал полива
@raspberrypi:~ $ sudo cat /var/www/html/iot/cron_polivayka.php
#!/usr/bin/php
<?php
/**
* УМНЫЙ ПОЛИВ КЛУБНИКИ
* С ПРОВЕРКОЙ СТАТУСА STUCK И ЗАЩИТОЙ ПО ТЕМПЕРАТУРЕ
* Версия 3.0
*/
$pdo = new PDO("mysql:host=127.0.0.1;dbname=iot_db;charset=utf8mb4", "sadfdsaf", "asdfsadf");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// ===== НАСТРОЙКИ =====
$USER = "adgdsag";
$PASS = "sadfdsaf";
$SENSOR_TOPIC = "zigbee2mqtt/gorshok_smallone";
$VALVE_CMD = "zigbee2mqtt/PushOK_kran/set";
$VALVE_STATE = "zigbee2mqtt/PushOK_kran";
// ===== ПОРОГИ =====
$MOISTURE_MIN = 40; // Ниже 40% - СУХО → включаем полив
$MOISTURE_MAX = 70; // Выше 70% - МОКРО → выключаем полив
$MOISTURE_CRITICAL = 80; // > 80% - экстренное закрытие
$AIR_HUMIDITY_MAX = 80; // Если влажность воздуха > 80% - ДОЖДЬ
$TEMP_MIN = 15; // Ниже 15°C - полив ОГРАНИЧЕН (растения плохо пьют)
$TEMP_CRITICAL = 5; // Ниже 5°C - полив ОТКЛЮЧЁН (риск заморозков)
$STUCK_THRESHOLD = 10; // Если stall_time > 10с - кран залип
$MIN_POLIV_INTERVAL = 30; // Минуты между поливами
$EMAIL_TO = 'asdfasfd@gmail.com';
$EMAIL_FROM = 'it@asdfdsf.ru';
// ===== ФУНКЦИИ =====
function getData($pdo, $topic) {
$stmt = $pdo->prepare("SELECT value FROM sensor_data WHERE topic = ? ORDER BY timestamp DESC LIMIT 1");
$stmt->execute([$topic]);
$row = $stmt->fetch();
return $row ? json_decode($row['value'], true) : null;
}
function sendMqtt($topic, $payload, $user, $pass) {
$cmd = "/usr/bin/mosquitto_pub -h 127.0.0.1 -p 1883 -u " . escapeshellarg($user) .
" -P " . escapeshellarg($pass) . " -t " . escapeshellarg($topic) .
" -m " . escapeshellarg($payload) . " -q 1 2>&1";
return shell_exec($cmd);
}
function sendEmail($to, $subject, $body) {
global $EMAIL_FROM;
$lockFile = '/tmp/irrigation_email.lock';
if (file_exists($lockFile)) {
$lastSend = (int)file_get_contents($lockFile);
if ((time() - $lastSend) < 300) {
echo " 📧 Письмо пропущено (интервал 5 мин)\n";
return false;
}
}
$headers = "From: " . $EMAIL_FROM . "\r\n";
$headers .= "Content-Type: text/plain; charset=utf-8\r\n";
$result = mail($to, $subject, $body, $headers);
if ($result) {
file_put_contents($lockFile, time());
}
return $result;
}
function checkValveState($pdo, $topic, $expected_state, $max_attempts = 5) {
for ($i = 1; $i <= $max_attempts; $i++) {
sleep(2);
$valve = getData($pdo, $topic);
$state = $valve['state'] ?? 'unknown';
$status = $valve['status'] ?? 'unknown';
echo " Попытка $i: state = $state, status = $status\n";
if ($state == $expected_state && $status != 'MOVING' && $status != 'STUCK') {
return true;
}
}
return false;
}
function fixStuckValve($pdo, $topic, $cmd_topic, $user, $pass) {
global $STUCK_THRESHOLD, $EMAIL_TO;
echo "🔧 ПЫТАЮСЬ ПОЧИНИТЬ ЗАЛИПШИЙ КРАН...\n";
echo " Шаг 1: Открываю кран...\n";
sendMqtt($cmd_topic, json_encode(['state' => 'ON']), $user, $pass);
sleep(5);
echo " Шаг 2: Закрываю кран...\n";
sendMqtt($cmd_topic, json_encode(['state' => 'OFF']), $user, $pass);
sleep(5);
echo " Шаг 3: Проверяю состояние (жду до 30 секунд)...\n";
$success = false;
for ($i = 1; $i <= 6; $i++) {
sleep(5);
$valve = getData($pdo, $topic);
$state = $valve['state'] ?? 'unknown';
$status = $valve['status'] ?? 'unknown';
$stall = $valve['stall_time'] ?? 0;
echo " Проверка $i: state = $state, status = $status, stall = {$stall}с\n";
if ($state == 'OFF' && $status != 'MOVING' && $status != 'STUCK') {
$success = true;
echo " ✅ КРАН ИСПРАВЛЕН!\n";
sendEmail($EMAIL_TO, "✅ КРАН ИСПРАВЛЕН",
"Кран был залип, выполнена процедура восстановления.\n" .
"Текущее состояние: $state\n" .
"status: $status\n" .
"stall_time: {$stall}с");
return true;
}
}
echo " ❌ НЕ УДАЛОСЬ ИСПРАВИТЬ КРАН!\n";
sendEmail($EMAIL_TO, "⚠️ КРАН НЕ ИСПРАВЛЯЕТСЯ!",
"Кран залип, процедура восстановления не помогла!\n" .
"Текущее состояние: " . ($valve['state'] ?? 'unknown') . "\n" .
"status: " . ($valve['status'] ?? 'unknown') . "\n" .
"stall_time: " . ($valve['stall_time'] ?? 0) . "с\n" .
"Требуется ручное вмешательство!");
return false;
}
function checkLastPoliv() {
global $MIN_POLIV_INTERVAL;
$file = '/tmp/irrigation_last_poliv.lock';
if (!file_exists($file)) {
return ['allow' => true, 'last' => null, 'diff' => null];
}
$data = json_decode(file_get_contents($file), true);
if (!$data) {
return ['allow' => true, 'last' => null, 'diff' => null];
}
$lastTime = $data['time'] ?? 0;
$now = time();
$diff = ($now - $lastTime) / 60;
if ($diff < $MIN_POLIV_INTERVAL) {
return [
'allow' => false,
'last' => date('Y-m-d H:i:s', $lastTime),
'diff' => round($diff, 1)
];
}
return ['allow' => true, 'last' => date('Y-m-d H:i:s', $lastTime), 'diff' => round($diff, 1)];
}
function markPoliv() {
$file = '/tmp/irrigation_last_poliv.lock';
file_put_contents($file, json_encode(['time' => time()]));
}
// ===== ОСНОВНАЯ ЛОГИКА =====
echo "\n" . str_repeat("=", 65) . "\n";
echo date('Y-m-d H:i:s') . " - 🌱 ЗАПУСК СИСТЕМЫ ПОЛИВА\n";
// 1. Получаем данные
$sensor = getData($pdo, $SENSOR_TOPIC);
$valve = getData($pdo, $VALVE_STATE);
if (!$sensor) {
echo "❌ НЕТ ДАННЫХ С ДАТЧИКА!\n";
sendEmail($EMAIL_TO, "⚠️ Ошибка датчика полива", "Нет данных с gorshok_smallone!");
exit;
}
// 2. Извлекаем данные
$moisture = $sensor['soil_moisture'] ?? 0;
$air_humidity = $sensor['humidity'] ?? 0;
$temperature = $sensor['temperature'] ?? 0;
$dry = $sensor['dry'] ?? false;
$battery = $sensor['battery'] ?? 0;
$soil_warning = $sensor['soil_warning'] ?? 20;
$valve_state = $valve['state'] ?? 'unknown';
$valve_status = $valve['status'] ?? 'unknown';
$stall_time = $valve['stall_time'] ?? 0;
// Проверяем залипание
$is_stuck = ($stall_time > $STUCK_THRESHOLD || $valve_status == 'STUCK' || $valve_status == 'MOVING');
// ===== 3. ПРОВЕРКА ТЕМПЕРАТУРЫ =====
$is_cold = ($temperature < $TEMP_MIN && $temperature >= $TEMP_CRITICAL);
$is_critical_cold = ($temperature < $TEMP_CRITICAL);
// 3. Выводим информацию
echo "📊 ДАННЫЕ ДАТЧИКОВ:\n";
echo " 🌱 Влажность почвы: {$moisture}%\n";
echo " 💨 Влажность воздуха: {$air_humidity}%\n";
echo " 🌡️ Температура: {$temperature}°C\n";
echo " 🏜️ Dry: " . ($dry ? 'ДА (СУХО)' : 'НЕТ (ВЛАЖНО)') . "\n";
echo " ⚠️ Порог сухости (soil_warning): {$soil_warning}%\n";
echo " 🔋 Батарея: {$battery}%\n";
echo " 🚰 Кран: " . ($valve_state == 'ON' ? 'ОТКРЫТ' : 'ЗАКРЫТ') . "\n";
echo " 📌 Status: {$valve_status}\n";
echo " ⏱️ stall_time: {$stall_time}с " . ($is_stuck ? '⚠️ ЗАЛИП!' : '✅ ОК') . "\n";
// Показываем статус температуры
if ($is_critical_cold) {
echo " ❄️ ТЕМПЕРАТУРА КРИТИЧЕСКАЯ: {$temperature}°C < {$TEMP_CRITICAL}°C - ПОЛИВ ОТКЛЮЧЁН!\n";
} elseif ($is_cold) {
echo " 🥶 ТЕМПЕРАТУРА НИЗКАЯ: {$temperature}°C < {$TEMP_MIN}°C - полив ограничен\n";
} else {
echo " ✅ Температура в норме: {$temperature}°C\n";
}
// ===== 4. ПРОВЕРКА НА STUCK (приоритет) =====
if ($is_stuck) {
echo "\n⚠️ ОБНАРУЖЕНО ЗАЛИПАНИЕ КРАНА!\n";
echo " stall_time: {$stall_time}с, status: {$valve_status}\n";
$fixed = fixStuckValve($pdo, $VALVE_STATE, $VALVE_CMD, $USER, $PASS);
if ($fixed) {
echo "🔄 Перезапускаю проверку после лечения...\n";
$valve = getData($pdo, $VALVE_STATE);
$valve_state = $valve['state'] ?? 'unknown';
$valve_status = $valve['status'] ?? 'unknown';
$stall_time = $valve['stall_time'] ?? 0;
} else {
echo "❌ Кран неисправен! Требуется ручное вмешательство.\n";
exit(1);
}
}
// ===== 5. ПРОВЕРКА: ИДЁТ ЛИ ДОЖДЬ? =====
$is_rain = ($air_humidity > $AIR_HUMIDITY_MAX);
$action = null;
$reason = "";
$status = "";
// ===== 6. ЗАЩИТА ОТ ЧАСТОГО ПОЛИВА =====
$polivCheck = checkLastPoliv();
// ===== 7. ТЕМПЕРАТУРНАЯ ЗАЩИТА (приоритет выше, чем влажность) =====
if ($is_critical_cold) {
// КРИТИЧЕСКИ ХОЛОДНО - полив ОТКЛЮЧЁН
echo "\n❄️ КРИТИЧЕСКАЯ ТЕМПЕРАТУРА: {$temperature}°C < {$TEMP_CRITICAL}°C\n";
echo " ⏸️ ПОЛИВ ПОЛНОСТЬЮ ОТКЛЮЧЁН (риск заморозков!)\n";
if ($valve_state == 'ON') {
$action = 'OFF';
$reason = "❄️ КРИТИЧЕСКИ ХОЛОДНО! Температура {$temperature}°C < {$TEMP_CRITICAL}°C";
} else {
echo " ✅ Кран уже закрыт\n";
}
} elseif ($is_rain) {
// ДОЖДЬ
echo "\n☔️ ОБНАРУЖЕНА ВЫСОКАЯ ВЛАЖНОСТЬ ВОЗДУХА: {$air_humidity}% > {$AIR_HUMIDITY_MAX}%\n";
echo " ⏸️ ПОЛИВ ОТКЛЮЧАЕТСЯ (идёт дождь или только что прошёл)\n";
if ($valve_state == 'ON') {
$action = 'OFF';
$reason = "☔️ ДОЖДЬ! Влажность воздуха {$air_humidity}% > {$AIR_HUMIDITY_MAX}%";
} else {
echo " ✅ Кран уже закрыт, дождь идёт\n";
}
} elseif ($is_cold) {
// ХОЛОДНО - полив ОГРАНИЧЕН (только если очень сухо)
echo "\n🥶 НИЗКАЯ ТЕМПЕРАТУРА: {$temperature}°C < {$TEMP_MIN}°C\n";
echo " ⏸️ ПОЛИВ ОГРАНИЧЕН (только при критической сухости)\n";
// Проверяем влажность - поливаем только если экстремально сухо
if ($moisture < 20 && $dry == true) {
// Экстремально сухо - поливаем даже в холода
if ($valve_state != 'ON' && $polivCheck['allow']) {
$action = 'ON';
$reason = "🥶 ЭКСТРЕМАЛЬНАЯ СУХОСТЬ при низкой температуре! moisture={$moisture}%, dry=" . ($dry ? 'true' : 'false');
} else {
echo " ⏸️ Экстремально сухо, но интервал полива ещё не прошёл\n";
}
} else {
echo " ✅ Полив отложен (не критическая сухость)\n";
}
} else {
// ===== 8. ОСНОВНАЯ ЛОГИКА УПРАВЛЕНИЯ (нормальная температура) =====
$is_dry = ($dry == true || $moisture < $MOISTURE_MIN);
if ($is_dry) {
$status = "🔴 СУХО (dry=" . ($dry ? 'true' : 'false') . ", moisture={$moisture}%)";
if (!$polivCheck['allow']) {
echo "\n⏰ ЗАЩИТА ОТ ЧАСТОГО ПОЛИВА!\n";
echo " Последний полив был: {$polivCheck['last']} (прошло {$polivCheck['diff']} мин)\n";
echo " Минимальный интервал: {$MIN_POLIV_INTERVAL} мин\n";
echo " ⏸️ ПОЛИВ ОТЛОЖЕН\n";
} elseif ($valve_state != 'ON') {
$action = 'ON';
$reason = "СУХО! dry=" . ($dry ? 'true' : 'false') . ", влажность {$moisture}% < {$MOISTURE_MIN}%";
} else {
echo "\n✅ Кран уже открыт, полив идёт\n";
}
}
elseif ($moisture > $MOISTURE_MAX) {
$status = "🔵 МОКРО";
if ($valve_state != 'OFF') {
$action = 'OFF';
$reason = "влажность почвы {$moisture}% > {$MOISTURE_MAX}% (МОКРО)";
} else {
echo "\n✅ Кран уже закрыт, влажность высокая\n";
}
}
else {
$status = "🟢 НОРМА";
if ($valve_state == 'ON') {
$action = 'OFF';
$reason = "влажность в норме ({$moisture}%), кран был открыт - закрываю";
} else {
echo "\n✅ Влажность в норме ({$moisture}%), полив не нужен\n";
}
}
// Экстренная защита от перелива
if ($moisture > $MOISTURE_CRITICAL && $valve_state == 'ON') {
$action = 'OFF';
$reason = "⚠️ КРИТИЧЕСКАЯ ВЛАЖНОСТЬ {$moisture}% > {$MOISTURE_CRITICAL}%! ЭКСТРЕННОЕ ЗАКРЫТИЕ!";
}
}
// ===== 9. ПРОВЕРКА БАТАРЕИ =====
if ($battery < 20 && $battery > 0) {
echo "\n⚠️ НИЗКИЙ ЗАРЯД БАТАРЕИ ДАТЧИКА! {$battery}%\n";
sendEmail($EMAIL_TO, "⚠️ НИЗКИЙ ЗАРЯД ДАТЧИКА!",
"Батарея датчика влажности: {$battery}%\nЗамените батарейку!");
}
// ===== 10. ВЫПОЛНЯЕМ КОМАНДУ =====
if ($action) {
echo "\n📢 " . ($action == 'ON' ? 'ОТКРЫВАЮ' : 'ЗАКРЫВАЮ') . " кран!\n";
if ($status) echo " Статус: $status\n";
echo " Причина: $reason\n";
$payload = json_encode(['state' => $action]);
$output = sendMqtt($VALVE_CMD, $payload, $USER, $PASS);
if ($output) {
echo " MQTT: $output\n";
}
echo " ⏳ Жду 60 секунд перед проверкой состояния...\n";
sleep(60);
echo " Проверяю состояние...\n";
$success = checkValveState($pdo, $VALVE_STATE, $action, 5);
if ($success) {
echo "✅ УСПЕШНО!\n";
if ($action == 'ON') {
markPoliv();
}
$subject = $action == 'ON' ? "💧 ПОЛИВ ВКЛЮЧЁН" : "🌧️ ПОЛИВ ВЫКЛЮЧЁН";
$body = "Влажность почвы: {$moisture}%\n";
$body .= "Влажность воздуха: {$air_humidity}%\n";
$body .= "Температура: {$temperature}°C\n";
$body .= "Dry: " . ($dry ? 'true (СУХО)' : 'false (ВЛАЖНО)') . "\n";
$body .= "Причина: $reason\n";
$body .= "Батарея датчика: {$battery}%\n";
sendEmail($EMAIL_TO, $subject, $body);
} else {
echo "❌ НЕ УДАЛОСЬ ПОДТВЕРДИТЬ КОМАНДУ!\n";
sendEmail($EMAIL_TO, "⚠️ ОШИБКА УПРАВЛЕНИЯ КРАНОМ!",
"Не удалось подтвердить " . ($action == 'ON' ? 'открытие' : 'закрытие') . " крана!\n" .
"Влажность почвы: {$moisture}%\n" .
"Влажность воздуха: {$air_humidity}%\n" .
"Температура: {$temperature}°C\n" .
"Dry: " . ($dry ? 'true' : 'false') . "\n" .
"Команда: $action\n" .
"Текущее состояние: " . ($valve['state'] ?? 'unknown') . "\n" .
"Текущий status: " . ($valve['status'] ?? 'unknown') . "\n" .
"Проверь кран вручную!");
}
} else {
echo "\n✅ Система в штатном режиме\n";
}
// ===== 11. СТАТИСТИКА =====
echo "\n📊 СТАТИСТИКА:\n";
echo " 🌱 Влажность почвы: {$moisture}%\n";
echo " 💨 Влажность воздуха: {$air_humidity}%\n";
echo " 🌡️ Температура: {$temperature}°C\n";
echo " 🏜️ Dry: " . ($dry ? 'true (СУХО)' : 'false (ВЛАЖНО)') . "\n";
echo " 🚰 Кран: " . ($valve_state == 'ON' ? 'ОТКРЫТ' : 'ЗАКРЫТ') . "\n";
echo " 📌 Status: {$valve_status}\n";
echo " ⏱️ stall_time: {$stall_time}с\n";
echo " 🎯 Целевой диапазон почвы: {$MOISTURE_MIN}% - {$MOISTURE_MAX}%\n";
echo " ☔️ Дождь: " . ($is_rain ? 'ДА (полив отключён)' : 'НЕТ') . "\n";
echo " 🌡️ Температурный режим: " .
($is_critical_cold ? '❄️ КРИТИЧЕСКИ ХОЛОДНО (полив отключён)' :
($is_cold ? '🥶 ХОЛОДНО (полив ограничен)' : '✅ НОРМА')) . "\n";
echo " ⏰ Минимальный интервал полива: {$MIN_POLIV_INTERVAL} мин\n";
if (!$polivCheck['allow']) {
echo " ⏸️ Последний полив был: {$polivCheck['last']} (прошло {$polivCheck['diff']} мин)\n";
echo " ⏳ Следующий полив через: " . round($MIN_POLIV_INTERVAL - $polivCheck['diff'], 1) . " мин\n";
}
echo "\n" . date('Y-m-d H:i:s') . " - ✅ ЗАВЕРШЕНО\n";
echo str_repeat("=", 65) . "\n";
?>
Оцените статью
Всего: 0
Категории:
Категории
← Назад к списку
Комментарии
Пока нет комментариев. Будьте первым!