Приветствую!

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

В далеком 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

Какие проблемы вылазили в ходе работы теплицы:

  1. Через 3 дня работы микроконтроллер зависал, приходилось перезагружать, поэтому сделал удаленную перезгразку через кнопку - restart. Потом я понял, что проблема была в переполнении памяти.

  2. Сложность реализации того вида логики и дизайна, который хотелось бы видеть - допустим: устаналивать пороги для полива (полив, если влажность почвы ниже 30% и остановка полива, если выше 60%), красивые кнопки для управления поливом и прочее.

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

  4. Невозможность менять код со стороны веб платформы, опять же это про ограничение кода.

  5. Я больше люблю собирать системы управления, нежели их программировать.

Оглядываясь назад я понимаю, что потратил слишком много времени на этот проект. Да было приколько всё это создавать, но это было всё долго и не гибко. Да и программировать микроконтроллеры я люблю меньше, чем разрабатывать электронику. Вместо того, что бы ломать голову над прошивкой я бы лучше больше потратил время на электронную часть проекта.

Разработка платформы для создания 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 файла и если всё нормально, то одобряет этот файл.

Подробнее расскажу в следующей статье.

Спасибо за внимание!