ESP8266:Примеры/Смарт-система для орошения растений на базе ESP8266
Поддержать проект | Содержание | Введение | Модули | AT-команды | Прошивки | Примеры | Проекты | Типовые проблемы | Библиотеки |
![]() |
Черновик |
Смарт-система для орошения растений на базе ESP8266[1]
Эта статья рассказывает, как при помощи модуля ESP8266 и сервиса EasyIoT Cloud собрать автоматическую систему для орошения растений. Эта система позволит удаленно управлять мотором водяной помпы и, следовательно, влажностью почвы. Благодаря этому проекту вы можете со спокойной душой отправиться в отпуск и поливать домашние растения, просто нажимая на кнопки в смартфоне или даже переключив систему в автоматический режим.
Введение
Функции, которыми будет оснащена наша система орошения растений:
- Удаленное управление водяной помпой
- Автоматическое орошение, если влажность почвы понизится до определенного уровня
- Автоматический/ручной режим
- Отображение данных о влажности почвы и статусе водяной помпы
- Запись логов о влажности почвы
Орошение можно запустить прямо из браузерного интерфейса, и в этом случае помпа начнет качать воду в течение 10 секунд. Кроме того, орошение можно выставить в автоматический режим. В этом случае вы задаете в браузерном интерфейсе пороговое значение для влажности почвы, и если значение упадет ниже заданного порога, через система на 10 секунд включит водяную помпу. Через 10 минут система снова проверит влажность почвы и, если потребуется, снова включит помпу.
[Видео – https://www.youtube.com/watch?v=NArYvZaukw8]
Пользовательский интерфейс EasyIoT Cloud:
Данные о влажности почвы:
Необходимые компоненты
- Один модуль ESP8266
- Одна водяная помпа
- Один датчик влажности почвы
- Один понижающий преобразователь напряжения на базе LM2596
- Один 12-вольтовый источник питания
- Один транзистор 2N2222 (типа NPN)
- Одна макетная плата MB102
- Один резистор на 5 кОм
Настройка 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-вольтовое питание, а ESP8266 – 3,3-вольтовое, нам нужно будет добавить в цепь транзистор, чтобы управлять 5-вольтовым реле при помощи 3,3 вольт. Кроме того, к контакту D0 на ESP8266 нужно подключить транзистор, а между D0 и транзистором – резистор на 5 кОм. Если вы подключите реле-модуль напрямую к модулю ESP8266, то просто поджарите его.
Другая сторона реле-модуля должна быть подключена к мотору водяной помпы и регулируемому источнику питания. Напряжение для мотора водяной помпы нужно поставить между 6 и 9 вольтами.
Подключение кнопки для включения/выключения помпы
Если вы используете плату NodeMCU, то подключать кнопку не потребуется – можно воспользоваться кнопкой Flash на плате. Но если вы используете другой тип модуля ESP8266, тогда возьмите резистор на 10 кОм, а затем подключите одну его сторону к 3,3 вольтам, а вторую – к контакту D3. Потом подключите контакт D3 к контакту GND, а между ними – кнопку.
См.также
Внешние ссылки