ESP8266:Примеры/Смарт-система для орошения растений на базе ESP8266

Материал из Онлайн справочника
Перейти к навигации Перейти к поиску

Перевод: Максим Кузьмин (Cubewriter) Контакты:</br>* Skype: cubewriter</br>* E-mail: cubewriter@gmail.com</br>* Максим Кузьмин на freelance.ru
Проверка/Оформление/Редактирование: Мякишев Е.А.


Pixel Art Mini Meow Animated.gif Черновик


Смарт-система для орошения растений на базе ESP8266[1]

Эта статья рассказывает, как при помощи модуля ESP8266 и сервиса EasyIoT Cloud собрать автоматическую систему для орошения растений. Эта система позволит удаленно управлять мотором водяной помпы и, следовательно, влажностью почвы. Благодаря этому проекту вы можете со спокойной душой отправиться в отпуск и поливать домашние растения, просто нажимая на кнопки в смартфоне или даже переключив систему в автоматический режим.

Введение

Функции, которыми будет оснащена наша система орошения растений:

  • Удаленное управление водяной помпой
  • Автоматическое орошение, если влажность почвы понизится до определенного уровня
  • Автоматический/ручной режим
  • Отображение данных о влажности почвы и статусе водяной помпы
  • Запись логов о влажности почвы

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

[Видео – https://www.youtube.com/watch?v=NArYvZaukw8]

Пользовательский интерфейс EasyIoT Cloud:

ESP8266 smart plant irrigation system 1.png

Данные о влажности почвы:

ESP8266 smart plant irrigation system 2.png

Необходимые компоненты

  • Один модуль ESP8266
  • Одна водяная помпа
  • Один датчик влажности почвы
  • Один понижающий преобразователь напряжения на базе LM2596
  • Один 12-вольтовый источник питания
  • Один транзистор 2N2222 (типа NPN)
  • Одна макетная плата MB102
  • Один резистор на 5 кОм
ESP8266 smart plant irrigation system 3.jpg

Настройка EasyIoT Cloud

Зарегистрируйтесь в сервисе EasyIoT Cloud. Зайдя в сервис, создайте новый модуль для системы орошения. О том, как выставить новый тип модуля (в нашем случае – ZMT_IRRIGATOR), читайте в этом руководстве.

Программа

Программа написана в IDE Arduino. Она использует библиотеку «esp-mqtt» (это враппер-библиотека MQTT для ESP8266, позволяющая коммуницировать с сервисом EasyIoT Cloud и управлять его настройками). Программу можно скачать с GitHub.

В программе нужно поменять следующие строчки (они отвечают за SSID, пароль к SSID, а также за логин и пароль к EasyIoT Cloud):

#define AP_SSID "xxx"
#define AP_PASSWORD "xxx"
#define EIOTCLOUD_USERNAME "xxx"
#define EIOTCLOUD_PASSWORD "xxx"

Затем загрузите программу на плату NodeMCU. Вместо платы NodeMCU можно воспользоваться и другим модулем ESP8266, но в этом случае вам понадобятся дополнительное питание и адаптер USB-Serial от FTDI.

  1 #include <ESP8266WiFi.h>
  2 #include <MQTT.h>
  3 #include <EEPROM.h>
  4 
  5 #define AP_SSID     "xxx"
  6 #define AP_PASSWORD "xxx"  
  7 
  8 #define EIOTCLOUD_USERNAME "xxx"
  9 #define EIOTCLOUD_PASSWORD "xxx"
 10 
 11 // создаем объект MQTT:
 12 #define EIOT_CLOUD_ADDRESS "cloud.iot-playground.com"
 13 
 14 
 15 
 16 #define PIN_PUMP         BUILTIN_LED //D0  // для встроенного 
 17                                            // светодиода на NodeMCU
 18 #define PIN_BUTTON       D3  // для кнопки «Flash» на NodeMCU
 19 #define PIN_HUM_ANALOG   A0  // для измерения влажности
 20 
 21 
 22 #define MAX_ANALOG_VAL         956
 23 #define MIN_ANALOG_VAL         250
 24 
 25 #define IRRIGATION_TIME        10   // время орошения (в секундах)
 26 #define IRRIGATION_PAUSE_TIME  300  // пауза между орошениями 
 27                                     // (в секундах); только для 
 28                                     // автоматического орошения
 29 
 30 // значения для статуса оросителя:
 31 typedef enum {
 32   s_idle             = 0,  // простой оросителя
 33   s_irrigation_start = 1,  // запуск орошения
 34   s_irrigation       = 2,  // орошение
 35   s_irrigation_stop  = 3,  // остановка орошения
 36 } e_state;
 37 
 38 
 39 #define CONFIG_START 0
 40 #define CONFIG_VERSION "v01"
 41 
 42 struct StoreStruct {
 43   // это чтобы убедиться, что это ваши настройки:
 44   char version[4];
 45   // переменные для ваших настроек:
 46   uint moduleId;  // ID модуля
 47 } storage = {
 48   CONFIG_VERSION,
 49   // дефолтный модуль «0»
 50   0,
 51 };
 52 
 53 #define PARAM_HUMIDITY_TRESHOLD   "/Sensor.Parameter1"
 54 #define PARAM_MANUAL_AUTO_MODE    "/Sensor.Parameter2"
 55 #define PARAM_PUMP_ON             "/Sensor.Parameter3"
 56 #define PARAM_HUMIDITY            "/Sensor.Parameter4"
 57 
 58 
 59 #define MS_IN_SEC  1000 // 1S  
 60 
 61 
 62 MQTT myMqtt("", EIOT_CLOUD_ADDRESS, 1883);
 63 
 64 // переменные:
 65 int state;
 66 bool stepOk = false;
 67 int soilHumidityThreshold;
 68 bool autoMode;
 69 String valueStr("");
 70 String topic("");
 71 boolean result;
 72 int lastAnalogReading;
 73 bool autoModeOld;
 74 int soilHumidityThresholdOld;
 75 unsigned long startTime;
 76 int soilHum;
 77 int irrigatorCounter;
 78 
 79 void setup() {
 80   state = s_idle;
 81   pinMode(PIN_PUMP, OUTPUT); 
 82   pinMode(PIN_BUTTON, INPUT);
 83 
 84   autoMode = false;
 85   stepOk = false;
 86   soilHumidityThresholdOld = -1;
 87   startTime = millis();
 88   soilHum = -1;
 89   
 90   Serial.begin(115200);
 91 
 92   WiFi.mode(WIFI_STA);  
 93   WiFi.begin(AP_SSID, AP_PASSWORD);
 94 
 95   Serial.println();
 96   Serial.println();
 97   Serial.print("Connecting to ");  //  "Подключение к "
 98   Serial.println(AP_SSID);
 99     
100   while (WiFi.status() != WL_CONNECTED) {
101     delay(500);
102     Serial.print(".");
103   };
104 
105   Serial.println("WiFi connected");  //  "Подключились к WiFi"
106   Serial.println("Connecting to MQTT server");  
107              //  "Подключение к MQTT-серверу"
108 
109 
110   EEPROM.begin(512);
111   loadConfig();
112 
113 
114   // задаем ID клиента;
115   // генерируем название клиента на основе MAC-адреса 
116   // и последних 8 бит счетчика микросекунд:
117   String clientName;
118   //clientName += "esp8266-";
119   uint8_t mac[6];
120   WiFi.macAddress(mac);
121   clientName += macToStr(mac);
122   clientName += "-";
123   clientName += String(micros() & 0xff, 16);
124   myMqtt.setClientId((char*) clientName.c_str());
125 
126   Serial.print("MQTT client id:");  //  "ID MQTT-клиента"
127   Serial.println(clientName);
128 
129   // функции обратного вызова:
130   myMqtt.onConnected(myConnectedCb);
131   myMqtt.onDisconnected(myDisconnectedCb);
132   myMqtt.onPublished(myPublishedCb);
133   myMqtt.onData(myDataCb);
134   
135   //////Serial.println("connect mqtt...");
136                    //  "Подключение к MQTT..."
137   myMqtt.setUserPwd(EIOTCLOUD_USERNAME, EIOTCLOUD_PASSWORD);  
138   myMqtt.connect();
139 
140   delay(500);
141   
142   Serial.print("ModuleId: ");
143   Serial.println(storage.moduleId);
144 
145 
146   // создаем модуль, если нужно:
147   if (storage.moduleId == 0)
148   {
149     // создаем модуль:
150     Serial.println("create module: /NewModule");
151                //  "Создание модуля: /NewModule"
152 
153     storage.moduleId = myMqtt.NewModule();
154 
155     if (storage.moduleId == 0)
156     {
157       Serial.println("Module NOT created. Check module limit");
158                  //  "Модуль не создан. Проверьте лимит модулей"
159 
160       while(1)
161         delay(100);
162     }
163 
164    // задаем тип модуля:
165     Serial.println("Set module type");  //  "Установка типа модуля"    
166     myMqtt.SetModuleType(storage.moduleId, "ZMT_IRRIGATOR");
167 
168     // создаем параметр Sensor.Parameter1;
169     // это пороговое значение для влажности почвы:
170     Serial.println("new parameter: /"+String(storage.moduleId)+ PARAM_HUMIDITY_TRESHOLD);    
171     myMqtt.NewModuleParameter(storage.moduleId, PARAM_HUMIDITY_TRESHOLD);
172 
173     // устанавливаем флаг «IsCommand»:
174     Serial.println("set isCommand: /"+String(storage.moduleId)+ PARAM_HUMIDITY_TRESHOLD);    
175     myMqtt.SetParameterIsCommand(storage.moduleId, PARAM_HUMIDITY_TRESHOLD, true);
176 
177 
178     // создаем параметр Sensor.Parameter2;
179     // «0» – ручной режим; «1» – автоматический режим:
180     Serial.println("new parameter: /"+String(storage.moduleId)+ PARAM_MANUAL_AUTO_MODE);    
181     myMqtt.NewModuleParameter(storage.moduleId, PARAM_MANUAL_AUTO_MODE);
182 
183     // устанавливаем флаг «IsCommand»:
184     Serial.println("set isCommand: /"+String(storage.moduleId)+ PARAM_MANUAL_AUTO_MODE);    
185     myMqtt.SetParameterIsCommand(storage.moduleId, PARAM_MANUAL_AUTO_MODE, true);
186 
187     
188     // создаем параметр Sensor.Parameter3;
189     // он отвечает за включение/выключение водяной помпы:
190     Serial.println("new parameter: /"+String(storage.moduleId)+ PARAM_PUMP_ON);    
191     myMqtt.NewModuleParameter(storage.moduleId, PARAM_PUMP_ON);
192 
193 
194     // устанавливаем флаг «IsCommand»:
195     Serial.println("set isCommand: /"+String(storage.moduleId)+ PARAM_PUMP_ON);    
196     myMqtt.SetParameterIsCommand(storage.moduleId, PARAM_PUMP_ON, true);
197 
198 
199     // создаем параметр Sensor.Parameter4;
200     // он отвечает за текущую влажность почвы:
201     Serial.println("new parameter: /"+String(storage.moduleId)+ PARAM_HUMIDITY);    
202     myMqtt.NewModuleParameter(storage.moduleId, PARAM_HUMIDITY);
203 
204 
205     // задаем описание:
206     Serial.println("set description: /"+String(storage.moduleId)+ PARAM_HUMIDITY);    
207     myMqtt.SetParameterDescription(storage.moduleId, PARAM_HUMIDITY, "Soil hum.");
208 
209     // задаем единицу измерения:
210     Serial.println("set Unit: /"+String(storage.moduleId)+ PARAM_HUMIDITY);    
211     myMqtt.SetParameterUnit(storage.moduleId, PARAM_HUMIDITY, "%");
212 
213     // устанавливаем флаг «dbLogging»;
214     // он отвечает за запись логов в базу данных:
215     Serial.println("set Unit: /"+String(storage.moduleId)+ PARAM_HUMIDITY);    
216     myMqtt.SetParameterDBLogging(storage.moduleId, PARAM_HUMIDITY, true);
217 
218     // сохраняем ID нового модуля:
219     saveConfig();
220   }
221 
222   subscribe();
223   
224   lastAnalogReading = analogRead(PIN_HUM_ANALOG); 
225 
226   autoModeOld = !autoMode;
227 }
228 
229 void loop() {
230   while (WiFi.status() != WL_CONNECTED) {
231     delay(500);
232 #ifdef DEBUG        
233     Serial.print(".");
234 #endif
235   }
236 
237   int in = digitalRead(PIN_BUTTON);
238 
239   //Serial.println(in);
240   if (in == 0)
241   {
242     while(digitalRead(PIN_BUTTON) == 0)
243       delay(100);
244 
245     if (state == s_idle || state == s_irrigation_start)
246       state = s_irrigation_start;
247     else
248       state = s_irrigation_stop;
249   }
250   
251 
252 
253   // публикуем изменения автоматического режима:
254   if (autoModeOld != autoMode)
255   {
256     autoModeOld = autoMode;
257 
258     if (autoMode)    
259       valueStr = String("1");
260     else
261       valueStr = String("0");
262     
263     topic  = "/"+String(storage.moduleId)+ PARAM_MANUAL_AUTO_MODE;
264     result = myMqtt.publish(topic, valueStr, 0, 1);
265 
266     Serial.print("Publish topic: ");  //  "Публикация в топик: "
267     Serial.print(topic);
268     Serial.print(" value: ");  //  " значение: "
269     Serial.println(valueStr);  
270   }
271 
272   // публикуем изменения порогового значения:
273   if (soilHumidityThreshold != soilHumidityThresholdOld)
274   {
275     soilHumidityThresholdOld = soilHumidityThreshold;
276     valueStr = String(soilHumidityThreshold);
277     
278     topic  = "/"+String(storage.moduleId)+ PARAM_HUMIDITY_TRESHOLD;
279     result = myMqtt.publish(topic, valueStr, 0, 1);
280 
281     Serial.print("Publish topic: ");  //  "Публикация в топик: "
282     Serial.print(topic);
283     Serial.print(" value: ");         //  " значение: "
284     Serial.println(valueStr);  
285   }
286   
287   if (IsTimeout())
288   {
289     startTime = millis();
290     // выполняем обработку данных каждую секунду:
291     int aireading = analogRead(PIN_HUM_ANALOG);
292 
293     Serial.print("Analog value: ");  //  "Аналоговое значение: "
294     Serial.print(aireading);
295     Serial.print(" ");
296     // фильтр:
297     lastAnalogReading += (aireading - lastAnalogReading) / 10;  
298     Serial.print(lastAnalogReading); 
299    
300    // вычисляем влажность почвы (в процентах): 
301    int newSoilHum = map(lastAnalogReading, MIN_ANALOG_VAL, MAX_ANALOG_VAL, 100, 0);  
302    Serial.print(", Soil hum %:");  //  ", Влажность почвы в %:"
303    Serial.println(newSoilHum); 
304         
305    // ограничиваем до 0-100%:
306    if (newSoilHum < 0)
307       newSoilHum = 0;
308 
309     if (newSoilHum > 100)
310       newSoilHum = 100;
311  
312    // если произошли изменения во влажности почвы, сообщаем о них: 
313    if (soilHum != newSoilHum)
314    {
315      soilHum = newSoilHum;
316      //esp.send(msgHum.set(soilHum)); 
317      
318      valueStr = String(soilHum);
319      topic  = "/"+String(storage.moduleId)+ PARAM_HUMIDITY;
320      result = myMqtt.publish(topic, valueStr, 0, 1);
321 
322      Serial.print("Publish topic: ");  //  "Публикация в топик: "
323      Serial.print(topic);
324      Serial.print(" value: ");  //  " значение: "
325      Serial.println(valueStr);  
326    }
327    
328    
329    // код для работы с состоянием оросителя:
330    switch(state)
331    {
332      case s_idle:     
333        if (irrigatorCounter <= IRRIGATION_PAUSE_TIME)
334          irrigatorCounter++;
335        
336        if (irrigatorCounter >= IRRIGATION_PAUSE_TIME && autoMode)
337        {
338          if (soilHum <= soilHumidityThreshold)
339            state = s_irrigation_start;       
340        }         
341        break;
342      case s_irrigation_start:
343        irrigatorCounter = 0;
344        digitalWrite(PIN_PUMP, HIGH);
345        //esp.send(msgMotorPump.set((uint8_t)1));       
346        valueStr = String(1);
347        topic  = "/"+String(storage.moduleId)+ PARAM_PUMP_ON;
348        result = myMqtt.publish(topic, valueStr, 0, 1);    
349 
350        Serial.print("Publish topic: ");  //  "Публикация в топик: "
351        Serial.print(topic);
352        Serial.print(" value: ");  //  " значение: "
353        Serial.println(valueStr);  
354  
355        state = s_irrigation;
356        break;
357      case s_irrigation:
358        if (irrigatorCounter++ > IRRIGATION_TIME)
359          state = s_irrigation_stop;
360        break;
361      case s_irrigation_stop:
362        irrigatorCounter = 0;
363        state = s_idle;
364        //esp.send(msgMotorPump.set((uint8_t)0));
365        valueStr = String(0);
366        topic  = "/"+String(storage.moduleId)+ PARAM_PUMP_ON;
367        result = myMqtt.publish(topic, valueStr, 0, 1);    
368 
369        digitalWrite(PIN_PUMP, LOW);
370        break;
371    }
372   }
373   
374 }
375 
376 void loadConfig() {
377   // чтобы убедиться, что настройки есть, и они ваши;
378   // если настроек найдено не будет, будут использованы 
379   // настройки по умолчанию:
380   if (EEPROM.read(CONFIG_START + 0) == CONFIG_VERSION[0] &&
381       EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1] &&
382       EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2])
383     for (unsigned int t=0; t<sizeof(storage); t++)
384       *((char*)&storage + t) = EEPROM.read(CONFIG_START + t);
385 }
386 
387 void saveConfig() {
388   for (unsigned int t=0; t<sizeof(storage); t++)
389     EEPROM.write(CONFIG_START + t, *((char*)&storage + t));
390 
391   EEPROM.commit();
392 }
393 
394 
395 String macToStr(const uint8_t* mac)
396 {
397   String result;
398   for (int i = 0; i < 6; ++i) {
399     result += String(mac[i], 16);
400     if (i < 5)
401       result += ':';
402   }
403   return result;
404 }
405 
406 void waitOk()
407 {
408   while(!stepOk)
409     delay(100);
410  
411   stepOk = false;
412 }
413 
414 boolean IsTimeout()
415 {
416   unsigned long now = millis();
417   if (startTime <= now)
418   {
419     if ( (unsigned long)(now - startTime )  < MS_IN_SEC ) 
420       return false;
421   }
422   else
423   {
424     if ( (unsigned long)(startTime - now) < MS_IN_SEC ) 
425       return false;
426   }
427 
428   return true;
429 }
430 
431 
432 void subscribe()
433 {
434   if (storage.moduleId != 0)
435   {
436     // параметр Sensor.Parameter1 – пороговое значение:
437     myMqtt.subscribe("/"+String(storage.moduleId)+ PARAM_HUMIDITY_TRESHOLD);
438   
439     // параметр Sensor.Parameter2 – 
440     // переключение между ручным и автоматическим режимами:
441     myMqtt.subscribe("/"+String(storage.moduleId)+ PARAM_MANUAL_AUTO_MODE);
442   
443     // параметр Sensor.Parameter3 – включение/выключение помпы:
444     myMqtt.subscribe("/"+String(storage.moduleId)+ PARAM_PUMP_ON);
445   }
446 }
447 
448 
449 void myConnectedCb() {
450 #ifdef DEBUG
451   Serial.println("connected to MQTT server");
452              //  "Подключение к серверу MQTT"
453 #endif
454   subscribe();
455 }
456 
457 void myDisconnectedCb() {
458 #ifdef DEBUG
459   Serial.println("disconnected. try to reconnect...");
460              //  "Произошло отключение. Пытаемся переподключиться..."
461 #endif
462   delay(500);
463   myMqtt.connect();
464 }
465 
466 void myPublishedCb() {
467 #ifdef DEBUG  
468   Serial.println("published.");  //  "Опубликовано."
469 #endif
470 }
471 
472 void myDataCb(String& topic, String& data) {  
473 #ifdef DEBUG
474   Serial.print("Receive topic: ");  //  "Принимающий топик: "
475   Serial.print(topic);
476   Serial.print(": ");
477   Serial.println(data);
478 #endif
479   if (topic == String("/"+String(storage.moduleId)+ PARAM_HUMIDITY_TRESHOLD))
480   {
481     soilHumidityThreshold = data.toInt();
482     Serial.println("soilHumidityThreshold");
483     Serial.println(data);
484   }
485 
486   else if (topic == String("/"+String(storage.moduleId)+ PARAM_MANUAL_AUTO_MODE))
487   {
488     autoMode = (data == String("1"));
489     Serial.println("Auto mode");  //  "Автоматический режим"
490     Serial.println(data);
491   }
492   else if (topic == String("/"+String(storage.moduleId)+ PARAM_PUMP_ON))
493   {
494     //switchState = (data == String("1"))? true: false;
495     if (data == String("1"))
496       state = s_irrigation_start;
497     else
498       state = s_irrigation_stop;
499     Serial.println("Pump");  //  "Водяная помпа"
500     Serial.println(data);
501   }
502 }

Подключение компонентов

Чтобы упростить цепь проекта, мы воспользуемся платой NodeMCU. Впрочем, вы можете использовать любой другой модуль с чипом ESP8266, но вам дополнительно понадобится 3,3-вольтовое питание.

Подключение датчика влажности почвы

Чтобы обеспечить питанием датчик влажности почвы, подключите контакт VCC на датчике к 3.3V на ESP8266, а контакт GND на датчике – к контакту GND на ESP8266. Затем подключите контакт AO на датчике (он отвечает за вывод аналоговых данных) к входному аналоговому контакту A0 на ESP8266. Контакт DO на датчике оставьте неподключенным.

Подключение мотора водяной помпы к ESP8266

Для этого понадобится реле-модуль. Поскольку реле-модуль использует 5-вольтовое питание, а ESP82663,3-вольтовое, нам нужно будет добавить в цепь транзистор, чтобы управлять 5-вольтовым реле при помощи 3,3 вольт. Кроме того, к контакту D0 на ESP8266 нужно подключить транзистор, а между D0 и транзистором – резистор на 5 кОм. Если вы подключите реле-модуль напрямую к модулю ESP8266, то просто поджарите его.

Другая сторона реле-модуля должна быть подключена к мотору водяной помпы и регулируемому источнику питания. Напряжение для мотора водяной помпы нужно поставить между 6 и 9 вольтами.

ESP8266 motor pump.png

Подключение кнопки для включения/выключения помпы

Если вы используете плату NodeMCU, то подключать кнопку не потребуется – можно воспользоваться кнопкой Flash на плате. Но если вы используете другой тип модуля ESP8266, тогда возьмите резистор на 10 кОм, а затем подключите одну его сторону к 3,3 вольтам, а вторую – к контакту D3. Потом подключите контакт D3 к контакту GND, а между ними – кнопку.

ESP8266 smart plant irrigation 5.png

См.также

Внешние ссылки