ESP8266:Примеры/Смарт-система для орошения растений на базе ESP8266
Черновик |
Смарт-система для орошения растений на базе 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.
#include <ESP8266WiFi.h>
#include <MQTT.h>
#include <EEPROM.h>
#define AP_SSID "xxx"
#define AP_PASSWORD "xxx"
#define EIOTCLOUD_USERNAME "xxx"
#define EIOTCLOUD_PASSWORD "xxx"
// создаем объект MQTT:
#define EIOT_CLOUD_ADDRESS "cloud.iot-playground.com"
#define PIN_PUMP BUILTIN_LED //D0 // для встроенного
// светодиода на NodeMCU
#define PIN_BUTTON D3 // для кнопки «Flash» на NodeMCU
#define PIN_HUM_ANALOG A0 // для измерения влажности
#define MAX_ANALOG_VAL 956
#define MIN_ANALOG_VAL 250
#define IRRIGATION_TIME 10 // время орошения (в секундах)
#define IRRIGATION_PAUSE_TIME 300 // пауза между орошениями
// (в секундах); только для
// автоматического орошения
// значения для статуса оросителя:
typedef enum {
s_idle = 0, // простой оросителя
s_irrigation_start = 1, // запуск орошения
s_irrigation = 2, // орошение
s_irrigation_stop = 3, // остановка орошения
} e_state;
#define CONFIG_START 0
#define CONFIG_VERSION "v01"
struct StoreStruct {
// это чтобы убедиться, что это ваши настройки:
char version[4];
// переменные для ваших настроек:
uint moduleId; // ID модуля
} storage = {
CONFIG_VERSION,
// дефолтный модуль «0»
0,
};
#define PARAM_HUMIDITY_TRESHOLD "/Sensor.Parameter1"
#define PARAM_MANUAL_AUTO_MODE "/Sensor.Parameter2"
#define PARAM_PUMP_ON "/Sensor.Parameter3"
#define PARAM_HUMIDITY "/Sensor.Parameter4"
#define MS_IN_SEC 1000 // 1S
MQTT myMqtt("", EIOT_CLOUD_ADDRESS, 1883);
// переменные:
int state;
bool stepOk = false;
int soilHumidityThreshold;
bool autoMode;
String valueStr("");
String topic("");
boolean result;
int lastAnalogReading;
bool autoModeOld;
int soilHumidityThresholdOld;
unsigned long startTime;
int soilHum;
int irrigatorCounter;
void setup() {
state = s_idle;
pinMode(PIN_PUMP, OUTPUT);
pinMode(PIN_BUTTON, INPUT);
autoMode = false;
stepOk = false;
soilHumidityThresholdOld = -1;
startTime = millis();
soilHum = -1;
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(AP_SSID, AP_PASSWORD);
Serial.println();
Serial.println();
Serial.print("Connecting to "); // "Подключение к "
Serial.println(AP_SSID);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
};
Serial.println("WiFi connected"); // "Подключились к WiFi"
Serial.println("Connecting to MQTT server");
// "Подключение к MQTT-серверу"
EEPROM.begin(512);
loadConfig();
// задаем ID клиента;
// генерируем название клиента на основе MAC-адреса
// и последних 8 бит счетчика микросекунд:
String clientName;
//clientName += "esp8266-";
uint8_t mac[6];
WiFi.macAddress(mac);
clientName += macToStr(mac);
clientName += "-";
clientName += String(micros() & 0xff, 16);
myMqtt.setClientId((char*) clientName.c_str());
Serial.print("MQTT client id:"); // "ID MQTT-клиента"
Serial.println(clientName);
// функции обратного вызова:
myMqtt.onConnected(myConnectedCb);
myMqtt.onDisconnected(myDisconnectedCb);
myMqtt.onPublished(myPublishedCb);
myMqtt.onData(myDataCb);
//////Serial.println("connect mqtt...");
// "Подключение к MQTT..."
myMqtt.setUserPwd(EIOTCLOUD_USERNAME, EIOTCLOUD_PASSWORD);
myMqtt.connect();
delay(500);
Serial.print("ModuleId: ");
Serial.println(storage.moduleId);
// создаем модуль, если нужно:
if (storage.moduleId == 0)
{
// создаем модуль:
Serial.println("create module: /NewModule");
// "Создание модуля: /NewModule"
storage.moduleId = myMqtt.NewModule();
if (storage.moduleId == 0)
{
Serial.println("Module NOT created. Check module limit");
// "Модуль не создан. Проверьте лимит модулей"
while(1)
delay(100);
}
// задаем тип модуля:
Serial.println("Set module type"); // "Установка типа модуля"
myMqtt.SetModuleType(storage.moduleId, "ZMT_IRRIGATOR");
// создаем параметр Sensor.Parameter1;
// это пороговое значение для влажности почвы:
Serial.println("new parameter: /"+String(storage.moduleId)+ PARAM_HUMIDITY_TRESHOLD);
myMqtt.NewModuleParameter(storage.moduleId, PARAM_HUMIDITY_TRESHOLD);
// устанавливаем флаг «IsCommand»:
Serial.println("set isCommand: /"+String(storage.moduleId)+ PARAM_HUMIDITY_TRESHOLD);
myMqtt.SetParameterIsCommand(storage.moduleId, PARAM_HUMIDITY_TRESHOLD, true);
// создаем параметр Sensor.Parameter2;
// «0» – ручной режим; «1» – автоматический режим:
Serial.println("new parameter: /"+String(storage.moduleId)+ PARAM_MANUAL_AUTO_MODE);
myMqtt.NewModuleParameter(storage.moduleId, PARAM_MANUAL_AUTO_MODE);
// устанавливаем флаг «IsCommand»:
Serial.println("set isCommand: /"+String(storage.moduleId)+ PARAM_MANUAL_AUTO_MODE);
myMqtt.SetParameterIsCommand(storage.moduleId, PARAM_MANUAL_AUTO_MODE, true);
// создаем параметр Sensor.Parameter3;
// он отвечает за включение/выключение водяной помпы:
Serial.println("new parameter: /"+String(storage.moduleId)+ PARAM_PUMP_ON);
myMqtt.NewModuleParameter(storage.moduleId, PARAM_PUMP_ON);
// устанавливаем флаг «IsCommand»:
Serial.println("set isCommand: /"+String(storage.moduleId)+ PARAM_PUMP_ON);
myMqtt.SetParameterIsCommand(storage.moduleId, PARAM_PUMP_ON, true);
// создаем параметр Sensor.Parameter4;
// он отвечает за текущую влажность почвы:
Serial.println("new parameter: /"+String(storage.moduleId)+ PARAM_HUMIDITY);
myMqtt.NewModuleParameter(storage.moduleId, PARAM_HUMIDITY);
// задаем описание:
Serial.println("set description: /"+String(storage.moduleId)+ PARAM_HUMIDITY);
myMqtt.SetParameterDescription(storage.moduleId, PARAM_HUMIDITY, "Soil hum.");
// задаем единицу измерения:
Serial.println("set Unit: /"+String(storage.moduleId)+ PARAM_HUMIDITY);
myMqtt.SetParameterUnit(storage.moduleId, PARAM_HUMIDITY, "%");
// устанавливаем флаг «dbLogging»;
// он отвечает за запись логов в базу данных:
Serial.println("set Unit: /"+String(storage.moduleId)+ PARAM_HUMIDITY);
myMqtt.SetParameterDBLogging(storage.moduleId, PARAM_HUMIDITY, true);
// сохраняем ID нового модуля:
saveConfig();
}
subscribe();
lastAnalogReading = analogRead(PIN_HUM_ANALOG);
autoModeOld = !autoMode;
}
void loop() {
while (WiFi.status() != WL_CONNECTED) {
delay(500);
#ifdef DEBUG
Serial.print(".");
#endif
}
int in = digitalRead(PIN_BUTTON);
//Serial.println(in);
if (in == 0)
{
while(digitalRead(PIN_BUTTON) == 0)
delay(100);
if (state == s_idle || state == s_irrigation_start)
state = s_irrigation_start;
else
state = s_irrigation_stop;
}
// публикуем изменения автоматического режима:
if (autoModeOld != autoMode)
{
autoModeOld = autoMode;
if (autoMode)
valueStr = String("1");
else
valueStr = String("0");
topic = "/"+String(storage.moduleId)+ PARAM_MANUAL_AUTO_MODE;
result = myMqtt.publish(topic, valueStr, 0, 1);
Serial.print("Publish topic: "); // "Публикация в топик: "
Serial.print(topic);
Serial.print(" value: "); // " значение: "
Serial.println(valueStr);
}
// публикуем изменения порогового значения:
if (soilHumidityThreshold != soilHumidityThresholdOld)
{
soilHumidityThresholdOld = soilHumidityThreshold;
valueStr = String(soilHumidityThreshold);
topic = "/"+String(storage.moduleId)+ PARAM_HUMIDITY_TRESHOLD;
result = myMqtt.publish(topic, valueStr, 0, 1);
Serial.print("Publish topic: "); // "Публикация в топик: "
Serial.print(topic);
Serial.print(" value: "); // " значение: "
Serial.println(valueStr);
}
if (IsTimeout())
{
startTime = millis();
// выполняем обработку данных каждую секунду:
int aireading = analogRead(PIN_HUM_ANALOG);
Serial.print("Analog value: "); // "Аналоговое значение: "
Serial.print(aireading);
Serial.print(" ");
// фильтр:
lastAnalogReading += (aireading - lastAnalogReading) / 10;
Serial.print(lastAnalogReading);
// вычисляем влажность почвы (в процентах):
int newSoilHum = map(lastAnalogReading, MIN_ANALOG_VAL, MAX_ANALOG_VAL, 100, 0);
Serial.print(", Soil hum %:"); // ", Влажность почвы в %:"
Serial.println(newSoilHum);
// ограничиваем до 0-100%:
if (newSoilHum < 0)
newSoilHum = 0;
if (newSoilHum > 100)
newSoilHum = 100;
// если произошли изменения во влажности почвы, сообщаем о них:
if (soilHum != newSoilHum)
{
soilHum = newSoilHum;
//esp.send(msgHum.set(soilHum));
valueStr = String(soilHum);
topic = "/"+String(storage.moduleId)+ PARAM_HUMIDITY;
result = myMqtt.publish(topic, valueStr, 0, 1);
Serial.print("Publish topic: "); // "Публикация в топик: "
Serial.print(topic);
Serial.print(" value: "); // " значение: "
Serial.println(valueStr);
}
// код для работы с состоянием оросителя:
switch(state)
{
case s_idle:
if (irrigatorCounter <= IRRIGATION_PAUSE_TIME)
irrigatorCounter++;
if (irrigatorCounter >= IRRIGATION_PAUSE_TIME && autoMode)
{
if (soilHum <= soilHumidityThreshold)
state = s_irrigation_start;
}
break;
case s_irrigation_start:
irrigatorCounter = 0;
digitalWrite(PIN_PUMP, HIGH);
//esp.send(msgMotorPump.set((uint8_t)1));
valueStr = String(1);
topic = "/"+String(storage.moduleId)+ PARAM_PUMP_ON;
result = myMqtt.publish(topic, valueStr, 0, 1);
Serial.print("Publish topic: "); // "Публикация в топик: "
Serial.print(topic);
Serial.print(" value: "); // " значение: "
Serial.println(valueStr);
state = s_irrigation;
break;
case s_irrigation:
if (irrigatorCounter++ > IRRIGATION_TIME)
state = s_irrigation_stop;
break;
case s_irrigation_stop:
irrigatorCounter = 0;
state = s_idle;
//esp.send(msgMotorPump.set((uint8_t)0));
valueStr = String(0);
topic = "/"+String(storage.moduleId)+ PARAM_PUMP_ON;
result = myMqtt.publish(topic, valueStr, 0, 1);
digitalWrite(PIN_PUMP, LOW);
break;
}
}
}
void loadConfig() {
// чтобы убедиться, что настройки есть, и они ваши;
// если настроек найдено не будет, будут использованы
// настройки по умолчанию:
if (EEPROM.read(CONFIG_START + 0) == CONFIG_VERSION[0] &&
EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1] &&
EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2])
for (unsigned int t=0; t<sizeof(storage); t++)
*((char*)&storage + t) = EEPROM.read(CONFIG_START + t);
}
void saveConfig() {
for (unsigned int t=0; t<sizeof(storage); t++)
EEPROM.write(CONFIG_START + t, *((char*)&storage + t));
EEPROM.commit();
}
String macToStr(const uint8_t* mac)
{
String result;
for (int i = 0; i < 6; ++i) {
result += String(mac[i], 16);
if (i < 5)
result += ':';
}
return result;
}
void waitOk()
{
while(!stepOk)
delay(100);
stepOk = false;
}
boolean IsTimeout()
{
unsigned long now = millis();
if (startTime <= now)
{
if ( (unsigned long)(now - startTime ) < MS_IN_SEC )
return false;
}
else
{
if ( (unsigned long)(startTime - now) < MS_IN_SEC )
return false;
}
return true;
}
void subscribe()
{
if (storage.moduleId != 0)
{
// параметр Sensor.Parameter1 – пороговое значение:
myMqtt.subscribe("/"+String(storage.moduleId)+ PARAM_HUMIDITY_TRESHOLD);
// параметр Sensor.Parameter2 –
// переключение между ручным и автоматическим режимами:
myMqtt.subscribe("/"+String(storage.moduleId)+ PARAM_MANUAL_AUTO_MODE);
// параметр Sensor.Parameter3 – включение/выключение помпы:
myMqtt.subscribe("/"+String(storage.moduleId)+ PARAM_PUMP_ON);
}
}
void myConnectedCb() {
#ifdef DEBUG
Serial.println("connected to MQTT server");
// "Подключение к серверу MQTT"
#endif
subscribe();
}
void myDisconnectedCb() {
#ifdef DEBUG
Serial.println("disconnected. try to reconnect...");
// "Произошло отключение. Пытаемся переподключиться..."
#endif
delay(500);
myMqtt.connect();
}
void myPublishedCb() {
#ifdef DEBUG
Serial.println("published."); // "Опубликовано."
#endif
}
void myDataCb(String& topic, String& data) {
#ifdef DEBUG
Serial.print("Receive topic: "); // "Принимающий топик: "
Serial.print(topic);
Serial.print(": ");
Serial.println(data);
#endif
if (topic == String("/"+String(storage.moduleId)+ PARAM_HUMIDITY_TRESHOLD))
{
soilHumidityThreshold = data.toInt();
Serial.println("soilHumidityThreshold");
Serial.println(data);
}
else if (topic == String("/"+String(storage.moduleId)+ PARAM_MANUAL_AUTO_MODE))
{
autoMode = (data == String("1"));
Serial.println("Auto mode"); // "Автоматический режим"
Serial.println(data);
}
else if (topic == String("/"+String(storage.moduleId)+ PARAM_PUMP_ON))
{
//switchState = (data == String("1"))? true: false;
if (data == String("1"))
state = s_irrigation_start;
else
state = s_irrigation_stop;
Serial.println("Pump"); // "Водяная помпа"
Serial.println(data);
}
}
Подключение компонентов
Чтобы упростить цепь проекта, мы воспользуемся платой 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, а между ними – кнопку.