↩️ Назад

Категории

Умный полив клубники на Raspberry Pi

08.08.2026 | коды из категории: IOT умный дом

Умный полив клубники на Raspberry Pi

🧠 Логика работы скрипта полива
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";
?>
Теги: #php #mqtt #iot #raspberrypi
Оцените статью
Всего: 0



Категории:

Категории

Комментарии

Пока нет комментариев. Будьте первым!

Оставить комментарий

← Назад к списку

Посетителей сегодня: 0
о блоге | карта блога | 📡 Подписаться на RSS

© Digital Specialist | Не являемся сотрудниками Google, Яндекса и NASA