Проект умная теплица на ESP8266 и ATMega328 + MQTT

tesak47
Offline
Зарегистрирован: 22.01.2015

Здравствуйте уважаемые форумчане! У нас очень важный проект на летнюю смену в лагере для IT-шников СИРИУС. Поэтому, с вашего позволения, мы хотим разместить код проекта в этой теме. Огромная просьба модераторов не удалять данную тему, так на нее мы закрепим гипперссылку в описании проекта. Заранее спасибо!

Код для микроконтроллера ATMega328

#include <string.h> //библиотека для работы с классом String
#include <SoftwareSerial.h> //библиотека для работы с программным UART
#include <ESP8266WiFi.h> //библиотека для ESP8266
#include <PubSubClient.h> //библиотека MQTT
SoftwareSerial sSerial(14, 12, false, 256); //создаем экземпляр класса и настраиваем программный UART
WiFiClient wclient; //создаем экземляр класса
const char *ssid = "solnechnie_zaichiki"; // имя вайфай точки доступа
const char *pass = "6578027c"; // пароль от точки доступа

//const char *mqtt_server = "m11.cloudmqtt.com"; // имя сервера MQTT
//const int mqtt_port = 19462; // порт для подключения к серверу MQTT
//const char *mqtt_user = "zanfdfbh"; // логин от сервер
//const char *mqtt_pass = "kwFcQj__tMkJ"; // пароль от сервера

const char *mqtt_server = "m11.cloudmqtt.com"; // имя сервера MQTT
const int mqtt_port = 18575; // порт для подключения к серверу MQTT
const char *mqtt_user = "msabpptd"; // логин от сервер
const char *mqtt_pass = "wUSAFs6Ee0q4"; // пароль от сервера
PubSubClient client(wclient, mqtt_server, mqtt_port);
String str; //строковая перменная для хранения данных с программного UART
unsigned long previousMillis; //переменная для хранения значений таймера
int temp, vlaga, ir1, ir2, pochva1, pochva2, svet;
char buffer[30]; //буфер для анпарсинга строковой переменной
int buf; //буфер для парсинга строковой переменной

void setup() { //функция настройки
  sSerial.begin(9600); //настраиваем скорость программного UART
}

void loop() { //основной цикл
  connect(); //запускаем функцию подключения к серверу
  if (millis() - previousMillis > 2000) { //если прошло 3 секунды
    previousMillis = millis(); //запоминаем когда это произошло
    str = ""; //очищаем строковую переменную
    while (sSerial.available()) { //пока на программном UART есть данные
      sSerial.setTimeout(10); // устанавливаем тайм-аут связи
      str = sSerial.readString(); //считывает строку в переменную
      str.trim(); //обрезаем вначале и в конце строки возможный мусор
      str.toCharArray(buffer, 30); //превращаем строковую переменную в массив
      pochva1 = atoi(strtok(buffer, ";")); //записываем в переменную занчаение до первого знака ;
      pochva2 = atoi(strtok(NULL, ";")); //записываем в переменную занчаение с первого до второго знака ;
      ir1 = atoi(strtok(NULL, ";")); //записываем в переменную занчаение со второго до третьего знака ;
      ir2 = atoi(strtok(NULL, ";")); //записываем в переменную занчаение с третьего до четвертого знака ;
      vlaga = atoi(strtok(NULL, ";")); //записываем в переменную занчаение с четвертого до пятого знака ;
      temp = atoi(strtok(NULL, ";")); //записываем в переменную занчаение с пятого до шестого знака ;
      svet = atoi(strtok(NULL, ";")); //записываем в переменную занчаение после шестого знака ;
    }
    client.loop(); //готовимся к отправке данных на сервер
    client.publish("greenhouse/temp", String(temp)); //отправляем данные с температурой
    client.publish("greenhouse/vlaga", String(vlaga)); //отправляем данные с влажностью
    client.publish("greenhouse/pochva1", String(pochva1)); //отправляем данные с 1 гигрометра
    client.publish("greenhouse/pochva2", String(pochva2)); //отправляем данные со 2 гигрометра
    client.publish("greenhouse/ir1", String(ir1)); //отправляем данные с двери
    client.publish("greenhouse/ir2", String(ir2)); //отправляем данные с форточки
    client.publish("greenhouse/svet", String(svet)); //отправляем данные с датчика уровня света
  }
}

void connect() { //функция подключения к т/д и серверу
  if (WiFi.status() != WL_CONNECTED) { //если подключения нет то...
    WiFi.begin(ssid, pass); //пробуем подключиться к т/д
    if (WiFi.waitForConnectResult() != WL_CONNECTED) {//если подключения нет то...
      return; //выходим из функции
    }
  }
  if (WiFi.status() == WL_CONNECTED) { //если есть подключение то...
    if (!client.connected()) { //если нет подключения к серверу MQTT то...
      if (client.connect(MQTT::Connect("arduinoClient2")
                         .set_auth(mqtt_user, mqtt_pass))) { //пробуем подключиться к серверу и если подключились...
        client.set_callback(callback); // обозначаем функцию для приема данных с сервера
        client.subscribe("greenhouse/ch1"); // подписывааемся по топик с данными для 1 канала реле
        client.subscribe("greenhouse/ch2"); // подписывааемся по топик с данными для 2 канала реле
        client.subscribe("greenhouse/ch3"); // подписывааемся по топик с данными для 3 канала реле
        client.subscribe("greenhouse/ch4"); // подписывааемся по топик с данными для 4 канала реле
        client.subscribe("greenhouse/angel1"); // подписывааемся по топик с данными для 1 сервопривода
        client.subscribe("greenhouse/angel2"); // подписывааемся по топик с данными для 2 сервопривода
      }
    }
  }
}

void callback(const MQTT::Publish& pub) {//функция приема данных с сервера
  String payload = pub.payload_string();
  if (String(pub.topic()) == "greenhouse/ch1") { // проверяем из нужного ли нам топика пришли данные
    buf = payload.toInt(); // преобразуем полученные данные в тип integer
    //собираем строку
    str = "2";
    str += ";";
    str += buf;
    sSerial.println(str);//отправляем данные через программный UART на Arduino
  }
  if (String(pub.topic()) == "greenhouse/ch2") { // проверяем из нужного ли нам топика пришли данные
    buf = payload.toInt(); // преобразуем полученные данные в тип integer
    //собираем строку
    str = "3";
    str += ";";
    str += buf;
    sSerial.println(str);//отправляем данные через программный UART на Arduino
  }
  if (String(pub.topic()) == "greenhouse/ch3") { // проверяем из нужного ли нам топика пришли данные
    buf = payload.toInt(); // преобразуем полученные данные в тип integer
    //собираем строку
    str = "4";
    str += ";";
    str += buf;
    sSerial.println(str);//отправляем данные через программный UART на Arduino
  }
  if (String(pub.topic()) == "greenhouse/ch4") { // проверяем из нужного ли нам топика пришли данные
    buf = payload.toInt(); // преобразуем полученные данные в тип integer
    //собираем строку
    str = "5";
    str += ";";
    str += buf;
    sSerial.println(str);//отправляем данные через программный UART на Arduino
  }
  if (String(pub.topic()) == "greenhouse/angel1") { // проверяем из нужного ли нам топика пришли данные
    buf = payload.toInt(); // преобразуем полученные данные в тип integer
    //собираем строку
    str = "7";
    str += ";";
    str += buf;
    sSerial.println(str); //отправляем данные через программный UART на Arduino
  }
  if (String(pub.topic()) == "greenhouse/angel2") { // проверяем из нужного ли нам топика пришли данные
    buf = payload.toInt(); // преобразуем полученные данные в тип integer
    //собираем строку
    str = "8";
    str += ";";
    str += buf;
    sSerial.println(str); //отправляем данные через программный UART на Arduino
  }
}

Код для микроконтроллера ESP8266:

#include <Bounce2.h>
#include <DHT.h> //библиотека для датчика температуры и влажности
#include <Servo.h> //библиотека для работы с серво
#include <SoftwareSerial.h> ////библиотека для работы с программным UART
#define ch1 2 //дирректива для 1 канала реле (тэны)
#define ch2 3 //дирректива для 2 канала реле (лампы)
#define ch3 4 //дирректива для 3 канала реле (увлажнитель)
#define ch4 5 //дирректива для 4 канала реле (электроклапан)
SoftwareSerial sSerial(10, 11); //создаем экземпляр класса и настраиваем программный UART
Bounce knopka = Bounce(); //создаем экземпляр класса
Servo ser1; //экземпляр класса Servo для 1 сервопривода
Servo ser2; //экземпляр класса Servo для 2 сервопривода
DHT dht(6, DHT11); //создаем экземпляр класса DHT и настраиваем пин, и тип датчика
int pochva1, pochva2, ir1, ir2, vlaga, temp, svet, angel1, angel2, target, state;
String str; //строковая перменная для передачи данных по программному UART
unsigned long currentTime, handControlTimer, timeServo; //переменная для хранения значений таймера
char buffer[30];
bool handControl, dataKnopka, servoState;

void setup() { //функция настройки
  sSerial.begin(9600); //настраиваем скорость программного UART
  Serial.begin(9600);
  pinMode(2, OUTPUT); //настраиваем пин на выход
  pinMode(3, OUTPUT); //настраиваем пин на выход
  pinMode(4, OUTPUT); //настраиваем пин на выход
  pinMode(5, OUTPUT); //настраиваем пин на выход
  pinMode(9, INPUT_PULLUP); //настраиваем пин для кнопки и подтягиваем пин на 5в
  knopka.attach(9); //назначаем экземпляр класса на 9 пин
  knopka.interval(10); //устанавливаем интервал защиты от дребезгов
  ser1.attach(7); //настраиваем пин для 1 сервопривода
  ser2.attach(8); //настраиваем пин для 2 сервопривода
  ser1.write(90); //останавливаем серво
  ser2.write(90); //останавливаем серво
  dht.begin(); //инициализация датчика DHT
}

void servoMove() { //функция управления сервоприводами
  if (ir1 > 1000 || ir2 > 1000) { //если форточка или дверь закрыты то...
    servoState = 1; //установить защиту от повторного закрытия
    ser2.write(90); //остановить вращение сервопривода
    ser1.write(90); //остановить вращение сервопривода
  }
  if (svet < 500 && handControl == 0 && servoState == 0) { //если  темно, включено автоматическое управление и форточка,дверь открыта...
    digitalWrite(ch2, HIGH); //включаем лампы
    ser1.write(180); //закрываем форточку
    ser2.write(180); //закрываем дверь
    servoState = 1; //установить защиту от повторного закрытия
  }
  else if (svet >= 550 && handControl == 0 && servoState == 1) { // если  светло, включено автоматическое управление и форточка,дверь закрыта...
    digitalWrite(ch2, LOW); //выключаем лампы
    ser1.write(0); //открываем форточку
    ser2.write(0); //открываем дверь
    servoState = 0; //установить защиту от повторного открытия
  }
}

void climatcontrol() { //функция климат-контроль
  while (sSerial.available()) { //пока на программном UART доступны данные
    str = ""; //очищаем сткроковую переменную
    sSerial.setTimeout(10); //устанавливаем тайм-аут связи
    str = sSerial.readString(); //считывает строку в переменную
    str.trim(); //обрезаем вначале и в конце строки возможный мусор
    str.toCharArray(buffer, 10); //превращаем строковую переменную в массив
    target = atoi(strtok(buffer, ";")); //записываем в переменную занчаение до первого знака ;
    state = atoi(strtok(NULL, ";")); //записываем в переменную занчаение после первого знака ;
    if (target == 7) { //если переменная приняла значение 7 то...
      ser1.write(state); //открыть дверь
      handControl = 1; //включить режим ручного управления
      handControlTimer = millis(); //запомнить когда это произошло
    }
    if (target == 8) { //если переменная приняла значение 8 то...
      ser1.write(state); //открыть форточку
      handControl = 1; //включить режим ручного управления
      handControlTimer = millis(); //запомнить когда это произошло
    }
    else { //иначе...
      digitalWrite(target, state); //установить цифровой пин target  в состояние state
      handControl = 1; //включить режим ручного управления
      handControlTimer = millis(); //запомнить когда это произошло
    }
  }
  if (knopka.read()) { //если кнопка нажата
    handControl = !handControl; //переключить режим управления
    handControlTimer = millis(); //запомнить когда это произошло
  }
  pochva1 = analogRead(A0); //читаем данные с 1 датчика влажности почвы
  pochva2 = analogRead(A1); //читаем данные со 2 датчика влажности почвы (инверсивный)
  ir1 = analogRead(A2); //читаем данные с 1 датчика отражения
  ir2 = analogRead(A3); //читаем данные со 2 датчика отражения
  vlaga = dht.readHumidity(); //читаем данные о влажности
  temp = dht.readTemperature(); //читаем данные о температуре
  svet = analogRead(A4); //читаем данные с датчика света

  if (millis() - handControlTimer > 1800000) { //если с момента включения ручного управления прошло пол-часа
    handControl = 0; //включить автоматическое управление
  }
  servoMove();
  if (pochva1 < 100 && pochva2 > 200  && handControl == 0) { //если почва сухая то...
    digitalWrite(ch4, HIGH); //включаем электроклапан
  }
  else if (pochva1 > 150 && pochva2 < 100 && handControl == 0) { //если почва влажная то...
    digitalWrite(ch4, LOW); //выключаем электроклапан
  }
  if (vlaga < 50 && handControl == 0) {  //если влажность низкая то...
    digitalWrite(ch3, HIGH); //включаем увлажнитель
  }
  else if (vlaga >= 55 && handControl == 0) { //если влажность высокая то...
    digitalWrite(ch3, LOW); //выключаем увлажнитель
  }
  if (temp < 27 && handControl == 0) { //если температура ниже отметки то...
    digitalWrite(ch1, HIGH); //включаем тэны
  }
  else if (temp >= 27 && handControl == 0) { //если температура выше отметки то...
    digitalWrite(ch1, LOW); //выключаем тэны
  }

}

void sendData() { //функция отправки данных на ESP
  if (millis() - currentTime > 2000) { //если прошло 3 секунды то...
    currentTime = millis(); //запоминаем когда это произошло
    //набираем строку
    str = "";
    str += pochva1;
    str += ";";
    str += pochva2;
    str += ";";
    str += ir1;
    str += ";";
    str += ir2;
    str += ";";
    str += vlaga;
    str += ";";
    str += temp;
    str += ";";
    str += svet;
    sSerial.println(str); //отправляем на ESP через программный UART
  }
}

void loop() { //основной цикл
  knopka.update(); //обновляем экземпляр класса
  climatcontrol(); //вызываем функцию климатконтроль
  sendData(); //вызываем функцию отправки данных на ESP
}
tesak47
Offline
Зарегистрирован: 22.01.2015

И еще пару фотографий:

yucan
Offline
Зарегистрирован: 20.04.2015

Регулирока температуры, влажности, управление поливом. Правильно я понял?

tesak47
Offline
Зарегистрирован: 22.01.2015

Автоматика+отправка данных через MQTT на клиентское приложение например в телефоне+переход на ручное управления как с клиентского приложения так и непосредственно с теплицы

alex286
Offline
Зарегистрирован: 16.07.2012

Судя по фото - это скорее всего действующий "макет"... потому, что чтобы там смонтировать "талант" нужен.. Ладно..

Я о другом.. Где схема, например... и каковы результаты? Есть ли отзывы... 

MaksVV
Offline
Зарегистрирован: 06.08.2015

Зато проводочки, идущие к земле можно еще и как подвязки использовать.

alex286
Offline
Зарегистрирован: 16.07.2012

Точно! Они и так к "земле идут"... Сегодня случайно попал на один  канал и... просто  восхитился... Не знаю как тут с правилами, но это точно по теме... Автор просто умница.. Особено порадовали его разработки в области датчиков влажности почвы.. А это ссылка скажем так "обзорная".  https://www.youtube.com/watch?v=jtVGblaS-50&list=PLDVEo6zzKgcu4BiKLJBmtM7DhfZqe9R0M&index=6

dmitron1036
Offline
Зарегистрирован: 10.01.2016

Прошу пояснить, каким образом у вас открывается форточка?

не понятно по фото.

И видео тоже можно было бы снять. как оно работает.