Автополив с "боевым" прошлым

Что можно сделать полезного из гильзы обычного оружейного патрона? Кулон, зажигалку?.. В этой статье она пройдет путь от орудия войны до распределительного золотника в системе автополива. Конечно, не без помощи электропривода, насоса и электронной системы управления. Под катом разберем внутреннюю кухню, философию устройства, а также перспективы практического применения. Спойлер: теперь стреляем только водой!
Начну с плюсов. Первый — многоканальность полива. Второй — относительная простота реализации. Ну и… все.

Далее автор описывает, что у других все неоптимально, и пора сделать оптимально. Итого предлагает систему на 5 погружных насосов, 5 реле и интерфейс для настройки и управления — ардуино с символьным дисплеем и энкодером. Соответственно, расширяемость системы в N раз потребует увеличения затрат опять же в N раз (практически). Что если я скажу, что можно расширяться куда дешевле?
Мне пришла в голову мысль, что, вообще говоря, нам необязательно поливать все растения одновременно, ведь делать это нужно где-то раз в сутки. А если не нужна одновременность — зачем столько параллельных насосов? Я решил сделать систему с одним насосом и автоматической распределяющей системой на основе движущегося золотника.
В основе — недорогие компоненты: мотор-редуктор, помпа, микроконтроллер ESP8266 (как в умных розетках), пара реле и мосфет. Всё это управляет подачей воды по расписанию или через интернет. Корпус и механические детали я решил напечатать на 3D-принтере. Ну и гвоздь программы — золотник на основе гильзы пистолетного патрона. Да-да!

Все началось именно с него. Просто потому, что я живо у себя в голове представил устройство с движущейся гильзой и несколькими выводами под поливные шланги. Образ был настолько реален, что я незамедлительно приступил к разработке 3д модели распределителя.

Ничего не напоминает? Воображения мне хватило только чтобы сделать какое-то пистолетоподобное устройство с приводом в виде моторчика N20. Внутри устройства должен был двигаться поршень, который приводился бы в движения червячной передачей из строительной шпильки, проворачивающейся вокруг гайки M4.

Поршень же через другую шпильку передавал бы поступательное движение золотнику из гильзы, к которой с «естественного отверстия» подводилась бы вода, а с «искусственного» (просверленного в боку гильзы) она через отверстие в «стволе» поступала бы к выбранному растению. После печати я попробовал собрать устройство, но встретил сразу букет проблем.
Во-первых, гильза не хотела достаточно свободно двигаться в «стволе». Я несколько раз пробовал разные диаметры, и способы печати (вдоль, поперек). Я даже пробовал греть ствол при прохождении гильзы, чтобы она сама расширила себе отверстие. Это не работало. В итоге я все-таки нашел некий внутренний компромисс, чтобы гильза не сильно туго шла, но при этом достаточно плотно.
Во-вторых, я не придумал, как надежно соединить шпильку с золотником. Была идея напечатать некий переходник и склеить, но мне это показалось ненадежным. Повертев устройство в руках, я закинул его «в долгий ящик», пока вдохновение не вернется.

Прошел год… И снова мне на глаза попался этот проект. На этот раз я твердо решил закончить.
Во-первых, я стал гораздо лучше в пайке, поэтому первое, что пришло на ум для соединения шпильки и гильзы — пайка. Оставался вопрос, как все зафиксировать и припаять ровно. Ответ вас удивит. Нужен обычный старый добрый глазомер.

Гильзу я зажал в тиски, а шпильку максимально легонько придерживая, приставил сверху. Все предметы под действием силы тяжести тянет к земле. Но сейчас это плюс, потому что упрощает вертикальное позиционирование. А центровку я выполнил на глаз, целясь в центр выбитого капсюля. Для первой пайки железяк, считаю, вышло неплохо.
И да, собрав устройство в корпус, я убедился, что большого перекоса нет — гильза достаточно свободно ходила внутри.

Окрыленный успехом, я принялся за электронную часть. Схема устройства была собрана «из того, что было». А была парочка мосфетов, двойное реле и esp8266. И разумеется, паял на макетке. Ничего сложного нет ни в ее составлении, ни в ее сборке.


Сама схема довольно простая. Есть два концевика, которые сигнализируют о достижении поршня стенок девайса. Мосфет включает насос. Для насоса сделал еще повышайку, чтобы при желании увеличить мощность. Сдвоенное реле отвечает за управление ходом поршня распределителя. Стоит заметить, что такое использование компонентов не очень-то оптимальное, но об этом поговорим позднее.

Чтобы оживить схему, требуется прошивка микроконтроллера. Учитывая некоторую специфичность конструкции, с ней пришлось повозиться.
Я решил придерживаться ООП для лучшей организации кода и читаемости. В проекте присутствуют следующие классы: WebServer, DataStore, Pump, Router, Machine. Структурно они организованы следующим образом.

Итак, разберемся по порядку. Устройству нужна веб-морда для управления (я считаю, что это значительно удешевляет проект при сохранении эффективности). Для этого и существует WebServer. По сути класс основан на серверной библиотеке для ESP8266. Я заложил в код настройку периодичности полива, времени орошения каждого растения, а также мощность насоса.
Класс DataStore реализует хранилище данных и FTP-сервер для доступа к файлам устройства. В первую очередь это нужно для хранения настроек, во вторую можно динамически менять оформление страницы настроек, закидывая другой файл index.html. Настройки хранятся в файле settings в формате js-переменных. Файл script.js отвечает за динамическое изменение значений при движении ползунков. Сервер при запросе отправляет index.html, добавляя в него содержимое файла настроек и скрипта. Ко всему, такая организация кода позволяет легко добавить функциональность логирования (которую я в прототипе добавлять не стал).
Сама веб-морда выглядит так.

Класс Pump реализует ШИМ-управление насосом и ничего больше. Это не более чем обертка над стандартными методами ядра.
Класс Router намного интереснее. На схеме, как можно заметить, нет и намека на энкодер. Как же мы определяем, где находится золотник? Если коротко, то определяем примерно. Для этого нужно отсчитывать время между активациями мотора распределителя. Кроме того, я решил не заморачиваться с забиванием каких-либо настроек. Напротив, в коде реализована автокалибровка при старте устройства. Так что если конструкция изменится, а принцип — нет (равные расстояния между отверстиями), то код не нужно будет переписывать.
Собственно, калибровка происходит расчетом времени перемещения от начала до конца пути следования золотника. Затем время делится на отрезки между отверстиями и далее при перемещении к конкретному отверстию рассчитывается конкретный период включения мотора и направление движения. Концевики обрабатываются функциональными прерываниями, хотя на самом деле вполне можно опрашивать их состояние и раз в n миллисекунд — большой потери точности не будет.
И наконец, класс Machine включает в себя Pump и Router и управляет ими в соответствии с логикой работы девайса. А именно, задает последовательность перемещения распределителя и включения насоса, а также отслеживает время автоматического полива. В устройстве задан полив 6 растений, а седьмой вывод — сброс, чтобы избежать залития внутренностей роутера водой.
Самое интересное, что теперь главный файл состоит всего из пары вызовов функций!
Теперь осталось сделать только коробку под электронику и поместить насос в емкость для воды.
Итоговое устройство получилось немного аляповатым, но это не финальная ревизия, а заморачиваться мне не хотелось. Вот так оно работает. И правда работает! Правда, сайт почему-то не смог принять в себя гиф больше 8 мегабайт, так что кушаем что дают. В целом устройство перемещает золотник от первого отверстия к последнему где-то за минуту. Но позиционирование работает, и на том спасибо. Здесь я настроил на полив только 5-го горшка.


Но не все столь радужно. Перейдем к самокритике.
Как можно оценить данный проект? По сложности сборки — средняя сложность, плюс достаточно трудно достать под него «золотник», а потом его еще надо спаять и просверлить. Плюс нужна 3д печать, хоть это не такая уж проблема.
По функциональности. Задачу свою выполняет хорошо, но я не слишком рассчитал емкость для полива — в итоге он расходует ее за 2-4 использования. Если растения нуждаются в поливе раз в неделю, может и неплохо (заправлять раз в месяц), но если каждый день — печально. Есть вопросы к надежности погружного насоса — по отзывам мрут как мухи. Также, учитывая конструкцию, лучше сильно не опускать насос относительно рассады — вода может протечь в распределитель во время полива. В целом, это небольшая проблема при поливе мелкой рассады. Позиционирование при этом в целом удовлетворительное — некоторые положения не совсем соответствуют выводам для воды, но с текущим насосом это не проблема. Серьезных протечек он не создает.
По удобству использования — нет интеграции с серверным умным домом, шум при работе распределителя. Да, жужжит эта штука знатно, на ночь полив лучше не настраивать. Иногда, кстати, почему-то не грузится веб-морда, но мне кажется, это библиотека шалит (кто знает, подскажите, пожалуйста).
У меня уже есть мысли, как избавиться от редкого золотника, значительно улучшить надежность и точность позиционирования, а также интегрировать в умный дом. Во-первых, использование диафрагменного насоса. Во-вторых, стандартной водопроводной трубы и напечатанного золотника с резиновыми прокладками (исходная идея наизнанку). В-третьих, «выпрямить» конструкцию, чтобы привод распределителя находился сверху и не боялся бы протечек. Для определения положения реализовать оптический энкодер. По прикидкам, это значительно улучшит все параметры без сильного удорожания (можно еще добавить датчики влажности, но это уже даст некоторое удорожание). В ближайшее время попробую заняться. Есть и еще более продвинутая идея улучшения… Но о ней как-нибудь в другой раз.
В целом, считаю, мне удалось собрать модель, превосходящую по параметрам конструкцию Гайвера, и это было весело! Пришлось, правда,кое-кого продыря.. порыться в поиске гильз, но в итоге это того стоило. А вам желаю использовать свои «маслины» правильно! Не то, что некоторые…
Пролог
Начнем, пожалуй, с «вьетнамских флешбеков». Итак, пару лет назад мне на глаза попалось видео с канала AlexGyver об авоматическом поливе для комнатных растений на платформе Arduino. Видео с ютуба, пожалуй, не все смогут (или захотят) увидеть, потому вот соответствующая статья .Видео
Начну с плюсов. Первый — многоканальность полива. Второй — относительная простота реализации. Ну и… все.

Далее автор описывает, что у других все неоптимально, и пора сделать оптимально. Итого предлагает систему на 5 погружных насосов, 5 реле и интерфейс для настройки и управления — ардуино с символьным дисплеем и энкодером. Соответственно, расширяемость системы в N раз потребует увеличения затрат опять же в N раз (практически). Что если я скажу, что можно расширяться куда дешевле?
Интерлюдия
Мне пришла в голову мысль, что, вообще говоря, нам необязательно поливать все растения одновременно, ведь делать это нужно где-то раз в сутки. А если не нужна одновременность — зачем столько параллельных насосов? Я решил сделать систему с одним насосом и автоматической распределяющей системой на основе движущегося золотника.
В основе — недорогие компоненты: мотор-редуктор, помпа, микроконтроллер ESP8266 (как в умных розетках), пара реле и мосфет. Всё это управляет подачей воды по расписанию или через интернет. Корпус и механические детали я решил напечатать на 3D-принтере. Ну и гвоздь программы — золотник на основе гильзы пистолетного патрона. Да-да!

Все началось именно с него. Просто потому, что я живо у себя в голове представил устройство с движущейся гильзой и несколькими выводами под поливные шланги. Образ был настолько реален, что я незамедлительно приступил к разработке 3д модели распределителя.

Ничего не напоминает? Воображения мне хватило только чтобы сделать какое-то пистолетоподобное устройство с приводом в виде моторчика N20. Внутри устройства должен был двигаться поршень, который приводился бы в движения червячной передачей из строительной шпильки, проворачивающейся вокруг гайки M4.

Поршень же через другую шпильку передавал бы поступательное движение золотнику из гильзы, к которой с «естественного отверстия» подводилась бы вода, а с «искусственного» (просверленного в боку гильзы) она через отверстие в «стволе» поступала бы к выбранному растению. После печати я попробовал собрать устройство, но встретил сразу букет проблем.
Во-первых, гильза не хотела достаточно свободно двигаться в «стволе». Я несколько раз пробовал разные диаметры, и способы печати (вдоль, поперек). Я даже пробовал греть ствол при прохождении гильзы, чтобы она сама расширила себе отверстие. Это не работало. В итоге я все-таки нашел некий внутренний компромисс, чтобы гильза не сильно туго шла, но при этом достаточно плотно.
Во-вторых, я не придумал, как надежно соединить шпильку с золотником. Была идея напечатать некий переходник и склеить, но мне это показалось ненадежным. Повертев устройство в руках, я закинул его «в долгий ящик», пока вдохновение не вернется.

Кульминация
Прошел год… И снова мне на глаза попался этот проект. На этот раз я твердо решил закончить.
Во-первых, я стал гораздо лучше в пайке, поэтому первое, что пришло на ум для соединения шпильки и гильзы — пайка. Оставался вопрос, как все зафиксировать и припаять ровно. Ответ вас удивит. Нужен обычный старый добрый глазомер.

Гильзу я зажал в тиски, а шпильку максимально легонько придерживая, приставил сверху. Все предметы под действием силы тяжести тянет к земле. Но сейчас это плюс, потому что упрощает вертикальное позиционирование. А центровку я выполнил на глаз, целясь в центр выбитого капсюля. Для первой пайки железяк, считаю, вышло неплохо.
И да, собрав устройство в корпус, я убедился, что большого перекоса нет — гильза достаточно свободно ходила внутри.

Окрыленный успехом, я принялся за электронную часть. Схема устройства была собрана «из того, что было». А была парочка мосфетов, двойное реле и esp8266. И разумеется, паял на макетке. Ничего сложного нет ни в ее составлении, ни в ее сборке.


Сама схема довольно простая. Есть два концевика, которые сигнализируют о достижении поршня стенок девайса. Мосфет включает насос. Для насоса сделал еще повышайку, чтобы при желании увеличить мощность. Сдвоенное реле отвечает за управление ходом поршня распределителя. Стоит заметить, что такое использование компонентов не очень-то оптимальное, но об этом поговорим позднее.

Чтобы оживить схему, требуется прошивка микроконтроллера. Учитывая некоторую специфичность конструкции, с ней пришлось повозиться.
Я решил придерживаться ООП для лучшей организации кода и читаемости. В проекте присутствуют следующие классы: WebServer, DataStore, Pump, Router, Machine. Структурно они организованы следующим образом.

Итак, разберемся по порядку. Устройству нужна веб-морда для управления (я считаю, что это значительно удешевляет проект при сохранении эффективности). Для этого и существует WebServer. По сути класс основан на серверной библиотеке для ESP8266. Я заложил в код настройку периодичности полива, времени орошения каждого растения, а также мощность насоса.
#ifndef SERVER_H
#define SERVER_H
#include "DataStore.h"
#include <ESP8266WebServer.h>
class WebServer {
public:
WebServer(DataStore& datastore ): dataHandler(datastore) {
}
void begin() {
serv.begin();
serv.on("/", HTTP_GET, std::bind(&WebServer::handleRootPath, this));
serv.on("/save", HTTP_POST, std::bind(&WebServer::handleSaveSettings, this));
serv.on("/testall",HTTP_GET, std::bind(&WebServer::handleTestAll, this));
}
void handle() {
serv.handleClient();
}
private:
ESP8266WebServer serv;
DataStore& dataHandler;
void handleRootPath() {
serv.send(200, "text/html", dataHandler.getWebPage());
}
void handleSaveSettings() {
if (serv.hasArg("interval")
&&serv.hasArg("power")
&&serv.hasArg("water1")
&&serv.hasArg("water2")
&&serv.hasArg("water3")
&&serv.hasArg("water4")
&&serv.hasArg("water5")
&&serv.hasArg("water6"))
dataHandler.save(serv.arg("interval"),
serv.arg("power"),
serv.arg("water1"),
serv.arg("water2"),
serv.arg("water3"),
serv.arg("water4"),
serv.arg("water5"),
serv.arg("water6"));
}
void handleTestAll() {
Serial.println("test");
dataHandler.setWateringNow();
}
};
#endif
Класс DataStore реализует хранилище данных и FTP-сервер для доступа к файлам устройства. В первую очередь это нужно для хранения настроек, во вторую можно динамически менять оформление страницы настроек, закидывая другой файл index.html. Настройки хранятся в файле settings в формате js-переменных. Файл script.js отвечает за динамическое изменение значений при движении ползунков. Сервер при запросе отправляет index.html, добавляя в него содержимое файла настроек и скрипта. Ко всему, такая организация кода позволяет легко добавить функциональность логирования (которую я в прототипе добавлять не стал).
#ifndef DATASTORE_H
#define DATASTORE_H
#include <ESP8266FtpServer.h>
class DataStore {
public:
DataStore() {
}
void begin() {
root = "/";
SPIFFS.begin();
ftp.begin("engineer", "ftp_fs_begin");
settings="";
html = loadWebPageChunk("index.html");
script = loadWebPageChunk("script.js");
loadSettings();
}
const String getWebPage() {
String page = html+settings+script;
if (page="")
{
html = loadWebPageChunk("index.html");
script = loadWebPageChunk("script.js");
loadSettings();
}
return html+settings+script;
}
void handle() {
ftp.handleFTP();
}
int getInterval() {
return intervalWatering;
}
int getPumpPwm() {
return pumpPwm;
}
long getWateringTime(int index) {
return wateringTime[index];
}
void setWateringNow() {
isWateringNowSet = !isWateringNowSet;
}
bool isWateringNow() {
return isWateringNowSet;
}
bool save(
const String &interval, const String &power,
const String &water1,const String &water2,const String &water3, const String &water4,const String &water5,const String &water6) {
intervalWatering = strToIntervalWatering(interval);
pumpPwm = strToPumpPwm(power);
wateringTime[0] = strToWateringTime(water1);
wateringTime[1] = strToWateringTime(water2);
wateringTime[2] = strToWateringTime(water3);
wateringTime[3] = strToWateringTime(water4);
wateringTime[4] = strToWateringTime(water5);
wateringTime[5] = strToWateringTime(water6);
return storeSettings(
interval, power,
water1, water2, water3, water4, water5, water6);
}
static unsigned long strToIntervalWatering(const String &interval) {
return interval.toInt()*86400ul;
}
static int strToPumpPwm(const String &power) {
return (float)(power.toInt()*1023)/100;
}
static long strToWateringTime(const String &wateringTime) {
return wateringTime.toInt()*1000ul;
}
private:
bool storeSettings (
const String &interval, const String &power,
const String &water1,const String &water2,const String &water3, const String &water4,const String &water5,const String &water6) {
String path = root+"settings.js";
File file = SPIFFS.open(path, "w");
settings = (String)"<script>\n"
+"var interval = "+interval+";\n"
+"var power = "+power+";\n"
+"var water1 = "+water1+";\n"
+"var water2 = "+water2+";\n"
+"var water3 = "+water3+";\n"
+"var water4 = "+water4+";\n"
+"var water5 = "+water5+";\n"
+"var water6 = "+water6+";\n"
+"</script>";
int bytesWritten = file.print(settings);
file.close();
if (bytesWritten>0)
return true;
else
return false;
}
bool loadSettings() {
String path = root+"settings.js";
File file = SPIFFS.open(path, "r");
if (!file) {
Serial.println("Error opening file for reading");
return false;
}
String openTag = file.readStringUntil('\n');
String buf;
String interval = file.readStringUntil('\n');
buf = getValue(interval,'=',1);
buf.replace(";", "");
intervalWatering = strToIntervalWatering(buf);
String power = file.readStringUntil('\n');
buf = getValue(power,'=',1);
buf.replace(";", "");
pumpPwm = strToPumpPwm(buf);
String water1 = file.readStringUntil('\n');
buf = getValue(water1,'=',1);
buf.replace(";", "");
wateringTime[0] = strToWateringTime(buf);
String water2 = file.readStringUntil('\n');
buf = getValue(water2,'=',1);
buf.replace(";", "");
wateringTime[1] = strToWateringTime(buf);
String water3 = file.readStringUntil('\n');
buf = getValue(water3,'=',1);
buf.replace(";", "");
wateringTime[2] = strToWateringTime(buf);
String water4 = file.readStringUntil('\n');
buf = getValue(water4,'=',1);
buf.replace(";", "");
wateringTime[3] = strToWateringTime(buf);
String water5 = file.readStringUntil('\n');
buf = getValue(water5,'=',1);
buf.replace(";", "");
wateringTime[4] = strToWateringTime(buf);
String water6 = file.readStringUntil('\n');
buf = getValue(water6,'=',1);
buf.replace(";", "");
wateringTime[5] = strToWateringTime(buf);
String closeTag = file.readStringUntil('\n');
settings = openTag+'\n'
+interval+'\n'
+power+'\n'
+water1+'\n'
+water2+'\n'
+water3+'\n'
+water4+'\n'
+water5+'\n'
+water6+'\n'
+closeTag;
file.close();
if (settings.length()>0)
return true;
else
return false;
}
String getValue(String data, char separator, int index)
{
int found = 0;
int strIndex[] = {0, -1};
int maxIndex = data.length()-1;
for(int i=0; i<=maxIndex && found<=index; i++){
if(data.charAt(i)==separator || i==maxIndex){
found++;
strIndex[0] = strIndex[1]+1;
strIndex[1] = (i == maxIndex) ? i+1 : i;
}
}
return found>index ? data.substring(strIndex[0], strIndex[1]) : "";
}
const String loadWebPageChunk(const String& filename) {
String path = root+filename;
File file = SPIFFS.open(path, "r");
if (!file) {
Serial.println("Error opening file for reading");
return "";
}
String data = file.readString();
file.close();
return data;
}
String root;
String settings;
String script;
String html;
unsigned long intervalWatering = 30;
int pumpPwm = 512;
long wateringTime[6] = {1000, 1000, 1000, 1000, 1000, 1000};
bool isWateringNowSet = false;
FtpServer ftp;
};
#endif
Сама веб-морда выглядит так.

Класс Pump реализует ШИМ-управление насосом и ничего больше. Это не более чем обертка над стандартными методами ядра.
#ifndef PUMP_H
#define PUMP_H
#include <Arduino.h>
class Pump {
public:
Pump() {
}
Pump(byte digiPin) {
pin = digiPin;
};
void water(unsigned long workTime, int pwm) {
unsigned long t1 = millis();
while(millis()-t1 < 100) {
yield();
}
t1 = millis();
while(millis()-t1 < workTime) {
analogWrite(pin, pwm);
yield();
}
analogWrite(pin, 0);
t1 = millis();
while(millis()-t1 < 100) {
yield();
}
};
private:
byte pin;
};
#endif
Класс Router намного интереснее. На схеме, как можно заметить, нет и намека на энкодер. Как же мы определяем, где находится золотник? Если коротко, то определяем примерно. Для этого нужно отсчитывать время между активациями мотора распределителя. Кроме того, я решил не заморачиваться с забиванием каких-либо настроек. Напротив, в коде реализована автокалибровка при старте устройства. Так что если конструкция изменится, а принцип — нет (равные расстояния между отверстиями), то код не нужно будет переписывать.
#ifndef ROUTER_H
#define ROUTER_H
#include <Arduino.h>
#include <FunctionalInterrupt.h>
//static volatile int pos = -1;
class Router {
public:
Router() {
}
Router(byte frontMotorPin, byte backMotorPin, byte upSwitchPin, byte downSwitchPin) {
commandsWithoutCalib = 0;
this->frontMotorPin = frontMotorPin;
this->backMotorPin = backMotorPin;
this->upSwitchPin = upSwitchPin;
this->downSwitchPin = downSwitchPin;
}
void begin() {
pinMode(frontMotorPin, OUTPUT);
pinMode(backMotorPin, OUTPUT);
pinMode(upSwitchPin, INPUT);
pinMode(downSwitchPin, INPUT);
if (!calibrate())
calibrate();//перекалибровка если измерить dt не вышло
}
int getPos() {
return pos;
}
bool calibrate() {
digitalWrite(frontMotorPin, HIGH);
digitalWrite(backMotorPin, HIGH);
if (digitalRead(upSwitchPin)) {
goToUpPos();
}
unsigned long t0 = millis();
goToDownPos();
dt = (millis() - t0)/5;
if (dt<=100)
return false;
commandsWithoutCalib = 0;
Serial.print("dt=");
Serial.println(dt);
Serial.print("Position is ");
Serial.println(pos);
return true;
}
int move(int targetPos) {
if (targetPos<0 || targetPos>5)
return -1;
if (targetPos == pos)
return pos;
int deltaPos = targetPos - pos;
byte pin = frontMotorPin;
if (deltaPos<0){
deltaPos = -deltaPos;
pin = backMotorPin;
}
attachInterrupt(upSwitchPin, std::bind(&Router::upPos,this), FALLING);
attachInterrupt(downSwitchPin, std::bind(&Router::downPos,this), FALLING);
unsigned long t0 = millis();
unsigned long t1 = 0;
while(t1 < dt*deltaPos) {
digitalWrite(pin, LOW);
t1 = millis()-t0;
pos = -1;
if ((pos==0 || pos==5) && t1>1000)
break;
yield();
}
digitalWrite(pin, HIGH);
commandsWithoutCalib++;
detachInterrupt(upSwitchPin);
detachInterrupt(downSwitchPin);
if (dt*deltaPos - t1<dt*0.05) {
pos = targetPos;
return pos;
} else
return -1;
}
int getNumCommandsWithoutCalib() {
return commandsWithoutCalib;
}
uint32_t deltaT() {
return dt;
}
private:
// void goToEdgePos(byte edgePin, byte motorPin, int target) {
// attachInterrupt(edgePin, upPos, FALLING);
// while (pos!=target) {
// digitalWrite(motorPin, LOW);
// yield(); //interrupt!
// }
// detachInterrupt(edgePin);
// digitalWrite(motorPin, HIGH);
// }
void goToUpPos() {
attachInterrupt(upSwitchPin, std::bind(&Router::upPos,this), FALLING);
while (pos!=5) {
digitalWrite(frontMotorPin, LOW);
yield();
}
detachInterrupt(upSwitchPin);
digitalWrite(frontMotorPin, HIGH);
}
void goToDownPos() {
attachInterrupt(downSwitchPin, std::bind(&Router::downPos,this), FALLING);
while (pos!=0) {
digitalWrite(backMotorPin, LOW);
yield(); //interrupt!
}
detachInterrupt(downSwitchPin);
digitalWrite(backMotorPin, HIGH);
}
IRAM_ATTR void upPos() {
pos = 5;
}
IRAM_ATTR void downPos() {
pos = 0;
}
uint32_t dt = 0;
volatile int pos = -1;
int commandsWithoutCalib = 0;
byte frontMotorPin;
byte backMotorPin;
byte upSwitchPin;
byte downSwitchPin;
};
#endif
Собственно, калибровка происходит расчетом времени перемещения от начала до конца пути следования золотника. Затем время делится на отрезки между отверстиями и далее при перемещении к конкретному отверстию рассчитывается конкретный период включения мотора и направление движения. Концевики обрабатываются функциональными прерываниями, хотя на самом деле вполне можно опрашивать их состояние и раз в n миллисекунд — большой потери точности не будет.
И наконец, класс Machine включает в себя Pump и Router и управляет ими в соответствии с логикой работы девайса. А именно, задает последовательность перемещения распределителя и включения насоса, а также отслеживает время автоматического полива. В устройстве задан полив 6 растений, а седьмой вывод — сброс, чтобы избежать залития внутренностей роутера водой.
#ifndef MACHINE_H
#define MACHINE_H
#include "Pump.h"
#include "Router.h"
#include "DataStore.h"
class Machine {
public:
Machine(Router &router, Pump &pump, DataStore &datastore, int routeWithoutCalib = 5) :
router(router),
pump(pump),
dataHandler(datastore),
routeWithoutCalib(routeWithoutCalib) {
}
void handle() {
if (dataHandler.isWateringNow()) {
duty();
dataHandler.setWateringNow();
}
unsigned long seconds = (millis()/1000ul)%dataHandler.getInterval();
if (seconds == 0) {
duty();
}
}
private:
void duty() {
if (router.getNumCommandsWithoutCalib() >= routeWithoutCalib)
router.calibrate();
int start = 0;
int end = 5;
int dir = 1;
if (router.getPos() == 5) {
start = 5;
end = 0;
dir = -1;
}
for (int pos = start;pos <= end;pos += dir) {
router.move(pos);
pump.water(dataHandler.getWateringTime(pos), dataHandler.getPumpPwm());
yield();
}
}
int routeWithoutCalib;
Router& router;
Pump& pump;
DataStore& dataHandler;
};
#endif
Самое интересное, что теперь главный файл состоит всего из пары вызовов функций!
#include <ESP8266WiFi.h>
#include "WebServer.h"
#include"Machine.h"
const char* ssid = "#####";
const char* password = "****";
DataStore datastore = DataStore();
WebServer webServer = WebServer(datastore);
Pump pump = Pump(D2);
Router router = Router(D1, D0, D5, D6);
Machine machine = Machine(router, pump, datastore);
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to "); // "Подключились к "
Serial.println(ssid);
Serial.print("IP address: "); // "IP-адрес: "
Serial.println(WiFi.localIP());
SPIFFS.begin();
datastore.begin();
webServer.begin();
router.begin();
}
void loop() {
machine.handle();
webServer.handle();
datastore.handle();
}
Теперь осталось сделать только коробку под электронику и поместить насос в емкость для воды.
Итоговое устройство получилось немного аляповатым, но это не финальная ревизия, а заморачиваться мне не хотелось. Вот так оно работает. И правда работает! Правда, сайт почему-то не смог принять в себя гиф больше 8 мегабайт, так что кушаем что дают. В целом устройство перемещает золотник от первого отверстия к последнему где-то за минуту. Но позиционирование работает, и на том спасибо. Здесь я настроил на полив только 5-го горшка.


Но не все столь радужно. Перейдем к самокритике.
Развязка
Как можно оценить данный проект? По сложности сборки — средняя сложность, плюс достаточно трудно достать под него «золотник», а потом его еще надо спаять и просверлить. Плюс нужна 3д печать, хоть это не такая уж проблема.
По функциональности. Задачу свою выполняет хорошо, но я не слишком рассчитал емкость для полива — в итоге он расходует ее за 2-4 использования. Если растения нуждаются в поливе раз в неделю, может и неплохо (заправлять раз в месяц), но если каждый день — печально. Есть вопросы к надежности погружного насоса — по отзывам мрут как мухи. Также, учитывая конструкцию, лучше сильно не опускать насос относительно рассады — вода может протечь в распределитель во время полива. В целом, это небольшая проблема при поливе мелкой рассады. Позиционирование при этом в целом удовлетворительное — некоторые положения не совсем соответствуют выводам для воды, но с текущим насосом это не проблема. Серьезных протечек он не создает.
По удобству использования — нет интеграции с серверным умным домом, шум при работе распределителя. Да, жужжит эта штука знатно, на ночь полив лучше не настраивать. Иногда, кстати, почему-то не грузится веб-морда, но мне кажется, это библиотека шалит (кто знает, подскажите, пожалуйста).
У меня уже есть мысли, как избавиться от редкого золотника, значительно улучшить надежность и точность позиционирования, а также интегрировать в умный дом. Во-первых, использование диафрагменного насоса. Во-вторых, стандартной водопроводной трубы и напечатанного золотника с резиновыми прокладками (исходная идея наизнанку). В-третьих, «выпрямить» конструкцию, чтобы привод распределителя находился сверху и не боялся бы протечек. Для определения положения реализовать оптический энкодер. По прикидкам, это значительно улучшит все параметры без сильного удорожания (можно еще добавить датчики влажности, но это уже даст некоторое удорожание). В ближайшее время попробую заняться. Есть и еще более продвинутая идея улучшения… Но о ней как-нибудь в другой раз.
В целом, считаю, мне удалось собрать модель, превосходящую по параметрам конструкцию Гайвера, и это было весело! Пришлось, правда,

Самые обсуждаемые обзоры
+42 |
3662
119
|
+28 |
1312
21
|
«Вы не обязаны не держать глаза незакрытыми»
Мне было просто лень с лейкой прыгать, поэтому я сделал вообще просто.
Моторчик от стеклоомывателя, трубочки, форсунки, регулятор струек, выключатель и бачок :)
pikabu.ru/story/prosteyshaya_sistema_poliva_rasteniy_9043293?utm_source=linkshare&utm_medium=sharing
pikabu.ru/story/prodolzhenie_posta_prosteyshaya_sistema_poliva_rasteniy_9130674?utm_source=linkshare&utm_medium=sharing
Впрочем, если уж делать клапан-переключатель — то я бы, пожалуй, предпочёл классическую конструкцию — шток в виде толстостенной трубки, в ней две проточки под о-ринги, между ними — отверстие для выхода воды. Ну и двигал бы сервой, пожалуй.
А ещё есть mysku.club/blog/diy/103035.html — с такой стоимостью можно, пожалуй, с клапанами и голову не морочить…
Хотя с третей стороны на гравитационную подачу можно поставить расходометр.
пс как перестать считать проезжающие машины на гифке?
Автоматика и ардуины мне не нужны, а вот «механика» интересная.
«Погружной» можно сделать плавающим — на пенопластовом кружечке (такие в реальности 3кВт плавают — откачка из подтопляемых подвалов), и пластик-бутыль с широким горлом есть для этого, от 3-5-10 литров
Тоже периодически думаю о таком устройстве, но мне кажется здесь куда разумнее по соленоиду поставить на каждую линию и открывать их по очереди.
А от шума можно избавиться если взять перистальтическую помпу, или вообще убрать помпу из уравнения поставив бак повыше.
Достаточно было Sonoff (или практически любой «умной» розетки на W600/W800/etc) поставить с Tasmota/ESPhome…
( В корпусе дефендер — зарядка/ акум. 18650/ повышайка до 9 в. Ну и индикатор зарядки)
Все гораздо проще… один насос и капельный полив… Длительность и частота подбирается экспериментально и все равно не угадаете… необходимо контролировать. ( идеальная это реверсивный полив)Для домашних цветов терпимо, а вот для урожая в теплице мало эффективная система.
Лично у меня организовано через телеграм бот. на есп 8266 и есп 32. Возможности принудительно вкл\выкл некое устройство, по команде вывод информации о наличии воды в емкости, а также наличие доп освещения, снятие с датчиков данные о влажности, темпы и атм давления.При событиях отсутствие\наличие воды, вкл\ выкл освещения шлется информация в телегу.
Полив организован через внешний таймер и при вкл\ выкл насоса шлет сообщение.
даже при таком уровне автоматизации происходят форс мажоры нужно в каждый горшек вставлять датчик влажности почвы с сигнализацией( иногда форсунки забиваются и вода не поступает растению)
Поливать «ровно столько сколько нужно » не получиться, потому что нужно поливать с избытком… куда избыток влаги девать?
на сопляхмодулями. Только прошивку с нуля не писал: поставил Tasmota, всем управлял с роутера из crontab. Обычно перед каждый отпуском всё это достаётся и всегда надо что-то поправлять.Из того что читал: капельный полив оказался лучне чем всякие сенсоры почвы (не надежные и показывающие погоду на марсе). Хотите равномерный полив — ставьте контроллер и клапан на каждое растение. Еще вариант подводить воду в начале, вконце и середине шланга, использовать разные диаметры труб.