Управление вытяжкой в погребе или подвале

Подробно о проекте рассказано в видео, ниже схемы подключения и исходный код для ардуино.

то как оно работает у меня:

Структурная схема или как все подключено, если собирать на модулях.

Принцип работы прост, измеряем температуру и относительную влажность воздуха в подвале и на улице (в приточной трубе вентиляции), из этих данных вычисляем абсолютную влажность и если в подвале влажность больше чем на улице, включаем вытяжной вентилятор. Для этого установлено два «датчика» с интерфейсом RS485, датчики являются исполнительными устройствами и по запросу передают в сеть данные с сенсора HDC1080 или управляют 9 ногой ардуино к которому подключен мосфет, который в свою очередь управляет вытяжным 12 вольтовым вентилятором. Отправляет команды на датчики ардуино леонардо, он же вычисляет абсолютную влажность из полученных с датчиков данных о температуре и влажности, дает команду на включение или выключение вентилятора, отображает информацию на LCD экране, передает телеметрию на радиомодуль HC12.

Уличный датчик, собран на плате с микроконтроллером, микросхемой MAX485 и стабилизатором питания AMS1117-05. имеет разъем с питанием и I2C шиной для подключения датчика температуры и влажности серии SHT, HDC или подобных, которые могут быть установлены на плате китайского модуля и две колодки, одну для подключения шины RS485 и питания, вторую, для подключения датчиков с однопроводной шиной. Изначально планировалось использовать датчик AM2301 или ему подобный и подключать его к той колодке, в итоге используется HDC1080 в I2C.

Датчик в подвале, имеет аналогичную разводку платы, только за место колодки для датчика AM2301, установлен мосфет и колодка для подключения 12 вольтового вентилятора.

Платы в формате .lay

на ардуино леонардо, установлен протошилд, на котором распаяны колодки для подключения RS485 и питания, китайский модуль на МАХ485, колодка для LCD1602 с I2C и радиомодуль HC12. для сети RS485 используется экранированная витая пара для локальной сети.

Для вычисления абсолютной влажности используется формула, истоки которой тут

Код:

код для леонардо с LCD1602 и HC12, как есть
//// код для леонардо с LCD1602 и HC12
#include <EasyTransfer.h>
#include <LiquidCrystal_I2C.h>
#include <SoftEasyTransfer.h>
#include <SoftwareSerial.h>
#include <Wire.h> 

#define DIR 10
#define interval 1000                     // интервал для таймера
 
SoftwareSerial RS485Serial(8, 9); 
LiquidCrystal_I2C lcd(0x27, 16, 2);
  
EasyTransfer ET; 
SoftEasyTransfer ETtx, ETrx; 

 

float cellarAbsH, outAbsH, inAbsH;
unsigned long previousMillis = 0;
unsigned long previousMillis1 = 0;
int IDsend = 0;
boolean mosStat;
  
struct RECEIVE_DATA_RS485{ 
  int ID;
  float Temp;
  float RH;
  boolean mos;
};

struct SEND_DATA_RS485{
  int ID; 
  int cmd;    
};

struct SEND_DATA_HC12{ 
  float outTemp;                  // улица
  float outRH;
  float inTemp;                   // гараж 
  float inRH;
  float cellarTemp;               // подвал
  float cellarRH; 
    // пока всё
};

RECEIVE_DATA_RS485 rxdata;
SEND_DATA_RS485 txdata;
SEND_DATA_HC12 HC12data;

// вычисление абсалютной влажности из относительной и температуры.
// https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
float CalcAbsH(float t, float h) {  
    double tmp = pow(2.718281828,(17.67*t)/(t+243.5));
    return (6.112*tmp*h*2.1674)/(273.15+t);    // Это граммы воды в дном кубометре воздуха.
}  

void sendCmdRS485(int id, int cmd){         /// отправка команд в RS485
    digitalWrite(DIR, HIGH);
    delay(10);
      txdata.ID = id;
      txdata.cmd = cmd;
      ETtx.sendData();
    delay(10);
    digitalWrite(DIR, LOW);
     
}

void sendLCD(){                             // мониторинг данных на LCD и Serial
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("O:");
  lcd.print(HC12data.outTemp, 0);
  lcd.print("C ");
  lcd.print(HC12data.outRH, 0);
  lcd.print("% ");
  lcd.print(outAbsH, 1);  
  lcd.print("A");
  
  lcd.setCursor(0, 1);
  lcd.print("I:");
  lcd.print(HC12data.cellarTemp, 0);
  lcd.print("C ");
  lcd.print(HC12data.cellarRH, 0);
  lcd.print("% ");
  lcd.print(cellarAbsH, 1);  
  lcd.print("A");

  lcd.setCursor(15, 1); 
  lcd.print(mosStat);

     
  
  Serial.print("O:");
  Serial.print(HC12data.outTemp);
  Serial.print("C ");
  Serial.print(HC12data.outRH);
  Serial.print("% ");
  Serial.print(outAbsH, 1);  
  Serial.println("A");
  
   
  Serial.print("I:");
  Serial.print(HC12data.cellarTemp);
  Serial.print("C ");
  Serial.print(HC12data.cellarRH);
  Serial.print("% ");
  Serial.print(cellarAbsH, 1);  
  Serial.print("A");

   
  Serial.println(mosStat);
   Serial.println( );
}
 

void setup(){
  Serial.begin(9600);
  Serial1.begin(4800);
  RS485Serial.begin(9600);
  
  lcd.begin();
  lcd.backlight();
  lcd.clear();
   
   
  ETrx.begin(details(rxdata), &RS485Serial);
  ETtx.begin(details(txdata), &RS485Serial);
  ET.begin(details(HC12data), &Serial1);
  
  pinMode(DIR, OUTPUT);
  digitalWrite(DIR, LOW);         // включаем прием 

 
}

void loop(){

 
  unsigned long currentMillis = millis();                     // ЗАМЕНИТЬ НА ПРЕРЫВАНИЕ!
  if(currentMillis - previousMillis > interval) { 
    previousMillis = currentMillis;
    if (previousMillis > currentMillis) previousMillis = 0;    // защита от переполнения

    IDsend++;                                                  // переменаня счетчик 
     
    if (IDsend > 2) IDsend = 0;                                // обнуление счетчика, 0 управление вентелем
    sendCmdRS485(IDsend, 1);                                   // спрашиваем с датчиков данные
    sendLCD(); 

   if (IDsend == 0){                                           // если спросили данные со всех датчиков     
        if (outAbsH + 1 < cellarAbsH){                        // если снаружи меньше, +1 для гистерезиса
          sendCmdRS485(1, 2);                   /////         /// включаем вентель
        }
        if (outAbsH > cellarAbsH){                            // если больше, выключаем
          sendCmdRS485(1, 3);                 //   ID 1, 2 вкл, 3 выкл  
        } 
    } 
  }
  
//////////////////////////////
  if(currentMillis - previousMillis1 > 30000) {              // отправляем данные через HC12
    previousMillis1 = currentMillis;
    if (previousMillis1 > currentMillis) previousMillis1 = 0;
  
    ET.sendData(); 
  }
  
 
  
  
   
  if(ETrx.receiveData()){ 
    switch(rxdata.ID){                                      // смотрим откого данные, 1 подвал, 2 улица
      case 1:
        cellarAbsH = CalcAbsH(rxdata.Temp, rxdata.RH);      // вычесляем колво воды в подвале
        HC12data.cellarTemp = rxdata.Temp;                  // для отправки на народмон
        HC12data.cellarRH = rxdata.RH;
        mosStat = rxdata.mos;
      break;
      case 2:
        outAbsH = CalcAbsH(rxdata.Temp, rxdata.RH);      // вычесляем колво воды на улице
        HC12data.outTemp = rxdata.Temp;                  // для отправки на народмон
        HC12data.outRH = rxdata.RH;        
      break;

      default:

      break;      
    } 

  }

 
 
}

платка для HDC1080, уличный датчик, стоит в дырке у входа
/// платка для HDC1080, уличный датчик, стоит в дырке у входа

#include <Wire.h>
#include "ClosedCube_HDC1080.h"
#include <EasyTransfer.h>
 

ClosedCube_HDC1080 hdc1080;
  
#define RS485ID 2

#define DHT21_PIN 5
#define MOS 9                   // тут прозапас, можно для второго ветелятора использовать 
#define DIR 2
 
EasyTransfer ETtx, ETrx; 

struct SEND_DATA_STRUCTURE{       // для отправки данных
  int ID;
  float HDC1080_T;
  float HDC1080_RH;
  boolean mos;
};

struct RECEIVE_DATA_STRUCTURE{  // для приема команд
  int ID; 
  int cmd;    
};

RECEIVE_DATA_STRUCTURE rxdata;
SEND_DATA_STRUCTURE txdata;


void setup() {
//  Serial1.begin(9600);  
  Serial.begin(9600);  
  hdc1080.begin(0x40);
  ETrx.begin(details(rxdata), &Serial);
  ETtx.begin(details(txdata), &Serial);
   
  
  pinMode(MOS, OUTPUT); 
  pinMode(DIR, OUTPUT); 

  digitalWrite(DIR, LOW);         // включаем прием 
  digitalWrite(MOS, LOW);
}

void loop() {  
 
    if (ETrx.receiveData()){                     // если чтото пришло
      if (rxdata.ID == RS485ID){                 // проверяем для кого пришло
        switch (rxdata.cmd) {
          case 1:                                         // вернуть структуру  
            delay(1);
              txdata.ID = RS485ID;                        // откого данные
              txdata.HDC1080_T = hdc1080.readTemperature();          // данные с датчика
              txdata.HDC1080_RH = hdc1080.readHumidity();
              txdata.mos = digitalRead(MOS);              // пусть будет
            digitalWrite(DIR, HIGH);                      // включаем передачу
            delay(10);                                    // небольшая задержка, иначе неуспевает
              ETtx.sendData();                            // отправляем     
              delay(50);                                  // небольшая задержка, иначе неуспевает       
            digitalWrite(DIR, LOW);                       // включаем прием 
            break; 
          case 2:                                         // включить вентель 
            digitalWrite(MOS, HIGH);
            break;
          case 3:                                         // выключить вентель 
            digitalWrite(MOS, LOW);
            break;
          default:
                                                          // если нечего не подошло
          break;
         }
    
    } 
   }
}

платка с HDC1080 и мосфетом для управления вытяжкой в подвале
/// платка с HDC1080 и мосфетом для управления вытяжкой в подвале 

#include <Wire.h>
#include "ClosedCube_HDC1080.h"
#include <EasyTransfer.h>

#define RS485ID 1

#define MOS 9
#define DIR 2
 
EasyTransfer ETtx, ETrx; 
ClosedCube_HDC1080 hdc1080;


struct SEND_DATA_STRUCTURE{ 
  int ID; 
  float SHT20_T;
  float SHT20_RH;
  boolean mos;
};

struct RECEIVE_DATA_STRUCTURE{
  int ID; 
  int cmd;    
};

RECEIVE_DATA_STRUCTURE rxdata;
SEND_DATA_STRUCTURE txdata;



void setup() {
  Serial.begin(9600);  
  hdc1080.begin(0x40);
  ETrx.begin(details(rxdata), &Serial);
  ETtx.begin(details(txdata), &Serial);
  
  
  pinMode(MOS, OUTPUT); 
  pinMode(DIR, OUTPUT); 

  digitalWrite(DIR, LOW);         // включаем прием 
  digitalWrite(MOS, LOW);
}

void loop() {  
 
    if (ETrx.receiveData()){                     // если чтото пришло
      if (rxdata.ID == RS485ID){
        switch (rxdata.cmd) {
          case 1:                                         // вернуть структуру 
            delay(1);
              txdata.ID = RS485ID; 
              txdata.SHT20_T = hdc1080.readTemperature();
              txdata.SHT20_RH = hdc1080.readHumidity();
              txdata.mos = digitalRead(MOS);
            digitalWrite(DIR, HIGH);                      // включаем передачу
            delay(1);                                    // небольшая задержка, иначе неуспевает
              ETtx.sendData();                            // отправляем     
              delay(50);                                  // небольшая задержка, иначе неуспевает       
            digitalWrite(DIR, LOW);                       // включаем прием 
            break; 
          case 2:                                         // включить вентель 
            digitalWrite(MOS, HIGH);
            break;
          case 3:                                         // выключить вентель 
            digitalWrite(MOS, LOW);
            break;
          default:
                                                          // если нечего не подошло
          break;
         }
    
    } 
   }
}

Библиотеки
https://github.com/madsci1016/Arduino-EasyTransfer https://github.com/closedcube/ClosedCube_HDC1080_Arduino https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library

то как повторить и адаптировать под себя:

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

вычисление абсолютной влажности из относительной и температуры.
// https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
float CalcAbsH(float t, float h) {  
    double tmp = pow(2.718281828,(17.67*t)/(t+243.5));
    return (6.112*tmp*h*2.1674)/(273.15+t);    // Это граммы воды в дном кубометре воздуха.
}  


А так выглядит код из которого убрано всё что относится к железу.


код
#define FAN 13      


// https://carnotcycle.wordpress.com/2012/08/04/how-to-convert-relative-humidity-to-absolute-humidity/
float CalcAbsH(float t, float h) {  
    double tmp = pow(2.718281828,(17.67*t)/(t+243.5));
    return (6.112*tmp*h*2.1674)/(273.15+t);    // Это граммы воды в дном кубометре воздуха.
}  


void setup() {
  Serial.begin(9600);

  pinMode(FAN, OUTPUT);

}

void loop() {

  float outTemp = 22.3;                              // предположим что это данные с датчиков
  float outHum = 55;                                 //   температуры и влажности  
  float inTemp = 15.5;                               
  float inHum = 89;

  float outAbsH = CalcAbsH(outTemp, outHum);        // вычисляем абсолютную влажность
  float inAbsH = CalcAbsH(inTemp, inHum);
                                                    // сверяем значение и управляем вентелятором
   if (outAbsH + 1 < inAbsH){                       // если снаружи меньше, +1 для гистерезиса
          digitalWrite(FAN, HIGH);                  /// включаем вентель
        }
   if (outAbsH > inAbsH){                            // если больше, выключаем
          digitalWrite(FAN, LOW);               
        } 


  Serial.print("outAbsH:");                         // смотрим результат
  Serial.println(outAbsH);
  Serial.print("inAbsH:");
  Serial.println(inAbsH);
  Serial.print("FAN:");
  Serial.println(digitalRead(FAN));
  Serial.println();

  delay(5000);
}

Далее к этому коду, за место данных которые указаны руками, можно подсунуть данные с датчиков температуры и влажности полученные любым доступным способом, по проводам, радиоканалу или использовать один микроконтроллер с несколькими подключенными датчиками.