Приветствую!
Меня всё ещё зовут Александр Воробьев и я всё ещё пытаюсь облегчить жизнь программистам микроконтроллеров, схемотехникам, стартаперам и всем тем, кто не ровно дышет к автоматизации и технологиям.
В далеком 2022 году решил я автоматизировать теплицу тёще и даже это реализовал на базе ESP32 с управлением автополива по WI-FI и мониторингом температуры, освещенности, влажности почвы в теплице. Использовал готовый сервис интернета вещей iocontrol.ru для управления поливом и мониторингом телеметрии - температура, влажность, освещенность. Удобная штука, но с ограничениями. Но тем не менее огромное спасибо создателям этого веб ресурса!
Вкратце расскажу про железную часть проекта
сервопривод шарового крана 12V, управляемый драйвером L298N.
блок питания 12 В
DC-DC преобразователь 12В - 3.3В для питания ESP32
роутер ASUS c USB модемом с симкой Мегафона для раздачи Wi-Fi для ESP32. Согласен - сложно, но как есть.
датчик температуры DS18B20
фоторезистор
ёмкостный датчик влажности почвы
По технологии полива всё просто:
Воду для полива наливал в бидон 50 литров, в бидоне сделал отверстие для фитинга к которому подключал трубу для полива, далее шёл сервопривод, подключаемый с 2х сторон фитингами к трубам. Здесь видос как это всё я делал - https://dzen.ru/video/watch/629c79f925adf4180850b694
От трубы к каждому кусту отходил тонкий шланг для капельного полива.
Для полива я нажимал кнопку в приложении и сервопривод открывал воду для полива. Датчик влажности почвы я использовал просто для мониторинга влажности почвы у одного куста. Ещё раз повторюсь - делал я этот проект по фану, поэтому серьёзного применения этого проекта не преследовал и не ставил у каждого куста по микролаборатории. Но всегда хватало несколько минут открытия сервопривода, что бы все кусты напитались необходимым количеством влаги.
К сожалению или к счастью кода этого проекта не сохранилось:
Но он был легкий без веба, так как веб полностью весь реализовывался на стороне iocontrol. Было лишь конфигурирование портов - аналогового для ёмкостного датчика влажности почвы, темрморезистора и цифрового для DS18B20 ну и несколько строк логики, в основном, повторюсь - код генерировался с помощью веб сервиса.
Вкратце веб интерфейс управления умной теплицей:

Плюсы решения:
скорость разработки iot решения
разработка без особых знаний веб-програмирования
бесплатность
Минусы этого решения:
отсутствие свободы разработки
ограничение количества виджетов до 10 (данные с датчиков, кнопки управления, вывод состояния портов,....)
зависимость от веб сервиса
нет возможности локально управлять через Wi-Fi
Какие проблемы вылазили в ходе работы теплицы:
Через 3 дня работы микроконтроллер зависал, приходилось перезагружать, поэтому сделал удаленную перезгразку через кнопку - restart. Потом я понял, что проблема была в переполнении памяти.
Сложность реализации того вида логики и дизайна, который хотелось бы видеть - допустим: устаналивать пороги для полива (полив, если влажность почвы ниже 30% и остановка полива, если выше 60%), красивые кнопки для управления поливом и прочее.
Хотелось бы реализовать управление поливом через ИК порт с пульта, потому что тёща так и не смогла адаптироваться к управлению теплицей через смартфон. Но тут больше недочёт по схемотехнике, нежели в программировании.
Невозможность менять код со стороны веб платформы, опять же это про ограничение кода.
Я больше люблю собирать системы управления, нежели их программировать.
Оглядываясь назад я понимаю, что потратил слишком много времени на этот проект. Да было приколько всё это создавать, но это было всё долго и не гибко. Да и программировать микроконтроллеры я люблю меньше, чем разрабатывать электронику. Вместо того, что бы ломать голову над прошивкой я бы лучше больше потратил время на электронную часть проекта.
Разработка платформы для создания Iot проектов на базе ESP32C6
В предыдущей своей статье https://habr.com/ru/articles/1002110/ я писал о том, что разработал платформу с ИИ агентом для написания кода для микроконтроллера ESP32C6. Не буду повторяться, а перейду сразу к сути.
На этой платформе я добавил в меню кейсы, которые можно реализовывать с помощью готового промпта для ИИ агента. Просто жму на кейс - "Умная теплица" и промпт для ИИ агента готов. Это не тот кейс, который я реализовывал выше на видео!


Реализованный код:
Листинг кода
#include <WiFi.h> #include <WebServer.h> #include <Adafruit_Sensor.h> #include <DHT.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <Preferences.h> // Конфигурация WiFi const char* ssid = "111"; const char* password = "112"; // Конфигурация пинов #define DHT_PIN 15 // D1 для DHT11 #define SOIL_MOISTURE_PIN 4 // A1 для датчика влажности почвы #define RELAY_PIN 18 // Реле 1 для полива // Настройки датчиков #define DHT_TYPE DHT11 DHT dht(DHT_PIN, DHT_TYPE); // Настройки OLED #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // Веб-сервер WebServer server(80); Preferences prefs; // Переменные данных float temperature = 0; float humidity = 0; int soilMoisture = 0; bool relayState = false; unsigned long lastDataSend = 0; const unsigned long SEND_INTERVAL = 300000; // 5 минут void setup() { Serial.begin(115200); // Инициализация пинов pinMode(RELAY_PIN, OUTPUT); digitalWrite(RELAY_PIN, LOW); // Инициализация датчиков dht.begin(); // Инициализация OLED Wire.begin(6, 7); // SDA=6, SCL=7 if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("OLED не найден")); } display.clearDisplay(); display.setTextSize(1); display.setTextColor(WHITE); // Подключение WiFi WiFi.begin(ssid, password); Serial.print("Подключение к WiFi"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.print("IP адрес: "); Serial.println(WiFi.localIP()); // Настройка веб-сервера setupWebServer(); // Загрузка настроек prefs.begin("greenhouse", false); Serial.println("Система запущена"); } void setupWebServer() { server.on("/", HTTP_GET, []() { String html = R"rawliteral( <!DOCTYPE html><html lang='ru'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width,initial-scale=1'> <title>Умная теплица</title><style> body{font-family:Arial,sans-serif;max-width:800px;margin:0 auto;padding:20px;background:#f5f5f5} .card{background:white;border-radius:8px;padding:20px;margin:10px 0;box-shadow:0 2px 4px rgba(0,0,0,0.1)} .sensor-data{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:15px;margin-bottom:20px} .sensor-item{text-align:center;padding:15px;background:#e8f5e8;border-radius:6px} .value{font-size:24px;font-weight:bold;color:#2e7d32} .unit{font-size:14px;color:#666} .control{display:flex;align-items:center;gap:10px} .switch{position:relative;display:inline-block;width:60px;height:34px} .switch input{opacity:0;width:0;height:0} .slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:#ccc;transition:.4s;border-radius:34px} .slider:before{position:absolute;content:\"\";height:26px;width:26px;left:4px;bottom:4px;background-color:white;transition:.4s;border-radius:50%} input:checked+.slider{background-color:#2196F3} input:checked+.slider:before{transform:translateX(26px)} .btn{padding:10px 20px;background:#2196F3;color:white;border:none;border-radius:4px;cursor:pointer;font-size:16px} .btn:hover{background:#1976D2} </style></head> <body> <div class='card'><h1>Умная теплица</h1> <div class='sensor-data'> <div class='sensor-item'><div>Температура</div><div class='value' id='temp'>--</div><div class='unit'>°C</div></div> <div class='sensor-item'><div>Влажность</div><div class='value' id='hum'>--</div><div class='unit'>%</div></div> <div class='sensor-item'><div>Влажность почвы</div><div class='value' id='soil'>--</div><div class='unit'>%</div></div> </div> <div class='card'><h2>Управление поливом</h2> <div class='control'><label class='switch'><input type='checkbox' id='relaySwitch' onchange='toggleRelay(this.checked)'><span class='slider'></span></label> <span id='relayStatus'>Выключено</span></div> <button class='btn' onclick='sendData()'>Обновить данные</button></div> </div> <script> (async function(){ async function updateData(){ try{const res=await fetch('/data');const data=await res.json(); document.getElementById('temp').textContent=data.temperature.toFixed(1); document.getElementById('hum').textContent=data.humidity.toFixed(1); document.getElementById('soil').textContent=data.soilMoisture; document.getElementById('relaySwitch').checked=data.relayState; document.getElementById('relayStatus').textContent=data.relayState?'Включено':'Выключено'; }catch(e){console.error('Ошибка:',e)}} async function toggleRelay(state){ try{await fetch(/setRelay?state=${state?1:0},{cache:'no-store'});updateData();} catch(e){console.error('Ошибка:',e)}} async function sendData(){ try{await fetch('/sendData',{cache:'no-store'});alert('Данные отправлены');} catch(e){console.error('Ошибка:',e)}} setInterval(updateData,5000);updateData();})(); </script></body></html> )rawliteral"; server.send(200, "text/html", html); }); server.on("/data", HTTP_GET, []() { String json = "{\"temperature\":" + String(temperature, 1) + ",\"humidity\":" + String(humidity, 1) + ",\"soilMoisture\":" + String(soilMoisture) + ",\"relayState\":" + String(relayState ? "true" : "false") + "}"; server.send(200, "application/json", json); }); server.on("/setRelay", HTTP_GET, []() { if (server.hasArg("state")) { relayState = server.arg("state").toInt() == 1; digitalWrite(RELAY_PIN, relayState ? HIGH : LOW); server.send(200, "text/plain", "OK"); } }); server.on("/sendData", HTTP_GET, []() { sendDataToCloud(); server.send(200, "text/plain", "Данные отправлены"); }); server.begin(); } void readSensors() { temperature = dht.readTemperature(); humidity = dht.readHumidity(); if (isnan(temperature) || isnan(humidity)) { Serial.println("Ошибка чтения DHT11"); temperature = 0; humidity = 0; } // Чтение влажности почвы (0-4095 → 0-100%) int rawValue = analogRead(SOIL_MOISTURE_PIN); soilMoisture = map(rawValue, 0, 4095, 0, 100); } void updateDisplay() { display.clearDisplay(); display.setCursor(0, 0); display.print(F("Теплица: ")); display.print(WiFi.localIP()); display.setCursor(0, 16); display.print(F("Темп: ")); display.print(temperature, 1); display.print(F("\337C")); display.setCursor(0, 26); display.print(F("Влаг: ")); display.print(humidity, 1); display.print(F("%")); display.setCursor(0, 36); display.print(F("Почва: ")); display.print(soilMoisture); display.print(F("%")); display.setCursor(0, 46); display.print(F("Полив: ")); display.print(relayState ? F("ВКЛ") : F("ВЫКЛ")); display.display(); } void controlIrrigation() { // Автоматическое управление поливом при влажности почвы < 30% if (soilMoisture < 30 && !relayState) { relayState = true; digitalWrite(RELAY_PIN, HIGH); Serial.println("Автополив включен"); } else if (soilMoisture >= 40 && relayState) { relayState = false; digitalWrite(RELAY_PIN, LOW); Serial.println("Автополив выключен"); } } void sendDataToCloud() { // Здесь можно реализовать отправку данных в облако // Например: MQTT, HTTP запрос к API и т.д. Serial.print("Отправка данных: "); Serial.print("Temp="); Serial.print(temperature); Serial.print("C, Hum="); Serial.print(humidity); Serial.print("%, Soil="); Serial.print(soilMoisture); Serial.print("%, Relay="); Serial.println(relayState ? "ON" : "OFF"); lastDataSend = millis(); } void loop() { server.handleClient(); static unsigned long lastSensorRead = 0; const unsigned long SENSOR_INTERVAL = 2000; // 2 секунды if (millis() - lastSensorRead >= SENSOR_INTERVAL) { readSensors(); controlIrrigation(); updateDisplay(); lastSensorRead = millis(); } if (millis() - lastDataSend >= SEND_INTERVAL) { sendDataToCloud(); } }
К плате подключён датчик DHT11 (влажность и температура в теплице), датчик влажности почвы, Насос 12V к реле и дисплей OLED 0.96 к I2C. И всё это управляется по веб интерфейсу.

При подключении по веб:

Дизайн получился кривоват, но это исправляется ещё промптом
"адаптируй веб инетрфейс под мобильные устройства и сделай веб интерфейс в стиле эко" :

Всё, умная теплица на базе платы aiot инноватор с дисплеем и веб интерфейсом готова! За пару промптов.
Пока моя платформа создаёт только код, но я уже начинаю на ней реализовывать возможность обучения нейросетей, а это очень нужно, особенно в умной теплице.
Я реализовал возможность загрузки датасетов c датчиков для обучения нейронки Tiny ML. Перед загрузкой датасетов на сервер - платформа анализирует структуру CSV файла и если всё нормально, то одобряет этот файл.


Подробнее расскажу в следующей статье.
Спасибо за внимание!
