image




Для понимания, необходимо прочитать предыдущую тему.

Здравствуйте.

В этой части будет немного изменён внешний вид и добавлен датчик температуры (и влажности), чтоб в зависимости от температуры включалась печка или кондиционер, а так же реализована ещё одна очень полезная функция…
Поскольку наш веб-интерфейс непрерывно запрашивает данные у ардуины, то будет не лишним отправлять его в «спящий режим», для того чтоб не мешать другим клиентам.

Онлаин...

Температуру и влажность будет измерять DHT22.

image


Ардуина


Прошиваем код:
#include <EEPROM.h>
#define MAXMILLIS 4294967295
#include "DHT.h"
#define DHTPIN 8 // пин для подключения датчика
#define DHTTYPE DHT22  
DHT dht(DHTPIN, DHTTYPE);

int hum = 110; // влажность
int thum = 110; // температура

byte d2 = EEPROM.read(2);     // флаги (состояние пинов) хранится в EEPROM, считываем их
byte d3 = EEPROM.read(3);
byte d4 = EEPROM.read(4);
int shim1 = EEPROM.read(5); // значение ШИМ хранится в EEPROM, считываем их
int shim2 = EEPROM.read(6);
byte d7 = 0; 
byte d8 = 0;
byte d11 = EEPROM.read(11);
byte d12 = EEPROM.read(12);
byte d13 = EEPROM.read(13);

int oxlagdenie = EEPROM.read(14); // заданая температура для включения кондиционера
int toxl = EEPROM.read(15); // флаг включения

int podogrev = EEPROM.read(16); // заданая температура для включения печки
int tpod = EEPROM.read(17); // флаг включения

byte descript[5]; // массив

unsigned long   time; // время

long timelapsed = 0;

byte count=0; // счетчик минут

void setup() 
{
  Serial.begin(57600);
  pinMode(2, OUTPUT); 
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT); // ШИМ
  pinMode(6, OUTPUT); // ШИМ
  pinMode(7, OUTPUT); // охлаждение
  pinMode(8, OUTPUT); // подогрев 
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);
  
  thum = dht.readTemperature(); // считываем температуру, сначала делаем это в сетапе, так как следующее чтение будет только через минуту
  hum = dht.readHumidity(); // считываем влажность
  
  if(d2) digitalWrite(2, HIGH); else digitalWrite(2, LOW); // если до перезагрузки d2 была включена, то включаем, если нет, то нет 
  delay(500); // чтобы не включалось всё сразу, делаем паузы
  if(d3) digitalWrite(3, HIGH); else digitalWrite(3, LOW);
  delay(500);
  if(d4) digitalWrite(4, HIGH); else digitalWrite(4, LOW);
  delay(500);
  analogWrite(5, shim1 * 2.55); // включаем ШИМ d5
  delay(500);
  analogWrite(6, shim2 * 2.55); // включаем ШИМ d6
  delay(500);
  if(d11) digitalWrite(11, HIGH); else digitalWrite(11, LOW);
  delay(500);
  if(d12) digitalWrite(12, HIGH); else digitalWrite(12, LOW);
  delay(500);
  if(d13) digitalWrite(13, HIGH); else digitalWrite(13, LOW);
}
  
void loop() 
{  
  if (Serial.available()>4) // ждём дескриптор и нужный символ
   {
    if (Serial.read()=='Y') // проверяем первый символ, если это 'Y', то продолжаем принимать, если нет, то выходим из цикла чтения
     {
      for (byte i=0; i < 5; i++)
        {
           descript[i] = Serial.read(); // добавляем символы в массив   
        } 
        
    if((descript[0] =='+') && (descript[1] =='=') && (descript[2] =='Z')) // проверяем дескриптор
     {
      switch (descript[3])
       {
         case 'o': // обновление
         glavnaia(); // отправка ответа
         break;
         
         ////////////// Кнопки ///////////////////
         case 'A': // d2 вкл
         digitalWrite(2, HIGH); // вкл d2
         d2 = 1; // ставим флаг в единицу (вкл)
         EEPROM.write(2, d2); // записываем состояние d2 в ячейку №2 EEPROM 
         glavnaia(); // отправка ответа
         break;
         
         case 'a': // d2 откл
         digitalWrite(2, LOW); // откл d2
         d2 = 0; // ставим флаг в ноль (откл)
         EEPROM.write(2, d2); // записываем состояние d2 в ячейку №2 EEPROM 
         glavnaia(); // отправка ответа
         break; 
 
         case 'B': // d3
         digitalWrite(3, HIGH);
         d3 = 1;
         EEPROM.write(3, d3);
         glavnaia();
         break;
         
         case 'b': // d3
         digitalWrite(3, LOW);
         d3 = 0;
         EEPROM.write(3, d3);
         glavnaia();
         break;          
  
         case 'C': // d4
         digitalWrite(4, HIGH);
         d4 = 1;
         EEPROM.write(4, d4);
         glavnaia();
         break;
         
         case 'c': // d4
         digitalWrite(4, LOW);
         d4 = 0;
         EEPROM.write(4, d4);
         glavnaia();
         break;   
         
         /////////////// ШИМ ///////////////
         case 'D': // d5 прибавляем shim1
         shim1++;
         if(shim1 > 100) shim1 = 100;
         EEPROM.write(5, shim1);
         analogWrite(5, shim1 * 2.55);
         glavnaia();
         break;
         
         case 'd': // d5 убавляем shim1
         shim1--;
         if(shim1 < 1) shim1 = 0;
         EEPROM.write(5, shim1);
         analogWrite(5, shim1 * 2.55);
         glavnaia();
         break; 
  
         case 'E': // d6 прибавляем shim2
         shim2++;
         if(shim2 > 100) shim2 = 100;
         EEPROM.write(6, shim2);
         analogWrite(6, shim2 * 2.55);
         glavnaia();
         break;
         
         case 'e': // d6 убавляем shim2
         shim2--;
         if(shim2 < 1) shim2 = 0;
         EEPROM.write(6, shim2);
         analogWrite(6, shim2 * 2.55);
         glavnaia();
         break;   
  
         case 'F': // мгновенное включение ШИМ на D5
         shim1 = EEPROM.read(5); // считываем значение ШИМ из EEPROM
         analogWrite(5, shim1 * 2.55); // включаем ШИМ D5
         glavnaia();
         break;
         
         case 'f': // мгновенное отключение ШИМ на D5
         shim1 = 0;
         analogWrite(5, shim1); // отключаем ШИМ D5, но НЕ записываем в EEPROM
         glavnaia();
         break;  
 
         case 'G': // мгновенное включение ШИМ на D6
         shim2 = EEPROM.read(6); // считываем значение ШИМ из EEPROM
         analogWrite(6, shim2 * 2.55); // включаем ШИМ D6
         glavnaia();
         break;
         
         case 'g': // мгновенное отключение ШИМ на D6
         shim2 = 0;
         analogWrite(6, shim2); // отключаем ШИМ D6, но НЕ записываем в EEPROM
         glavnaia();
         break;  
         
         //////////////// Кнопки//////////////////
         case 'J': // d11
         digitalWrite(11, HIGH);
         d11 = 1;
         EEPROM.write(11, d11);
         glavnaia();
         break;
         
         case 'j': // d11
         digitalWrite(11, LOW);
         d11 = 0;
         EEPROM.write(11, d11);
         glavnaia();
         break;  
        
         case 'K': // d12
         digitalWrite(12, HIGH);
         d12 = 1;
         EEPROM.write(12, d12);
         glavnaia();
         break;
         
         case 'k': // d12
         digitalWrite(12, LOW);
         d12 = 0;
         EEPROM.write(12, d12);
         glavnaia();
         break;         
      
         case 'M': // d13
         digitalWrite(13, HIGH);
         d13 = 1;
         EEPROM.write(13, d13);
         glavnaia();
         break;
         
         case 'm': // d13
         digitalWrite(13, LOW);
         d13 = 0;
         EEPROM.write(13, d13);
         glavnaia();
         break;
         
         ////////////// ОХЛАЖДЕНИЕ /////////////////
         case 'N': // увеличение "температуры включения охлаждения"
         oxlagdenie++; // если "температура включения охлаждения" больше нуля, то охлаждение будет работать в автоматическом режиме
         EEPROM.write(14, oxlagdenie); // запишем значение в память
         toxl = 1; // включаем флаг
         EEPROM.write(15, toxl); // запишем флаг в память
         glavnaia();
         break;
         
         case 'n': // уменьшение "температуры включения охлаждения"
         oxlagdenie--;
         if(oxlagdenie < 1) // если "температура включения охлаждения" = 0, то охлаждение отключится
            {
               oxlagdenie = 0; // меньше нуля, у нас не будет
               toxl = 0; // отключаем флаг
               EEPROM.write(14, oxlagdenie); // запишем значение в память
               EEPROM.write(15, toxl); // запишем флаг в память
               digitalWrite(7, LOW); // отключаем
               d7 = 0; 
            }
         glavnaia();
         break;  
  
         ////////////// ПОДОГРЕВ /////////////////
         case 'P': // увеличение "температуры включения печки"
         podogrev++; // если "температура включения печки" больше нуля, то печка будет работать в автоматическом режиме
         EEPROM.write(16, podogrev); // запишем значение в память
         tpod = 1; // включаем флаг
         EEPROM.write(17, tpod); // запишем флаг в память
         glavnaia();
         break;
         
         case 'p': // уменьшение "температуры включения печки"
         podogrev--;
         if(podogrev < 1) // если "температура включения печки" = 0, то печка отключится
            {
               podogrev = 0; // меньше нуля, у нас не будет
               tpod = 0; // отключаем флаг
               EEPROM.write(16, podogrev); // запишем значение в память
               EEPROM.write(17, tpod); // запишем флаг в память
               digitalWrite(8, LOW); // отключаем
               d8 = 0; 
            }
         glavnaia();
         break;  
  
         default:
         glavnaia();
       }
     }
   
    else // если дескриптор ложный, то очищаем буфер
      {
        for(byte i=0; i < 255; i++) 
         {
           Serial.read();    
         } 
      } 
     }    // конец if (Serial.read()=='Y')
   }    // конец чтение порта
 
 
 unsigned long currtime = millis(); 
 
 if(currtime > time) timelapsed = (currtime - time); 
 
 else timelapsed = (MAXMILLIS - time + currtime);

 if(timelapsed >= 120000)  // функция будет выполняться раз в 2 минуты
   { 
     time = currtime;
     count++;
     // поскольку время чтения каждого датчика ~ 250 мс (программа вешается на это время), то будем читать их по отдельности, в разное время  
     if(count == 1)  thum = dht.readTemperature(); // считываем температуру. Время чтения = 250 мс.
     if(count == 2)  hum = dht.readHumidity(); // считываем влажность, если не нужна, то лучше отключить. Время чтения = 250 мс
     if(count > 1) count=0;
 
   /////////////////////// Охлаждение ///////////////////////   
       if((thum > oxlagdenie) && (toxl == 1)) // если тепература больше заданого значения и флаг = 1, тогда включаем кондиционер (если флаг = 0, тогда функция не работает)
         {                                  
           digitalWrite(7, HIGH); // включили охлаждение                        
           d7 = 1;                          
         }
   
       else if(toxl == 1) // иначе если меньше и флаг = 1, тогда отключаем охлаждение                                   
         {
           digitalWrite(7, LOW); // отключили охлаждение  
           d7 = 0;
         }  
        
   /////////////////////// Подогрев ////////////////////////
       if((thum < podogrev) && (tpod == 1)) // если тепература меньше заданого значения и флаг = 1, тогда включаем печку (если флаг = 0, тогда функция не работает)  
         {                                
           digitalWrite(8, HIGH); // включили печку                      
           d8 = 1;                       
         }
   
       else if(tpod == 1) // иначе если больше и флаг = 1, тогда отключаем печку
         {
           digitalWrite(8, LOW); // отключили печку 
           d8 = 0;
         }
         
   } // конец включения по температуре
   
 } // конец loop

void glavnaia() // отправка данных
 {
      Serial.print(d2);//0
      Serial.print(",");
      Serial.print(d3);//1
      Serial.print(",");
      Serial.print(d4);//2
      Serial.print(",");
      Serial.print(0);//3  //  пока отключаем, потом пригодится 
      Serial.print(",");
      Serial.print(0);//4  //  пока отключаем, потом пригодится 
      Serial.print(",");
      Serial.print(d7);//5 охлаждение
      Serial.print(",");
      Serial.print(d8);//6 подогрев
      Serial.print(",");
      Serial.print(0);//7  //  пока отключаем, потом пригодится 
      Serial.print(",");
      Serial.print(0);//8 //  пока отключаем, потом пригодится 
      Serial.print(",");
      Serial.print(d11);//9
      Serial.print(",");
      Serial.print(d12);//10
      Serial.print(",");
      Serial.print(d13);//11 
      Serial.print(",");
      Serial.print(shim1); // 12 
      Serial.print(",");
      Serial.print(shim2); // 13 
      Serial.print(",");
      Serial.print(thum); // 14 //температура
      Serial.print(",");
      Serial.print(hum); // 15 // влажность
      Serial.print(",");
      Serial.print(oxlagdenie);//16 
      Serial.print(",");
      Serial.println(podogrev);//17 , отсылается 18 значений разделённых запятой
 }





Библиотека DHT22.

Опрос DHT22

Внутри DHT22 стоят два датчика, температуры и влажности. Время чтения каждого составляет 250 мс, то есть во время опроса (dht.readTemperature) вся программа «вешается» и обмен данными с ардуиной невозможен.
Исходя из этого, мы будем опрашивать датчики поочереди, с интервалом 2 мин.

...
 if(timelapsed >= 120000) // функция будет выполняться раз в 2 минуты
   {  
     time = currtime;
     count++;
     // поскольку время чтения каждого датчика ~ 250 мс (программа вешается на это время), то будем читать их по отдельности, в разное время  
     if(count == 1)  thum = dht.readTemperature(); // считываем температуру. Время чтения = 250 мс.
     if(count == 2)  hum = dht.readHumidity(); // считываем влажность, если не нужна, то лучше отключить. Время чтения = 250 мс
     if(count > 1) count=0;
... 


Опрос датчиков произойдёт только через две минуты после старта программы, поэтому необходимо первый раз получить данные в функции void setup()

...
  thum = dht.readTemperature(); // считываем температуру, сначала делаем это в сетапе, так как следующее чтение будет только через две минуты
  hum = dht.readHumidity(); // считываем влажность
...  


Включение устройств по температуре

Ардуина будет включать кондиционер (или вентелятор) если температура в помещении поднимется выше заданой, а также включать обогреватель, если температура упадёт ниже заданой.

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

...
 if(timelapsed >= 120000) // функция будет выполняться раз в 2 минуты
   {  
     time = currtime;
     count++;
     // поскольку время чтения каждого датчика ~ 250 мс (программа вешается на это время), то будем читать их по отдельности, в разное время  
     if(count == 1)  thum = dht.readTemperature(); // считываем температуру. Время чтения = 250 мс.
     if(count == 2)  hum = dht.readHumidity(); // считываем влажность, если не нужна, то лучше отключить. Время чтения = 250 мс
     if(count > 1) count=0;
 
   /////////////////////// Охлаждение ///////////////////////   
       if((thum > oxlagdenie) && (toxl == 1)) // если тепература больше заданого значения и флаг = 1, тогда включаем кондиционер (если флаг = 0, тогда функция не работает)
         {                                  
           digitalWrite(7, HIGH); // включили охлаждение                        
           d7 = 1;                          
         }
   
       else if(toxl == 1) // иначе если меньше и флаг = 1, тогда отключаем охлаждение                                   
         {
           digitalWrite(7, LOW); // отключили охлаждение  
           d7 = 0;
         }  
        
   /////////////////////// Подогрев /////////////////////////
       if((thum < podogrev) && (tpod == 1)) // если тепература меньше заданого значения и флаг = 1, тогда включаем печку (если флаг = 0, тогда функция не работает)  
         {                                
           digitalWrite(8, HIGH); // включили печку                      
           d8 = 1;                       
         }
   
       else if(tpod == 1) // иначе если больше и флаг = 1, тогда отключаем печку
         {
           digitalWrite(8, LOW); // отключили печку 
           d8 = 0;
         }
         
   } // конец включения по температуре  


Если температура окружающей среды (thum), поднимется выше заданой (oxlagdenie), то кондиционер включится, а когда опустится ниже, то отключится.

Температура включения кондиционера, задаётся в этих блоках:


         ////////////// ОХЛАЖДЕНИЕ /////////////////
         case 'N': // увеличение "температуры включения охлаждения"
         oxlagdenie++; // если "температура включения охлаждения" больше нуля, то охлаждение будет работать в автоматическом режиме
         EEPROM.write(14, oxlagdenie); // запишем значение в память
         toxl = 1; // включаем флаг
         EEPROM.write(15, toxl); // запишем флаг в память
         glavnaia();
         break;
         
         case 'n': // уменьшение "температуры включения охлаждения"
         oxlagdenie--;
         if(oxlagdenie < 1) // если "температура включения охлаждения" = 0, то охлаждение отключится
            {
               oxlagdenie = 0; // меньше нуля, у нас не будет
               toxl = 0; // отключаем флаг
               EEPROM.write(14, oxlagdenie); // запишем значение в память
               EEPROM.write(15, toxl); // запишем флаг в память
               digitalWrite(7, LOW); // отключаем
               d7 = 0; 
            }
         glavnaia();
         break;  


При получении символа N, ардуина увеличивает значение oxlagdenie на единицу, устанавливает флаг работы toxl = 1; и записывает эту инфу в EEPROM.
После обесточивания и последующего включения, система вернётся в рабочее состояние.

При получении символа n, ардуина уменьшает значение oxlagdenie на единицу.
Если oxlagdenie будет равно нулю, то флаг toxl обнулится и автоматическая работа кондиционера будет выключена.

Подогрев устроен так же.

Управление

Скачайте архив (github) и распакуйте его в рабочую папку сервера – /var/www/knoppolztemp/

Для наглядности, откройте файл index.html из архива:

Нажатие на «Кнопки», «Диммер» и «Темп» открывают/закрывают соответствующие панели с элементами управления.

image


Зайдите по адресу ваш_роутер/knoppolztemp/ и нажмите кнопку "Темп".

image


Левые кнопки + и - отвечают за «охлаждение», правые за «подогрев».

Установите температуру «включения охлаждения» (19`C) ниже температуры окружающей среды (20`C) и подождите.
Когда сработает функция:

if(timelapsed >= 120000) // функция будет выполняться раз в 2 минуты  


Охлаждение включится и «плюсик» на кнопке станет красным.

С подогревом всё аналогично, только температуру «включения подогрева» (17`C) надо установить выше.

Спящий режим

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

В файле index.html, в конце функции обновления (function show()) есть строки:

...
slmode++;
if(slmode > 60) { /* спящий режим */
$(".pansl").show();
$("st").hide();
flagobnov = 0;
slmode = 0;
...  


Каждый раз, при срабатывании функции show(), переменная slmode увеличивается на единицу. По достижении указаного значения if(slmode > 60), обновление отключится и экран закроется полупрозрачной панелью с кнопкой «ПУСК».

image


Нажатие на Пуск, включит обновление и опять начнётся отсчёт.


$(".slip").click(function(){ /* кнопка пуск  */

        $(".pansl").hide();
        flagobnov = 1;
        show();
}); 


На этом пока всё, этот шаблон можно использовать для управления дачей или теплицей.

Добавил исходники на github.

Комментарии (12)


  1. sidisko
    24.05.2015 00:20
    +2

    на реальной модели по мере строительства своего умного дома столкнулся с тем, что иногда датчики выдают значения взятые с «потолка».
    Поэтому чтобы исключить ложные срабатывания, советую проверять данные на адекватность, например брать 3 значения и контролировать на разброс значений либо еще как.
    Вот например скриншот для понимания.

    Один из датчиков на регулярной основе «простреливает» какой-то ерундой. Раньше с ним все было ок, но спустя год работы он начал вот так чудить.


  1. dimastd Автор
    24.05.2015 00:50
    +1

    Да, я тоже сталкивался с этой фигнёй, ещё бывает, просто не читаются. Вообще температуру лучше терморезистором мерять, и надёжней, и опрашивается мгновенно, а влажность (если это актуально) делать как Вы советуете.


    1. sidisko
      24.05.2015 01:08

      в моем случае слишком много терморезисторов выйдет)))
      температуры во всех помещениях, температура котла, бойлера, отопления, теплого пола, солнечных коллекторов.
      Я решил что буду брать 3 пары крайних значений и оценивать по ним тенденцию падения или повышения температуры. Значения с большим выбросом от соседей в расчет не берутся.


  1. dimastd Автор
    24.05.2015 01:40

    А какие у Вас датчики везде стоят?


    1. sidisko
      24.05.2015 01:58

      ds18b20 и DHT22


  1. dimastd Автор
    24.05.2015 03:02

    Получается у Вас МК как минимум секунд пять «висит», пока датчики опрашивает.


    1. 4umak
      24.05.2015 12:59
      +1

      А ещё под комментарием есть кнопка «Ответить»;)


  1. ntfs1984
    24.05.2015 13:52

    Время чтения каждого составляет 250 мс, то есть во время опроса (dht.readTemperature) вся программа «вешается» и обмен данными с ардуиной невозможен.

    На самом деле библиотека жутко закостылирована, и по факту задержки там больше, чем на 250 мс:
    delay(250);
    

    delay(20);
    

    delayMicroseconds(40);
    

    Плюс ко всему delay() в цикле и использование тормознутых digitalRead()\DigitalWrite...В общем весело.
    for ( i=0; i< MAXTIMINGS; i++) {
        counter = 0;
        while (digitalRead(_pin) == laststate) {
          counter++;
          delayMicroseconds(1);
          if (counter == 255) {
            break;
          }
        }
    

    255 раз (в плохом случае) чтений digitalRead + delayMicroseconds(1) — это жутко.


    1. dimastd Автор
      24.05.2015 15:04

      Благодарю. Я в «своих поделках» использую термисторы, а вот для влажности, обязательно поковыряю либу.

      А это:

      70 вместо 250-ти

      Вы экспериментальным путём подобрали? Повлияло ли на качество работы?

      В свою очередь хочу порекомендовать Вам ещё одну библиотеку для ускорения ардуины.


      1. dimastd Автор
        24.05.2015 15:10

        И если не затруднит, подскажите, какие значения Вы меняли в библиотеке?


  1. michaelkl
    29.05.2015 10:00

    Внутри DHT22 стоят два датчика, температуры и влажности. Время чтения каждого составляет 250 мс, то есть во время опроса (dht.readTemperature) вся программа «вешается» и обмен данными с ардуиной невозможен.


    На самом деле, это не так. Датчик DHT22 отдаёт оба параметра вместе и нельзя запросить только один из них. Я не видел класс dht, но судя по вашему описанию, он делает запрос к датчику и возвращает только один из полученных параметров, хотя считать он обязан оба.

    У вас в статье же есть ссылка на библиотеку DHT22, а вы используете класс dht. Почему?
    Используйте DHT22. На сколько я могу судить при беглом взгляде на его код, он работает адекватнее: сначала вы делаете запрос DHT22::readData() (это будет занимать какое-то время, пусть даже те же 250 мс), а затем получаете результаты геттерами DHT22::getHumidity и DHT22::getTemperatureC (мгновенно).


  1. dimastd Автор
    29.05.2015 21:30

    Используйте DHT22. На сколько я могу судить при беглом взгляде на его код, он работает адекватнее: сначала вы делаете запрос DHT22::readData() (это будет занимать какое-то время, пусть даже те же 250 мс), а затем получаете результаты геттерами DHT22::getHumidity и DHT22::getTemperatureC (мгновенно).


    Я на самом деле ненавижу этот датчик, то читается, то нет. Вобщем скверно работает, но другого, за те же деньги наверно нет.

    Если Вас не затруднит, покажитете пожалуйста на примере, как правильно делать?