Хочу представить вашему вниманию контроллер управления насосами в зависимости от датчиков влажности.
Программа написана на C++ с использованием фреймворка Arduino.
Но никаких дополнительных библиотек типа Thread для реализации кода без блокировок(delay).
Динамическое добавление насосов в класс контроллера. Для добавления насоса в контроллер надо добавить строку:
pumpController.addPump(SoilSensor(A0, 800, 100000), 2, 2000); // Насос 1
где указывается пин датчика влажности, порог сухости, интервал проверки датчика, пин насоса, длительность работы насоса. но это можно и в конструкторе классов подсмотреть.
Важно отметить:
Программа использует объектно-ориентированное программирование с классами SoilSensor,
PumpController, ProcessStats и Pump,
используются классы, конструкторы и другие объектно-ориентированные возможности.
Программа предназначена для выполнения на микроконтроллерах Arduino или совместимых платформах.
-
Arduino-специфичные элементы:
Функции
pinMode()
,digitalWrite()
,analogRead()
Константы
HIGH
,LOW
Функции времени
millis()
,micros()
Объект
Serial
для работы с последовательным портомСтандартные функции Arduino
setup()
иloop()
// Инвертированные значения HIGH и LOW
const int MYHIGH = LOW;
const int MYLOW = HIGH;
// Буфер для форматированной строки
char buffer[100];
// Структура для статистики выполнения
struct ProcessStats {
unsigned long callCount = 0;
unsigned long totalTime = 0;
unsigned long minTime = 0xFFFFFFFF; // Максимальное значение unsigned long
unsigned long maxTime = 0;
void update(unsigned long executionTime) {
callCount++;
totalTime += executionTime;
if (executionTime < minTime) minTime = executionTime;
if (executionTime > maxTime) maxTime = executionTime;
}
unsigned long getAverageTime() const {
return callCount > 0 ? totalTime / callCount : 0;
}
String getStatsString() const {
return "(" + String(callCount) +
") " + String(minTime) +
"/" + String(maxTime) +
"/" + String(getAverageTime());
}
};
// Глобальная статистика для всех насосов
ProcessStats globalProcessStats;
// Класс для датчика влажности
class SoilSensor {
private:
int soilPin; // Пин датчика влажности
int dryThreshold; // Порог сухости
unsigned long checkInterval; // Интервал проверки
unsigned long lastCheckTime; // Время последней проверки
public:
// Конструктор
SoilSensor(int soilPin, int dryThreshold, unsigned long checkInterval)
: soilPin(soilPin), dryThreshold(dryThreshold), checkInterval(checkInterval), lastCheckTime(0) {}
// Метод для проверки, пришло ли время для следующей проверки
bool isCheckTime() {
return millis() - lastCheckTime >= checkInterval;
}
// Метод для чтения влажности
int readMoisture() {
// Обновляем время последней проверки
lastCheckTime = millis();
return analogRead(soilPin);
}
// Метод для проверки необходимости полива
bool needsWatering(int moisture) {
return moisture >= dryThreshold;
}
// Метод для получения информации о датчике
String getInfo(int moisture, int getPumpPin) {
return String(millis() /1000) + " сек. - Датчик " + String(soilPin) +
"(p="+ String(getPumpPin)+"): влажность=" + String(moisture) +
"(" + String(dryThreshold) +
")" + String(moisture - dryThreshold) +
" | " + globalProcessStats.getStatsString();
}
// Геттеры
int getSoilPin() const { return soilPin; }
int getDryThreshold() const { return dryThreshold; }
unsigned long getCheckInterval() const { return checkInterval; }
unsigned long getLastCheckTime() const { return lastCheckTime; }
};
// Класс для управления насосом
class Pump {
private:
SoilSensor sensor; // Объект датчика (копия)
int pumpPin; // Пин реле насоса
unsigned long pumpDuration; // Длительность работы насоса
bool isPumping; // Флаг активности насоса
unsigned long pumpStartTime; // Время начала работы насоса
public:
// Конструктор принимает временный объект SoilSensor
Pump(SoilSensor sensor, int pumpPin, unsigned long pumpDuration)
: sensor(sensor), pumpPin(pumpPin), pumpDuration(pumpDuration),
isPumping(false), pumpStartTime(0) {}
// Метод для настройки пина насоса
void setupPin() {
pinMode(pumpPin, OUTPUT);
}
// Метод для проверки, истекло ли время работы насоса
bool isPumpTimeExpired() {
return millis() - pumpStartTime >= pumpDuration;
}
// Метод для включения насоса
void startPumping() {
digitalWrite(pumpPin, MYHIGH);
isPumping = true;
pumpStartTime = millis();
Serial.println("Насос на пине " + String(pumpPin) + " включен s=" + String(getSensor().getSoilPin()) + "");
}
// Метод для выключения насоса
void stopPumping() {
digitalWrite(pumpPin, MYLOW);
isPumping = false;
Serial.println("Насос на пине " + String(pumpPin) + " выключен");
}
// Основной метод обработки насоса
void process() {
unsigned long startTime = micros(); // Засекаем время начала
// Проверяем, не активен ли насос в данный момент
if (isPumping) {
// Если насос работает, проверяем, не истекло ли время работы
if (isPumpTimeExpired()) {
stopPumping();
}
unsigned long endTime = micros(); // Засекаем время окончания
globalProcessStats.update(endTime - startTime); // Обновляем статистику
return; // Прерываем выполнение, если насос активен
}
// Проверяем, пришло ли время для следующей проверки датчика
if (sensor.isCheckTime()) {
int moisture = sensor.readMoisture();
// Выводим информацию в монитор порта
Serial.println(sensor.getInfo(moisture, getPumpPin()));
// Проверяем, слишком ли сухая почва
if (sensor.needsWatering(moisture)) {
startPumping();
}
}
unsigned long endTime = micros(); // Засекаем время окончания
globalProcessStats.update(endTime - startTime); // Обновляем статистику
}
// Геттеры
bool getIsPumping() const { return isPumping; }
unsigned long getPumpStartTime() const { return pumpStartTime; }
unsigned long getPumpDuration() const { return pumpDuration; }
SoilSensor getSensor() const { return sensor; }
int getPumpPin() const { return pumpPin; }
};
// Класс контроллера насосов
class PumpController {
private:
Pump** pumps; // Массив указателей на насосы
int pumpCount; // Количество насосов
int maxPumps; // Максимальное количество насосов
public:
// Конструктор
PumpController(int maxPumps = 10) : pumpCount(0), maxPumps(maxPumps) {
pumps = new Pump*[maxPumps];
for (int i = 0; i < maxPumps; i++) {
pumps[i] = nullptr;
}
}
// Деструктор для очистки памяти
~PumpController() {
for (int i = 0; i < pumpCount; i++) {
delete pumps[i];
}
delete[] pumps;
}
// Метод для добавления насоса
bool addPump(SoilSensor sensor, int pumpPin, unsigned long pumpDuration) {
if (pumpCount >= maxPumps) {
Serial.println("Ошибка: достигнуто максимальное количество насосов");
return false;
}
pumps[pumpCount] = new Pump(sensor, pumpPin, pumpDuration);
pumpCount++;
return true;
}
// Метод инициализации для раздела setup
void setup() {
for (int i = 0; i < pumpCount; i++) {
pumps[i]->setupPin();
pumps[i]->stopPumping();
}
Serial.println("Система автоматического полива инициализирована");
Serial.println("Количество насосов: " + String(pumpCount));
}
// Метод process для loop
void process() {
for (int i = 0; i < pumpCount; i++) {
pumps[i]->process();
}
}
// Геттеры
int getPumpCount() const { return pumpCount; }
int getMaxPumps() const { return maxPumps; }
};
// Создание контроллера насосов
PumpController pumpController(10); // Максимум 10 насосов
void setup() {
// Инициализация последовательного порта
Serial.begin(9600);
// Добавление насосов в контроллер
pumpController.addPump(SoilSensor(A0, 800, 100000), 2, 2000); // Насос 1
pumpController.addPump(SoilSensor(A1, 275, 16000), 3, 2000); // Насос 2
pumpController.addPump(SoilSensor(A2, 600, 700000), 4, 2000); // Насос 3
pumpController.addPump(SoilSensor(A3, 700, 7000000), 5, 2000); // Насос 4
// Инициализация контроллера
pumpController.setup();
}
void loop() {
// Обработка всех насосов через контроллер
pumpController.process();
}



Спасибо за внимание! Ожидаю комментарии и рекомендации.
Комментарии (9)
xSVPx
02.09.2025 19:07Вы правда планируете использовать сенсор так, как изображено на предпоследнем фото? Насколько его хватит, как думаете ?
В таких проектах основная сложность монтаж и обработка аварий...
sim2q
02.09.2025 19:07Вы правда планируете использовать сенсор так, как изображено на предпоследнем фото?
Если он не ёмкостный, то питание подавать только на время опроса. Это конечно должно быть предусмотрено в схеме.
theult
02.09.2025 19:07За статью и старания большое спасибо! Надо дальше расти, прикручивать взрослые вещи на контроль ошибок (как мы мониторим наличие/отсутствие/неисправность датчика, как мы отслеживаем исправность насоса, наличие воды. Вотчдог не повредит)
Электронику платы датчика не помешает в специальный лак закатать. Датчики можно по питанию включать и выключать, выдерживая время готовности для замера
randomsimplenumber
Нейминг суровое ;)
Не то чтобы это сильно мешало.. но специальная константа для ровно одного места, да с таким странным именем..
Логику и реализацию лучше бы разделить. Вдруг кому то на Raspberry pi захочеться портировать ;)
Воще выделение памяти в контроллерах дурной тон. Что произойдет, если памяти для add Pump не хватит?
doctor_kulibin Автор
Нейминг да) у меня ардуино выключает релюхи в режиме HIGH, а хотелось бы включение с таким названием.
Над разделением логики и реализации надо ещё соображать что и где разделять. А какие будут предложения?
А вот как проверять оставшуюся свободную память на ардуино пока не знаю. Но я специально так сделал чтобы было удобнее добавлять насосы.
m039
Как альтернатива можно использовать вместо HIGH/LOW - ON/OFF.
doctor_kulibin Автор
проверил использование памяти: при создании 10 насосов будет использована половина памяти ардумино уно, т.е. из 2К остаётся только 1К.
randomsimplenumber
Памяти может не хватить в любой момент. Добавите какие то улучшения, поменяете проц - и оно вдруг ребутнется.