Хочу представить вашему вниманию контроллер управления насосами в зависимости от датчиков влажности.
Программа написана на 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();
}


Спасибо за внимание! Ожидаю комментарии и рекомендации.
Комментарии (84)
 - xSVPx02.09.2025 19:07- Вы правда планируете использовать сенсор так, как изображено на предпоследнем фото? Насколько его хватит, как думаете ? - В таких проектах основная сложность монтаж и обработка аварий...  - sim2q02.09.2025 19:07- Вы правда планируете использовать сенсор так, как изображено на предпоследнем фото? - Если он не ёмкостный, то питание подавать только на время опроса. Это конечно должно быть предусмотрено в схеме.  - doctor_kulibin Автор02.09.2025 19:07- Согласен. Какой-нибудь MOSFET использовать. В следующей версии скорректирую. 
  - doctor_kulibin Автор02.09.2025 19:07- А если датчик немного более чем ёмкостный то питание не стоит отключать? Или все таки добавить эту фичу?  - sim2q02.09.2025 19:07- а почитайте про электропитание спутников :) там много что предусмотрено, для себя в единичных штуках так делаю, но мне больше просто нравится) 
 
 
 
 - theult02.09.2025 19:07- За статью и старания большое спасибо! Надо дальше расти, прикручивать взрослые вещи на контроль ошибок (как мы мониторим наличие/отсутствие/неисправность датчика, как мы отслеживаем исправность насоса, наличие воды. Вотчдог не повредит) - Электронику платы датчика не помешает в специальный лак закатать. Датчики можно по питанию включать и выключать, выдерживая время готовности для замера  - doctor_kulibin Автор02.09.2025 19:07- В лак закатал края платы сенсора. Планирую добавить управление питанием датчиков. А вот как мониторить неисправности(датчика\насоса) пока сложно представляю. Долив воды планирую сделать с поплавковым клапаном.  - theult02.09.2025 19:07- Аналоговые значения выхода датчика обычно никогда не бывают 0 и 1023, можно отслеживать выход за диапазон. Цифровые датчики обычно имеют библиотеки, там часто есть функционал контроля наличия датчика, даже у простейшего ds18b20 это есть. Наличие воды и исправность насоса можно контролировать косвенно (полили X секунд, через Y минут должна вырасти влажность почвы), напрямую либо датчики уровня (если это ёмкость), либо датчики давления, если это водопровод. Датчики давления бывают вечные промышленные 4-20мА и все остальное дендрофекалити. Насосы можно мониторить датчиками тока или датчиками давления 
 
 
 - aspirinne02.09.2025 19:07- Классная статья! 
 А можете расписать список покупок? Что входит помимо сенсора и ардуины? - doctor_kulibin Автор02.09.2025 19:07- Я покупал набор с Али. Там ещё насосы и блок реле: https://a.aliexpress.com/_oDFKljH   - sim2q02.09.2025 19:07- А где диоды параллельно каждой помпе? 
 также раз у вас реле ими щёлкает (хотя я бы поставил по транзистору с контролем тока (шунт... можно один для всех на общий и если насосов много - то возможно ещё один общий ключ на случай отключения всей цепи), но параллельно контактам нужна RC цепочка... тут достаточно 0.1 мкф плёнка/керамика + 1...10 Ом
 Но такой подход аппаратной избыточности возможно не всем подходит, речь про доп коммутацию питания и измерения (диоды и RC тут обязательны!)
 Можно глянуть на промку как это сделано. - doctor_kulibin Автор02.09.2025 19:07- Поставил диод 1N4007, однако в моем случае это вряд ли поможет - у меня отдельный аккумулятор для насосов.  - Диод на насосе - 1N4007  - Отдельный аккумулятор для насосов  - randomsimplenumber02.09.2025 19:07- Паранойи много не бывает ;) чуть меньше износ контактов; протянут не 10 лет а все 20 ;) 
  - sim2q02.09.2025 19:07- однако в моем случае это вряд ли поможет - и для @randomsimplenumber что бы два раза не писать:) - Паранойи много не бывает ;) чуть меньше износ контактов; протянут не 10 лет а все 20 ;) - Так это для того что бы из за обратной ЭДС не было высоковольтной искры которая для такого монтажа словно РЭБ девайс :) На катушку реле также ставят - на плате блока реле видно стеклянный красный 1N4148 около каждого реле. 
 
 
 
 
 - Mishootk02.09.2025 19:07- Расскажите о механической части - конструкция насоса с поплавком, зачем так, какая проблема решалась? - Как решается проблема медленного смачивания почвы? До момента попадания влаги на датчик насос может накачать достаточно много воды в горшок.  - doctor_kulibin Автор02.09.2025 19:07- Насос с поплавком потому что насосы пришли с концами проводов только 20см. Я удлинил канешна же провод и соединения сделал с термоусадкой, но мне всё равно не нравится что соединение находится в воде. А с поправком уже лучше. - А вот проблема медленного смачивания почвы думаю решать увеличением интервала опроса датчика, за один опрос - один полив, до того момента пока налившаяся вода не впитается(методом проб и ошибок канешна же). 
 
 - Mishootk02.09.2025 19:07- А теперь давайте ближе к реальности. Я вижу, у вас автономное питание. Рассказывайте, сколько система живет по времени без участия человека. Надолго ли можно уехать в отпуск оставив цветы под присмотром этого устройства. - Я тоже такую штучку собираю. Только не на датчиках влажности, а на расписании со счетчиком воды.  - doctor_kulibin Автор02.09.2025 19:07- За четыре дня работы берите показатели  - Аккумуляторы контроллера  - Аккумулятор насосов  - Mishootk02.09.2025 19:07- По МК более интересно. Переведите в дни живучести. Разряд линейный? Начальное значение какое? Минимальное при котором еще работает какое? - И показательнее токи померить. В режиме контроля и в режиме активного реле. Ну я больше вас спрашиваю, потому что по спецификации я знаю потребление МК и релюшек. 
  - Mishootk02.09.2025 19:07- Дополню. Посмотрел графики разряда аккумуляторов. Если две банки дают 5.35, то одна, скорее всего дает половину - 2.675, а на графиках это уже за пределами линейного участка разряда, и дальше крутое пике в ноль. И это уже не рекомендованное состояние по разряду - сильно влияет на износ. 
 
  - doctor_kulibin Автор02.09.2025 19:07- Вот как я поменял значения: 
 https://github.com/Akullaaa/pumpController/commit/46d272f37a00d0e030d79d9649b524239755292f - изменения 
 
 - VernM202.09.2025 19:07- Добрый день! 
 Делал я нечто подобное на той же Ардуинке, с тем же датчиком, но с другой помпой. Мои заметки:
 1. Датчики нужно обязательно покрывать лаком со всех сторон (и торцов тоже). Штатное исполнение не защищает от влаги, она скоро проникнет к электродам, и начнутся глюки с показаниями.
 2. Датчики нужно запитывать от образцового напряжения, иначе показания так же "плывут".
 3. Для стабильности измерений я брал среднее из нескольких измерений.
 4. Каждый датчик нужно калибровать индивидуально.
 5. Крайне нежелательно проводить замеры при работающем насосе. Как бы ни фильтровал -- будут погрешности. - ptr12802.09.2025 19:07- Можно использовать BlueTooth/ZigBee влагозащищенные датчики, что автоматически решает первые две проблемы и последнюю. - Для стабильности измерений лучше медиана, чем среднее. - При большом количестве датчиков я бы предпочёл насосам гидроаккумулятор и электромагнитные клапаны, что позволило бы подключать цепочкой, а не звездой. Но это уже дело вкуса.  - Mishootk02.09.2025 19:07- При большом количестве датчиков я бы предпочёл насосам гидроаккумулятор и электромагнитные клапаны, что позволило бы подключать цепочкой, а не звездой. Но это уже дело вкуса. - Думаю, тут достаточно будет резервуара приподнятого на пару метров и самотека. Качать в резервуар по поплавку. В общую магистраль импульсный счетчик воды. Лить в каждый горшок по количеству. Повторная поливка по высыханию, но опять по количеству - этим избегаем проблему перелива из-за медленного смачивания. Аварийные ситуации типа "всегда сухо" или "всегда мокро" переводит горшок в поливку строго определенным минимальным количеством, чтобы только цветок не сдох.  - ptr12802.09.2025 19:07- достаточно будет резервуара приподнятого на пару метров и самотека - Так это и есть один из видов гидроаккумуляции. Причем весьма популярный до сих пор в странах с мягким климатом. И, насколько я знаю, единственный используемый в гидроаккумулирующих электростанциях. - В общую магистраль импульсный счетчик воды. - Я бы не усложнял аппаратную часть, так как ПИД регулирование даст куда более точный результат, позволяя поддерживать влажность в заданных пределах. Да это и проще, чем анализировать дебет воды и испарение, в зависимости от температуры, влажности воздуха и попадания солнечных лучей на почву. - Аварийные ситуации - Тут огромный простор для деятельности. Например, в Невинномысске у нас почти везде тройное резервирование плюс автоматические чисто аппаратные аварийные системы. Так что всё зависит от паранойи создателя системы ) - Для дома я бы в аварийной ситуации просто обесточивал систему. Слишком мокро - возможно будет выливаться из горшка. Слишком сухо - возможно трубка полива вывалилась из горшка и льет на пол. Лучше цветочки засохнут, чем залить соседей. Но это моё субъективное мнение.  - Mishootk02.09.2025 19:07- Для дома я бы в аварийной ситуации просто обесточивал систему. Слишком мокро - возможно будет выливаться из горшка. Слишком сухо - возможно трубка полива вывалилась из горшка и льет на пол. Лучше цветочки засохнут, чем залить соседей. Но это моё субъективное мнение. - Для себя - все цветы в поддоне, объем заведомо больше резервуара. Объем резервуара достаточен для всего срока отсутствия. Датчик протечки в поддоне, контроль уровня в резервуаре, мониторинг питания, контроль объема налива - при выходе за допустимые значения СМС на телефон "я сделал все что мог", переход в аварийный режим (смотря какой отказ) и попытка дотянуть пока едет помощь. - Понимаю, что тут перебор по функциональности и паранойя зашкаливает, но у меня это игрушка, я так пар после работы спускаю.  - ptr12802.09.2025 19:07- попытка дотянуть пока едет помощь - Мои заказчики почти всегда такое исключают, так как всё предусмотреть невозможно. Но при 500 градусах и 350 атмосферах последствия могут быть куда хуже, чем при заливе соседей водой ) - паранойя зашкаливает - Так у меня тоже самое. С "Невинномысским азотом" проект закончили, так теперь бразильские заводы начались ) 
 
 
 
 
 
 
           
 

randomsimplenumber
Нейминг суровое ;)
Не то чтобы это сильно мешало.. но специальная константа для ровно одного места, да с таким странным именем..
Логику и реализацию лучше бы разделить. Вдруг кому то на Raspberry pi захочеться портировать ;)
Воще выделение памяти в контроллерах дурной тон. Что произойдет, если памяти для add Pump не хватит?
doctor_kulibin Автор
Нейминг да) у меня ардуино выключает релюхи в режиме HIGH, а хотелось бы включение с таким названием.
Над разделением логики и реализации надо ещё соображать что и где разделять. А какие будут предложения?
А вот как проверять оставшуюся свободную память на ардуино пока не знаю. Но я специально так сделал чтобы было удобнее добавлять насосы.
m039
Как альтернатива можно использовать вместо HIGH/LOW - ON/OFF.
doctor_kulibin Автор
совсем решил избавиться от этих костант( и еще добавил проверку памяти): https://github.com/Akullaaa/pumpController/blob/main/pumpController.ino
doctor_kulibin Автор
проверил использование памяти: при создании 10 насосов будет использована половина памяти ардумино уно, т.е. из 2К остаётся только 1К.
randomsimplenumber
Памяти может не хватить в любой момент. Добавите какие то улучшения, поменяете проц - и оно вдруг ребутнется.
doctor_kulibin Автор
В новой версии будет отображаться оставшаяся память RAM.
randomsimplenumber
Тогда надо лог писать. Чтобы если ребутнулось, можно было взять флешку и прочитать.
doctor_kulibin Автор
Это надо новое железо добавлять как я понимаю. Хотя мне канешна хорошо бы вести логи, а то каждый раз когда включаю монитор порта в ARDUINO IDE программа заново перезапускается. Какой модуль добавить для флешки? Что порекомендуете?
randomsimplenumber
Я рекомендую аллоцировать все обьекты статически. Если что то не влезет в память - прошивка не соберется.
doctor_kulibin Автор
Согласен что это хорошая практика. Но предпочитаю решать вопросы по мере их появления. Сейчас ситуация приемлемая. Буду тестировать вопросы с памятью - нагроможу ещё фишек в код(запись логов на флешку) и посмотрим как оно взлетит. А то по моему памяти слишком много остаётся)
ptr128
Если поменять МК на более современный, то памяти будет намного больше. Например, RP2040-Zero сейчас можно купить дешевле, чем Arduino UNO. А там SRAM на два порядка больше, чем в ATMega328.
Если нужно динамическое распределение памяти, то выделять её надо ещё при инициализации, а в тех случаях, когда это невозможно - обрабатывать ошибку нехватки памяти с индикацией этого события каким-то способом. Хоть светодиодом.
randomsimplenumber
Тут stc8 нормально подойдет ;)
Я про сам принцип. Какой смысл в динамическом выделении памяти, когда мы заранее знаем, что подключено к контроллеру?
Возвращаешься из отпуска- соседи залиты, цветы засохли, на контроллере бодро мигает лампочка ;)
ptr128
Если насосов всего пара, то даже PMS171B-S08 за 10 рублей подойдет. Вот только Arduino IDE Padauk не поддерживает и через USB его без дополнительного оборудования, причем совсем не дешевого, не прошить. Я всё же пытаюсь держаться рамок DIY уровня Arduino )
А если не знаем на момент прошивки, позволяя пользователю задавать всё в динамике, сохраняя конфигурацию в EEPROM или флеш? Не предлагать же своей жене или бабушке перепрошивать МК, если захотелось добавить цветок или датчик из фиалок переставить в алоэ?
Или МК уже обругался электронными письмами или сообщениями в Telegram через HA. В любом случае, ошибку надо обрабатывать так, чтобы последствия были минимальны. Это как раз при уходе в перезагрузку можно соседей залить, а не при аварийном завершении работы с отключением всех насосов.
randomsimplenumber
Ну и задавайте. Контроллеры насосов и датчиков аллоцированы сразу, а конфигурируются значениями из eeprom.
Так то если памяти много можно писать на питоне и не заморачиваться с плюсами.
ptr128
Вы вообще читаете на что отвечаете? Я же писал: "Если нужно динамическое распределение памяти, то выделять её надо ещё при инициализации".
Другая проблема, как тоже я писал, "в тех случаях, когда это невозможно".
Если хватает памяти на одновременную работу интерфейса конфигурации и на обслуживание датчиков и насосов - хорошо. Но это совсем не всегда так.
Даже во время работы, если параметры регулирования подбираются автоматически, то памяти может не хватать на одновременную калибровку всех датчиков и насосов.
Можно. Но можно упереться в производительность. Например, ПИД регулирование с автоподстройкой методом многомерного сканирования Вишняковой нескольких одновременно включенных насосов, на Python может оказаться слишком ресурсоемкой задачей для МК из-за издержек динамической типизации. Впрочем, ТС обошелся вообще без ПИД регулирования, опытным путем подбирая параметры, исходя из размера горшков и мощности насосов, на удивление обеспечивающих одинаковый расход воды при разном его уровне в накопительном баке )))
randomsimplenumber
Если. Как правило, нет в нем необходимости. В данном проекте точно нет
Сложно было бы его сюда прикрутить ;)
ptr128
И где ссылка на это правило? У меня почему-то намного чаще динамическое распределение памяти требуется.
Для Вас? )))
randomsimplenumber
Разные задачи по разному решаются. Здесь ни pid регулятор ни динамическое распределение памяти нафиг не нужны. Но если хочется, то конечно же можно.
ptr128
Аргументация потрясающая. Ну вперед, докажите для начала ложность моего утверждения: "Если хватает памяти на одновременную работу интерфейса конфигурации и на обслуживание датчиков и насосов - хорошо. Но это совсем не всегда так."
А потом уже вернемся к тому, насколько инертно изменение влажности земли.
randomsimplenumber
Неплохо было бы, если бы ктото доказал, что динамическое выделение памяти - всегда правильное решение.
ptr128
Употребление слова "всегда" - уже признак демагогии. Так что возвращайтесь к доказательству того, что утверждали Вы.
randomsimplenumber
Требование сходу каких то доказательств - признак демагогии ;) Я художник, я так вижу. Вы можете попросить меня обосновать мою точку зрения, и, возможно, я приведу какие то аргументы.
Mishootk
Если на маленьком МК динамическая память используется только при инициализации, то значит динамическая память не нужна.
Если в маленьком МК используется динамическая память в процессе работы, и это устройство должно наботать автономно, то это ошибка проектирования. Динамического распределения памяти там быть не должно (в общепринятом понимании).
ptr128
Докажите, что не нужны МК с конфигурацией задаваемой пользователем и от которой может зависеть потребность в оперативной памяти.
Докажите, для примера, что буфер размером в страницу флеш-памяти, необходимый исключительно в момент сохранения изменений конфигурации, должен быть выделен статически. Даже если он занимает до четверти всей RAM.
А потому уже перейдем к обсуждению того, можно ли вообще, например, в ESP32 c RTOS обойтись без динамического распределения памяти. И насколько разумно для встроенного веб-сервера статически выделять память.
Mishootk
Я упоминаю конкретные случаи, в которых использование динамического выделения памяти не оправдано.
Если у меня две не пересекающиеся задачи, например, показать меню и сохранить настройки, которые требуют по 1Кб памяти, то разумно размещать их временные данные в структурах размещенных по одному и тому же адресу в статически выделенном буфере. Зато я точно знаю, что на эту память больше никто не претендует. И сюрпризов не возникнет, что в очередной запрос у меня почему-то окажется свободными только 900 байт.
Ну и я рассуждаю про себя, а не про пользователя. Для удобства пользователя пусть выделяет себе память динамически, это его задачи.
Конкретно, если рассматривать поливалку, если ей не хватит памяти при статической инициализации, то никакая динамика ее не спасет.
Встречал интересные случаи, когда вроде бы человек оперировал только строками (с динамическим выделением), но из-за интересного расположения переменных в стеке вызова и перекрывающемся времени жизни и немного гуляющего размера МК приходил к ситуации сегментирования кучи. И привет.
Неприятность динамического выделения в двух местах. Первое - это сегментирование. Второе - это привычка им пользоваться забывая про первое.
ptr128
С точки зрения безопасности при работе с памятью - это совершенно не разумно. Намного надежней выделять динамически память для буферов страницы флеша, сектора FAT на SD-карте или MQTT сообщения, корректно обрабатывая ошибки выделения памяти, чем писать свою реализацию динамической работы с памятью в пуле.
Откуда уверенность в безошибочности своего кода? )))
Просто проверяйте успешность выделения памяти, а память, необходимую для аварийного завершения, выделяйте заранее. Зато сможете адекватно сообщить пользователю, что, для примера, либо ему надо уменьшить количество насосов, либо сократить глубину хранения истории температур и влажности с датчиков.
А вот если Вы напишете работоспособный аналог lwip без динамического выделения памяти - я сильно удивлюсь. И да, поливалка тоже вполне может работать с использованием MQTT. А в случае интеграции этой поливалки в HA, MQTT становится обязательным требованием.
Почему не спасёт? Например, если не хватает памяти для 10 датчиков и 512-байтного буфера для SD-карты, она просто об этом сообщит и пользователь будет вынужден или ограничиться 9 датчиками, или отказаться от протоколирования на SD-карту.
С этим никто не спорит. Но для решения этой проблемы известны пути решения.
randomsimplenumber
Где то в в давно забытом шкафчике есть давно забытая коробочка. На ней засветилась красная лампочка.
ptr128
Потому что пользователь добавил датчик или включил протоколирование на SD-карту и ушел не посмотрев на результат? )))
randomsimplenumber
Вся надежда на программиста. Он всегда обрабатывает исключительные ситуации. Правда?
Mishootk
Тут чего-то разделили пользователя и программиста. В данном примере есть только программист. Он сконфигурировал поливайку, она либо при компиляции не влезла в память, либо при первом запуске завалилась.
У пользователя никаких вариантов нет, кроме того, что уже умеет устройство. Поддерживает 10 датчиков, значит в нем уже есть память под них. Даже если сейчас только 2 подключено. Было бы странно докупив еще комплект датчик+насос и выбрав в меню большее количество получить красную лампочку.
ptr128
Не предлагать же своей жене или тёще перепрошивать МК, если захотелось добавить цветок или датчик из фиалок переставить в алоэ?
А то, что количество поддерживаемых датчиков может зависеть от параметров задаваемых пользователем Вы так и не смогли представить?
randomsimplenumber
Пользователь: меняет настройки. Программа: падает, настройки несовместимы с количеством памяти. Представить могу, конечно. Но такие шедевры , слава Б-у, не попадались.
ptr128
Вы просто не умеете программировать, поэтому и не можете представить, что программа при ошибке выделения памяти может не падать, а внятно информировать пользователя, какие из заданных им параметров можно изменить и в какую сторону, чтобы программа могла продолжить работу.
На ESP32 я прямо в веб-интерфейсе такие параметры подсвечивал, что даже домохозяйке должно быть понятно.
Если Вы ни разу не сталкивались с нехваткой памяти на принтерах, коммутаторах, роутерах и т.п., то это говорит лишь о недостатке Вашего опыта.
randomsimplenumber
Или я просто везучий ;) Чтобы словить баг в кофеварке, и уверенно продиагностировать именно нехватку памяти - нужно быть очень талантливым и везучим тестировщиком. Ну, и я надеюсь, что прошивки для кофеварок таки тестируются, и когда там обнаруживают такое поведение - программистов лишают смузи, а каждого 10-го бьют палкой.
ptr128
Я же говорю, недостаток опыта )))
randomsimplenumber
Не нужно увеличивать количество говнокода. Отличный пример как не надо делать.
ptr128
То есть, кофемашина знающая только восемь рецептов приготовления кофе, заложенных производителем - это хорошо. А кофемашина, поддерживающая множество задаваемых пользователем рецептов, количество которых ограничено лишь доступной памятью - уже говнокод? )))
randomsimplenumber
Кофемашина с записью рецептов - хорошо. Говнокод, который никто не тестировал, навайбкодили и в прод - плохо. Ложная дихотомия - один из приемов демагогии, плохо.
ptr128
Вы действительно не понимаете, что количество рецептов ограничено их сложностью и объёмом памяти? Или просто включили дурака?
Mishootk
Множество пользовательских рецептов - хорошо.
"Пользовательских рецептов 3 из 10 возможных. 7 свободно.
[ДОБАВИТЬ][УДАЛИТЬ]"
- допустимая картинка на экране.
ptr128
Ваше предложение выглядит как сообщение на смартфоне: Можете включить запись видео, 7 видеозаписей осталось )))
Рецепты могут занимать разный объем памяти. Теоретически, можно такой рецепт наворотить из смеси зерен разного помола и жарки, что он вообще всю память займёт.
randomsimplenumber
Память, в которую влезут абсолютно все рецепты в мире, стоит как несколько чашек кофе.
ptr128
Только если найдется идиот, который в кофемашину установит SSD. Это даже не считая усложнения схемотехники и снижения надёжности устройства при использовании внешней памяти для МК.
Можно на Ваш github взглянуть? На мой то я не раз уже ссылки давал. И насколько сложно использовать внешнюю память, там хорошо видно.
randomsimplenumber
Сколько стоит микро sd карта на 16 Гб? Сколько места занимает 1 рецепт? Если в виде циклограммы - в какой момент какой релюшкой щелкать - наверное, 1К.
Пожалуй, все существующие рецепты кофе влезут в вашу esp32 ;)ptr128
Вот это я и называю идиотизмом. Вы бы еще программы лифта предложили на SD-карте держать )))
Сначала дайте ссылку на Ваш github, а потом я буду отвечать на подобные вопросы.
randomsimplenumber
Сколько стоит emmc память? На каком волшебном принципе работает память в вашем esp32?Упсь, я дверью ошибся. Тут экзамен идет. Ухожу, ухожу..
:D
ptr128
Точно ошиблись. Коллеги как то делали на ПЛК110 Овен только обжарку кофе. Ну так не хватило 6МБ энергонезависимой памяти на борту для всех рецептур, которые хотел заказчик. Пришлось делать загрузку рецептур из CODESYS по Ethernet.
Mishootk
Неужели это не диагностический режим? Это допустимо на этапе конфигурирования. Но это не для "пользователя".
ptr128
То есть пользователю нельзя даже кинокамеру давать, так как там плёнка заканчивается? )))
Mishootk
Программы попадались, это еще меньшее из зол.
А вот когда так начинает вести себя устройство с 5 кнопками... Хорошо, если есть кнопка (или способ) сброса к заводским настройкам.
Mishootk
Для устройства, которое используется еще кем-то кроме его создателя, это плохой дизайн.
Не предлагать. Придумайте интерфейс пользователя заново.
ptr128
Ну так отберите у жены и тёщи смартфоны, ведь там тоже памяти может не хватить )))
А то ведь такой ужас! Снимает пользователь видео, а ему вдруг смартфон заявляет, что память закончилась. Очень интересно, какой же дизайн собрались предложить для этого Вы? )))
Mishootk
О, как это знакомо. Контролировать объемы, не допускать переполнения, предупреждать о нехватке ресурсов - все правильно. Сложно - придумать как это сделать на языке пользователя, чтобы было понятно, не мешало работать и не приводило к неожиданным проблемам.
Было у меня на каком-то предыдущем смартфоне. Программы просто наедались, рос кэш, росли данные. И какая-то мелкая но очень нужная, хоть и редкоиспользуемая программка в одном из своих экранов просто выдавала пустой список вместо моих данных. Через аккаунт на сайте я все вижу, а клиент не показывает. Хорошо, я интуитивно глюки чую (работа такая), память подосвободил, к жизни вернул.
Сумеете предложить грамотный дизайн поливалки с наращиваемым количеством горшков, чтобы любая домохозаяйка справилась - отлично. Требования - любое (пока памяти хватит) количество горшков и любое расписание на каждый (элемент расписания, будильник, тоже отъедает память). Управление - только через меню. Уровень подготовки - нулевой, т.е. не сложнее бытовой техники на кухне.
Я с такими случаями сталкивался. Проще - сразу сказать что 10 горшков, в каждом по 3 расписания. Хочешь больше - бери второе устройство. Хотя как программист я знаю, что можно 15 горшков втолкать с 1 расписанием. Или 5 горшков, но где-то 10 расписаний, где-то 1...
ptr128
Ничего сложного. Я же уже давно писал решение:
"внятно информировать пользователя, какие из заданных им параметров можно изменить и в какую сторону, чтобы программа могла продолжить работу.
На ESP32 я прямо в веб-интерфейсе такие параметры подсвечивал, что даже домохозяйке должно быть понятно."