Суточный термостат для газового котла

R2PM
Offline
Зарегистрирован: 28.12.2015

bagotu пишет:

это к этому:

dwk26111974 пишет:

 

#include <hd44780.h>
0003 #include <hd44780ioClass/hd44780_I2Cexp.h>

Выдает ошибку..а какую библиотеку использовали?

LCD к градуснику не причем.

надо внимательно просмотреть и прозвонить цепи. бывает.

может отвалилось чего...

только что всё проверил - всё так - может среда должна быть другая, например 1.0.1

bagotu
Offline
Зарегистрирован: 15.10.2016

[/quote]

только что всё проверил - всё так - может среда должна быть другая, например 1.0.1

[/quote]

первая же работает.

может его на ногу А2 подвесить и в скетче подправить. (нога пустая)

R2PM
Offline
Зарегистрирован: 28.12.2015

bagotu пишет:

только что всё проверил - всё так - может среда должна быть другая, например 1.0.1

[/quote]

первая же работает.

может его на ногу А2 подвесить и в скетче подправить. (нога пустая)

[/quote]

пробую

R2PM
Offline
Зарегистрирован: 28.12.2015

Всё запустил!!! на А2 не пошёл, вернул на А1 заработал!!!

При каких условиях вкл реле???

bagotu
Offline
Зарегистрирован: 15.10.2016

Мистика )))
Реле управляется низким уровнем. При Т в комнате = заданной

vlad219i
Offline
Зарегистрирован: 05.03.2014

Обратите внимание - в скетче есть задержка включения реле, чтобы оно не слишком часто котёл дёргало. Это я к тому, что стОит немного подождать перед тем, как писать, что реле не включается. :-))

R2PM
Offline
Зарегистрирован: 28.12.2015

vlad219i пишет:

Обратите внимание - в скетче есть задержка включения реле, чтобы оно не слишком часто котёл дёргало. Это я к тому, что стОит немного подождать перед тем, как писать, что реле не включается. :-))

Спасибо за проет!!!!!

Уже давно заметил, сейчас всё в порядке!!! РАБОТАЕТ!!!!

Виктор R2PM

 

bagotu
Offline
Зарегистрирован: 15.10.2016

Больше человеческое спасибо за проэкт!
Есть мысли прикрутить сюда модуль wi-fi для связи с внешним миром?

vlad219i
Offline
Зарегистрирован: 05.03.2014

wi-fi - нет, rf24 - да, мысли есть, а вот времени на это развлечение пока нет. :-)

 

Slaiterx
Offline
Зарегистрирован: 05.05.2016

а как добавить датчиков температуры хочу один на улицу выкинуть а второй на котел поставить чисто для мониторинга у меня дисплей 20х4 места для вывода предостаточно

UNIVERSE
Offline
Зарегистрирован: 26.10.2015
gurol
Offline
Зарегистрирован: 06.12.2016

Третий сезон работает самодельный контроллер отопления, реализованный на меге 2560. К этому контроллеру подключены: - газовый котел АОГВК-35 с клапаном ХОнивелл, который позволяет подключить удаленный термостат; - 4 циркулящионных насоса для гидрострелки, теплотрассы, теплого пола и радиаторов; - 6 датчиков темпратуры (DS18b20); - 5 реле для управления газовым клапаном и насосами. Информация о температуре выводится на LCD 1620, еще имеется 5 кнопок (2 для управления меню дисплея и 3- для выбора режима работы термостата. В контроллере реализована погодозависимая регулировка температуры воды в котле. На контроллере реализован сервер, есть возможность выводить информацию в локальную сеть. Из наблюдений за работой СВОЕЙ системы отопления могу сказать, что: - управление котлом по времени суток наиболее целесообразно в конце весны и в начале осени; - в управлении котлом кроме наружной температуры необходимо учитывать и скорость ветра (особенно актуально в Ростовской области, где зимой дуют сильные и холодные восточные ветры).

demonik-13
Offline
Зарегистрирован: 22.12.2016

День добрый.

Первый вариант кода работает отлично. Со вторым появляется ошибка при компиляции, уже и переустанавливал среду и все библиотеки, ошибка одна и та же: 

Arduino: 1.6.13 (Windows XP), Плата:"Arduino/Genuino Uno"
 
In file included from C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h:9:0,

                 from C:\Documents and Settings\Solar User\My Documents\Arduino\termostat_na_den_2\termostat_na_den_2.ino:3:

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMex.h:159:22: error: conflicting declaration 'EEPROMClassEx EEPROM'

 extern EEPROMClassEx EEPROM;

                      ^

In file included from C:\Documents and Settings\Solar User\My Documents\Arduino\termostat_na_den_2\termostat_na_den_2.ino:1:0:

C:\Program Files\Arduino\hardware\arduino\avr\libraries\EEPROM\src/EEPROM.h:145:20: note: previous declaration as 'EEPROMClass EEPROM'

 static EEPROMClass EEPROM;

                    ^

In file included from C:\Documents and Settings\Solar User\My Documents\Arduino\termostat_na_den_2\termostat_na_den_2.ino:3:0:

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h: In constructor 'EEPROMBackupVar<T>::EEPROMBackupVar(T, int, compareFunction, long unsigned int)':

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h:41:24: error: 'struct EEPROMClass' has no member named 'getAddress'

   baseAddress = EEPROM.getAddress(sizeof(T)*addressRange); 

                        ^

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h: In member function 'void EEPROMBackupVar<T>::initialize(T)':

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h:48:24: error: 'struct EEPROMClass' has no member named 'getAddress'

   baseAddress = EEPROM.getAddress(sizeof(T)*addressRange);

                        ^

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h: In member function 'void EEPROMBackupVar<T>::save()':

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h:108:10: error: 'struct EEPROMClass' has no member named 'updateBlock'

   EEPROM.updateBlock<T>(address(position), var);

          ^

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h:108:23: error: expected primary-expression before '>' token

   EEPROM.updateBlock<T>(address(position), var);

                       ^

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h: In member function 'void EEPROMBackupVar<T>::restore()':

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h:116:11: error: 'struct EEPROMClass' has no member named 'readBlock'

    EEPROM.readBlock<T>(address(position), var);

           ^

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h:116:22: error: expected primary-expression before '>' token

    EEPROM.readBlock<T>(address(position), var);

                      ^

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h: In member function 'void EEPROMBackupVar<T>::retreive(T)':

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h:127:11: error: 'struct EEPROMClass' has no member named 'readBlock'

    EEPROM.readBlock<T>(address(i), candidateValue);

           ^

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h:127:22: error: expected primary-expression before '>' token

    EEPROM.readBlock<T>(address(i), candidateValue);

                      ^

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h: In member function 'void EEPROMBackupVar<T>::clearMemory(T)':

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h:146:11: error: 'struct EEPROMClass' has no member named 'writeBlock'

    EEPROM.writeBlock<T>(address(i), init);

           ^

C:\Documents and Settings\Solar User\My Documents\Arduino\libraries\EEPROMex-master/EEPROMBackupVar.h:146:23: error: expected primary-expression before '>' token

    EEPROM.writeBlock<T>(address(i), init);

                       ^

C:\Documents and Settings\Solar User\My Documents\Arduino\termostat_na_den_2\termostat_na_den_2.ino: In function 'void setup()':

termostat_na_den_2:129: error: 'struct EEPROMClass' has no member named 'setMaxAllowedWrites'

   EEPROM.setMaxAllowedWrites(32767);

          ^

termostat_na_den_2:139: error: 'struct EEPROMClass' has no member named 'updateBlock'

       EEPROM.updateBlock(int(&TimerEE[i]), Timer[i]);

              ^

termostat_na_den_2:141: error: 'struct EEPROMClass' has no member named 'updateFloat'

     EEPROM.updateFloat(int(&TstatTempEE), TstatTemp);

            ^

termostat_na_den_2:142: error: 'struct EEPROMClass' has no member named 'updateByte'

     EEPROM.updateByte(int(&BeepEnabledEE), BeepEnabled);

            ^

termostat_na_den_2:143: error: 'struct EEPROMClass' has no member named 'updateFloat'

     EEPROM.updateFloat(int(&TemperatureCorrEE), TemperatureCorr);

            ^

termostat_na_den_2:144: error: 'struct EEPROMClass' has no member named 'updateFloat'

     EEPROM.updateFloat(int(&HysteresisEE), Hysteresis);

            ^

termostat_na_den_2:145: error: 'struct EEPROMClass' has no member named 'updateFloat'

     EEPROM.updateFloat(int(&AlarmTempEE), AlarmTemp);

            ^

termostat_na_den_2:156: error: 'struct EEPROMClass' has no member named 'readByte'

   BeepEnabled = EEPROM.readByte(int(&BeepEnabledEE));

                        ^

termostat_na_den_2:157: error: 'struct EEPROMClass' has no member named 'readFloat'

   TstatTemp = EEPROM.readFloat(int(&TstatTempEE));

                      ^

termostat_na_den_2:158: error: 'struct EEPROMClass' has no member named 'readFloat'

   TemperatureCorr = EEPROM.readFloat(int(&TemperatureCorrEE));

                            ^

termostat_na_den_2:159: error: 'struct EEPROMClass' has no member named 'readFloat'

   Hysteresis = EEPROM.readFloat(int(&HysteresisEE));

                       ^

termostat_na_den_2:160: error: 'struct EEPROMClass' has no member named 'readFloat'

   AlarmTemp = EEPROM.readFloat(int(&AlarmTempEE));

                      ^

termostat_na_den_2:163: error: 'struct EEPROMClass' has no member named 'readBlock'

     EEPROM.readBlock(int(&TimerEE[i]), Timer[i]);

            ^

C:\Documents and Settings\Solar User\My Documents\Arduino\termostat_na_den_2\termostat_na_den_2.ino: In function 'void loop()':

termostat_na_den_2:270: error: 'struct EEPROMClass' has no member named 'readFloat'

     if ((EEPROM.readFloat(int(&TstatTempEE)) != TstatTemp)&(Seconds == 0)) {

                 ^

termostat_na_den_2:271: error: 'struct EEPROMClass' has no member named 'updateFloat'

       EEPROM.updateFloat(int(&TstatTempEE), TstatTemp);

              ^

termostat_na_den_2:281: error: 'struct EEPROMClass' has no member named 'updateFloat'

         EEPROM.updateFloat(int(&TstatTempEE), TstatTemp);

                ^

termostat_na_den_2:497: error: 'struct EEPROMClass' has no member named 'updateFloat'

         EEPROM.updateFloat(int(&HysteresisEE), Hysteresis); // запись в ЕЕПРОМ

                ^

termostat_na_den_2:544: error: 'struct EEPROMClass' has no member named 'updateFloat'

         EEPROM.updateFloat(int(&TemperatureCorrEE), TemperatureCorr); // запись в ЕЕПРОМ

                ^

termostat_na_den_2:550: error: 'struct EEPROMClass' has no member named 'readFloat'

         TemperatureCorr = EEPROM.readFloat(int(&TemperatureCorrEE));

                                  ^

termostat_na_den_2:586: error: 'struct EEPROMClass' has no member named 'updateByte'

         EEPROM.updateByte(int(&BeepEnabledEE), BeepEnabled);

                ^

termostat_na_den_2:589: error: 'struct EEPROMClass' has no member named 'readByte'

         BeepEnabled = EEPROM.readByte(int(&BeepEnabledEE));

                              ^

termostat_na_den_2:627: error: 'struct EEPROMClass' has no member named 'updateFloat'

         EEPROM.updateFloat(int(&AlarmTempEE), AlarmTemp); // запись в ЕЕПРОМ

                ^

termostat_na_den_2:633: error: 'struct EEPROMClass' has no member named 'readFloat'

         AlarmTemp = EEPROM.readFloat(int(&AlarmTempEE));

                            ^

C:\Documents and Settings\Solar User\My Documents\Arduino\termostat_na_den_2\termostat_na_den_2.ino: In function 'void TimerXSetup(int)':

termostat_na_den_2:853: error: 'struct EEPROMClass' has no member named 'updateBlock'

           EEPROM.updateBlock(int(&TimerEE[X]), Timer[X]);

                  ^

termostat_na_den_2:885: error: 'struct EEPROMClass' has no member named 'updateBlock'

           EEPROM.updateBlock(int(&TimerEE[X]), Timer[X]);

                  ^

termostat_na_den_2:891: error: 'struct EEPROMClass' has no member named 'readBlock'

           EEPROM.readBlock(int(&TimerEE[X]), Timer[X]);

                  ^

termostat_na_den_2:903: error: 'struct EEPROMClass' has no member named 'readBlock'

           EEPROM.readBlock(int(&TimerEE[X]), Timer[X]);

                  ^

termostat_na_den_2:905: error: 'struct EEPROMClass' has no member named 'updateBlock'

           EEPROM.updateBlock(int(&TimerEE[X]), Timer[X]);

                  ^

exit status 1
'struct EEPROMClass' has no member named 'setMaxAllowedWrites'

 

                
 
Andrej_SL
Offline
Зарегистрирован: 25.12.2016

стр. 2 сообщение №85

Или вот здесь почитайте https://forum.pjrc.com/threads/24589-Another-library-fails-to-compile%E2..., нужно установить патч

Tarantull
Offline
Зарегистрирован: 09.01.2017

Добрый день vlad219i, очень хорошая статья. Не смог найти схему подключения в вашем Посте. 

demonik-13
Offline
Зарегистрирован: 22.12.2016

Andrej_SL пишет:

стр. 2 сообщение №85

Или вот здесь почитайте https://forum.pjrc.com/threads/24589-Another-library-fails-to-compile%E2..., нужно установить патч

Долго не мог добраться до термостата, всё праздники да праздники. Этот пост читал уже давно. Вобщем много танцев, много бубнов разных племён и ничего. И вот решил перенести с работы всё домой. И о чудо! На вин7 с первого раза, чистая установка последней среды и библиотек напрямую с поиска, всё компилится и всё работает. А ноут на работе остался загадкой, жаль там винду нельзя перебить, думаю проблема в ней.

vlad219i
Offline
Зарегистрирован: 05.03.2014

Tarantull пишет:

Добрый день vlad219i, очень хорошая статья. Не смог найти схему подключения в вашем Посте. 

Её там и нет. Подключение железа описано в самом начале скетча в дефайнах, там всё прозрачно.

 

Tarantull
Offline
Зарегистрирован: 09.01.2017

Спасибо.

Aloe_Weider
Offline
Зарегистрирован: 09.01.2017

Добрый день vlad219i ваш проэкт очень интерестный но если можно было бы сделать LED 16:2 подключение по I2С То вобще было бы шикарно, я бы сам сделал только я рукажоп начинающий и могу создать код только мигающего светодиода =)

Slaiterx
Offline
Зарегистрирован: 05.05.2016

странициы две назад есть код для I2C

 

Aloe_Weider
Offline
Зарегистрирован: 09.01.2017

СПС

Staryjdub
Offline
Зарегистрирован: 08.04.2016

Доброго времени суток! По поводу библиотек к скетчу на 4ой странице. Не могу найти hd44780.h, RealTimeClockDS1307.h, EEPROM.h, TimerOne.h ... Гопода помогите найти - ткните носом!!!! И какая версия компилятора?

olezhan36
Offline
Зарегистрирован: 29.02.2016

спасибо автору за скетч. Все работает на 5.

Было бы здорово сделать термостат многоканальным. Т.е. отдельно управлять радиаторами, теплым полом и бойлером косвенного нагрева.

У бойлера, например свои температуры, свой гистерезис

// Термостат, до пяти датчиков DS18B20.
//  

//  v 0.2.1 beta

#include <EEPROM.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <LiquidCrystal.h>
 
OneWire  oneWire(15); // A1 вход датчиков 18b20
DallasTemperature ds(&oneWire);
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);


const byte OUT[5] = {13, 12, 11, 3, 2}; // номера выходов 
 
byte pos;
byte tempOUTon[5];  // массив с температурой включения выхода
float tempSensor[5]; // массив куда читается температура
float Hysteresis = 0.5; // гистерезис
byte qty; // количество градусников на шине.

// адреса градусников.
DeviceAddress sensor1 = {0x28, 0xFF, 0x08, 0x26, 0x34, 0x16, 0x04, 0x68};
DeviceAddress sensor2 = {0x28, 0xFF, 0x01, 0x4D, 0x34, 0x16, 0x04, 0x9F};
DeviceAddress sensor3 = {0x28, 0xFF, 0x75, 0x2D, 0x34, 0x16, 0x03, 0xFE};
DeviceAddress sensor4 = {0x28, 0xFF, 0x084, 0x4D, 0x34, 0x16, 0x04, 0x86};
DeviceAddress sensor5 = {0x28, 0xFF, 0x4D, 0x28, 0x34, 0x16, 0x03, 0xC5};

byte gradus[8] = {
  0b00110,
  0b01001,
  0b01001,
  0b00110,
  0b00000,
  0b00000,
  0b00000,
  0b00000
};
byte on[8] = {
  0b00110,
  0b01111,
  0b11111,
  0b11111,
  0b11111,
  0b11111,
  0b11110,
  0b01100
};

void readSet(){
  for (byte i = 0; i < qty; i++) tempOUTon[i] = EEPROM.read(i);
}
 
void outOff(){ // выключает выходы
  for (byte i = 0; i < qty; i++) digitalWrite(OUT[i], LOW);
}

void erorr(){ // останавливает работу программы и сигнализирует ошибку
  outOff(); // выключаем выходы    
  lcd.clear();
  lcd.print("sensor error");
   while(1){ // крутим бесконечный цикл
      analogWrite(10, 100);
      delay(500);
      analogWrite(10, 255);
      delay(500);
  }
}
////

byte key(){ //// для кнопок ЛСДшилда
  int val = analogRead(0);
    if (val < 50) return 5;
    else if (val < 150) return 3;
    else if (val < 350) return 4;
    else if (val < 500) return 2;
    else if (val < 800) return 1;
    else return 0;  
}
////  

void setMenu(byte pos){ // установка температуры   
 outOff();
 lcd.clear();
  
  while(1){ // крутим бесконечный цикл      
    byte KEY = key(); // читаем состояние кнопок
    
    lcd.setCursor(2, 0); // выводим на экран
    lcd.print("Out:");
    lcd.print(pos+1);
    lcd.setCursor(9, 0);
    lcd.print("Pin:");
    lcd.print(OUT[pos]);
    lcd.print("    ");
     
    lcd.setCursor(0, 1);   
    lcd.print(" PinON: ");
    lcd.print(tempOUTon[pos]);
    lcd.write(1);
    lcd.print("C <>");
    
     if (key() == 4
     {TimeOUTon[pos]--;   // изменяем значение
        if (TimeOUTon[pos] > 125) tempOUTon[pos] = 0;
      EEPROM.write(
     }
     if (key() == 3 && pos != 0) pos--;
  
    if (KEY == 2) { // если нажата кнопка
      tempOUTon[pos]--;   // изменяем значение
        if (tempOUTon[pos] > 125) tempOUTon[pos] = 0;
      EEPROM.write(pos, tempOUTon[pos]); // сохраняем в еепром
     }
     else if (KEY == 5){
       tempOUTon[pos]++;
         if (tempOUTon[pos] > 125) tempOUTon[pos] = 0;
       EEPROM.write(pos, tempOUTon[pos]);    
     } 
 
   delay(200);
  }/////   
}//

void getTemp(){ // читаем температуру и заполняем массив
   ds.requestTemperatures();    
   
   tempSensor[0] = ds.getTempC(sensor1); // немного китайского кода
   tempSensor[1] = ds.getTempC(sensor2); 
   tempSensor[2] = ds.getTempC(sensor3);
   tempSensor[3] = ds.getTempC(sensor4);
   tempSensor[4] = ds.getTempC(sensor5);
} 

void sensorTest(){  // ищим датчики на шине, если количество изменилось, останавливаем работу
   ds.begin();
   if(ds.getDeviceCount() != qty) erorr();
}

void setup() {
 //  Serial.begin(9600); 
  ds.begin();   
  qty = ds.getDeviceCount(); // при включении, сохраняем количество градусников, 
                             // можно и лучше количество указать руками. 
  
  for (int i = 0; i < qty; i++) pinMode(OUT[i], OUTPUT);  
  for (int i = 0; i < qty; i++) digitalWrite(OUT[i], LOW);
  
  lcd.createChar(1, gradus);
  lcd.createChar(2, on);
  lcd.begin(16, 2);
  lcd.clear();
  
  readSet();  // читаем настройки из еепром   
}

void loop() {   
  if (key() == 1) setMenu(pos); // если нажата селект, уходим в настройки
  
  else if (key() == 4 && pos < qty-1) pos++; // крутим позицию
  else if (key() == 3 && pos != 0) pos--;
  
  
    sensorTest(); // тест наличия градусников на шине
    getTemp(); // читаем температуру с датчиков  
   
  //// сверяем температуру и управляем выходами
  for (int i = 0; i < qty; i++) 
        {if (tempSensor[i] > (tempOUTon[i]+Hysteresis))
          if ( digitalRead(OUT[i]) == HIGH ) // если горелка включена 
              digitalWrite(OUT[i], LOW);
             
         
    if (tempSensor[i] < tempOUTon[i])
           if ( digitalRead(OUT[i]) == LOW ) // если горелка выключена 
              digitalWrite(OUT[i], HIGH);
  }          

  /////// вывод инфы на экран
  lcd.setCursor(0, 0); 
  lcd.print(pos+1); 
  lcd.print(". ");
  lcd.print(tempSensor[pos], 1);  // показываем температуру с датчика
  lcd.write(1);
  lcd.print("C  ");
  lcd.setCursor(2, 1);
  lcd.print(tempOUTon[pos]); // показываем установленную температуру
  lcd.write(1);
  lcd.print("C  ");    
  
  lcd.setCursor(11, 0);  // рисуем статус выхода  
    for (int i = 0; i < qty; i++){ 
      if (digitalRead(OUT[i])) lcd.write(2);
      else lcd.print(" ");
    }
  lcd.setCursor(11, 1); // рисуем номера выходов   
    for (int i = 0; i < qty; i++) lcd.print(i+1); 

// delay(500); // необязательная задержка
}

и свой график расхода воды.

Ниже код для 5-ти канального термостата (скаченый из http://arrduinolab.blogspot.ru/2014/10/5-ds18b20.html), но функции суточной коррекции температуры там нет.

Задача-слепить из двух один.

С энкодером, наверное не получится , а с LCD Shield должно (кнопок больше и свободных пинов). Там 5 свободных цифровых выходов. 4 канала под реле, а 5-й можно на зуммер оставить.

Сам могу как и многие лишь моргать светодиодами.

Допилить кто может? можно за небольшое вознаграждение.

Tolchoks
Offline
Зарегистрирован: 27.05.2016

trembo пишет:

vlad219i пишет:

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

Вкратце - я ж говорю, меня устраивает. Вариант с уличным датчиком наверняка во сто крат правильнее и экономичнее, но в моём случае room thermostat'а мне хватает за глаза. Елозить установкой температуры теплоносителя больше не приходится, ну и, к примеру, котёл реже включается, если солнце через окна подогревает дом, - дык этого я и добивался. :-)

Или вы меня не понимаете или заблуждаетесь.

1. Уличный датчик- тот же самый DS18B20, он не стОит  несуразных денег.

2. Протокол нас не интересует- лезть в газовый котёл  так глубоко даже опасно. Единственное ЛЕГАЛЬНОЕ место куда можно подключаться как раз и есть те самые контакты комнатного термостата.

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

У меня два года у соседа работала Ардуина с блоком реле и ЖКИ шилдом на котле Junkrs Eurostar который по своей природе на минимуме регулятора не мог выдавать меньше 45-50 градусов, а его тёплый пол при этом становился горячим пока не сработают клапана на рейке, потом они отключались и когда снова включались - пол уже был ледяной. И так всё время колебания: холодный-горячий

Ардуина выключала котёл при 32 и снова включала при 28 (условно) Все клапана пола были НАВСЕГДА открыты! Регулировка вручную  температура-гистерезис, стабильная температура в доме, сосед был доволен.

У меня у самого дома котёл с погодником- Junkers Cerapur, как лет 8 назад отрегулировал наклон графика так и работает. Клапана тёплого пола тоже сняты навсегда за ненадобностью

 

у нас уличный датчик стоит данфосс - как часы все работает!

Slaiterx
Offline
Зарегистрирован: 05.05.2016

а где взять библиотеку вачдог таймер

 

demonik-13
Offline
Зарегистрирован: 22.12.2016

Отличный термостатик получился. Автору премного благодарностей! Если кому интересно есть код на два модуля с радиосвязью по RF24.

bwn
Offline
Зарегистрирован: 25.08.2014

Slaiterx пишет:

а где взять библиотеку вачдог таймер

 

Э, он как бы уже на борту. Надо включить или выключить, ну и сбрасывать своевременно.

Slaiterx
Offline
Зарегистрирован: 05.05.2016

имелось в виду библиотека из этого скетча немогу найтиhttp://arduino.ru/forum/proekty/sutochnyi-termostat-dlya-gazovogo-kotla?page=2#comment-151943

dwk26111974
Offline
Зарегистрирован: 11.07.2016

День добрый. Было бы очень интересно получить и воплотить термостат на 2 модуля. Просьба вышлите на ЭП

dwk26111974@gmail.com Заранее благодарен.

Rukey
Offline
Зарегистрирован: 19.05.2016

Здравствуйте. Реализовал для загородного дома контроллер и добавил к нему модем siemens mc35i, подключаемый через com порт, для управления изменением температуры и получения уведомления о снижении ниже уровня, а также получания статуса. Огромное спасибо автору проекта vlad219i.

vlad219i
Offline
Зарегистрирован: 05.03.2014

Rukey пишет:

Реализовал для загородного дома контроллер и добавил к нему модем siemens mc35i

ЗдОрово, это как раз то, до чего у меня руки не дошли пока. :-)

Поинтересуюсь - для управления и получения статуса используются смс или интернет-подключение? Лет 10 назад я делал нечто похожее (дачная двусторонняя сигналка), там использовал СМС-команды.

Первую фотку удалось открыть только по прямой ссылке, остальные - не видно никак, а жаль.

 
Rukey
Offline
Зарегистрирован: 19.05.2016
 
 
 
 
 
 
 
Прошу прощения, яндекс диск подвел.
 
Управление SMS.
olezhan36
Offline
Зарегистрирован: 29.02.2016

demonik-13 пишет:

Отличный термостатик получился. Автору премного благодарностей! Если кому интересно есть код на два модуля с радиосвязью по RF24.

Два модуля-это два разных канала,  с независимым термостатированием??

Если да, то мне очень нужно.

Rukey скажите где брать такие корпуса??? Они идеальны. Я так понимаю, держатели под каждый модуль вы приклеиваете самостоятельно. И по сути это конструктор. Откройте для меня тайну, плиз!!

demonik-13
Offline
Зарегистрирован: 22.12.2016

olezhan36 пишет:

demonik-13 пишет:

Отличный термостатик получился. Автору премного благодарностей! Если кому интересно есть код на два модуля с радиосвязью по RF24.

Два модуля-это два разных канала,  с независимым термостатированием??

Если да, то мне очень нужно.

Rukey скажите где брать такие корпуса??? Они идеальны. Я так понимаю, держатели под каждый модуль вы приклеиваете самостоятельно. И по сути это конструктор. Откройте для меня тайну, плиз!!

 

Нет, это значит что модуль с экраном и датчиком висит в комнате, а в котёл засунул вторую ардуинку нано с релюшкой и они общаются через РФ-ку. Хотя собрать ещё один модуль для второго помещения и отправлять команду на тот-же адрес РФ-ки в котле и всё. Код одинаков, только адреса радиомодуей выставить.

balbes323
Offline
Зарегистрирован: 06.12.2016

Доброй ночи, товарищи! Вот сваял, поставил, все работает, автору респект, все устраивает. Немного допилил под свои задачи, три месяца, полет нормальный. Ежедневный отчет по GSM приходит исправно. Управляется по GSM тоже ок. Можно даже позвонить туда и оттуда и полноценно поговорить. Но вот сижу и думаю... Хата моя стоит за 300 км от меня, хорошо, что в скетче автора не используется адрес датчика DS, заменить сможет любой электрик. А если адрес датчика прописан и он накрылся? тогда брать ноут и новый датчик и ехать менять, т.к. без скетча никто ничего не сможет сделать. Далее, возникла идея прикрутить еще датчик на улицу. Тут уже адрес нужен и ремонтопригодность сводится к 1 человеку, который это сделал. Вот сижу и думаю, ведь можно и без адресов опросить все датчики на шине, но как контроллер узнает, какой из них какой, если адреса не прописаны? Но если это реализовать, то обслуживание и ремонт наших термостатов упростится в разы. Может, у кого есть идеи по этому поводу?

vlad219i
Offline
Зарегистрирован: 05.03.2014

balbes323 пишет:

Далее, возникла идея прикрутить еще датчик на улицу. Тут уже адрес нужен и ремонтопригодность сводится к 1 человеку, который это сделал. Вот сижу и думаю, ведь можно и без адресов опросить все датчики на шине, но как контроллер узнает, какой из них какой, если адреса не прописаны?

Никак не узнает.

Я в подобном случае каждый датчик на отдельный пин вешал и адрес датчика не использовал.

 

balbes323
Offline
Зарегистрирован: 06.12.2016

Тоже думал над этим вариантом, только у 328 пинов маловато, придется мегу ставить.

vlad219i
Offline
Зарегистрирован: 05.03.2014

Думаю, что мега тут - слишком жирно. LCD можно подключить по i2c, порты расширить 75hc595 (заодно светодиодов для индикации добавить). Ну это так - навскидку...

У меня умный дом немного по-другому организован. База на меге + LCD + LAN-шилд + датчики уличной температуры-давления-влажности + RF24, а уже через RF24 опрашиваются дополнительные модули (контроллер насоса, термостат и тд и тп). Все данные собираются-обрабатываются в меге и отправляются через мобильный инет на сервак, где и ведутся логи и генерятся странички с удобочитаемыми отчётами. Таким образом, из любой точки с интернетом есть доступ к инфе по дому.

balbes323
Offline
Зарегистрирован: 06.12.2016

Вот код и аппарат)))

#include <Wire.h> // i2c (для RTC)
#include <RealTimeClockDS1307.h> // RTC
#include <EEPROM.h> // EE
#include <LiquidCrystal.h> // LCD 16*2
#include <TimerOne.h> // прерывания по таймеру1 
#include <OneWire.h> // 1wire для DS18B20
#include <DallasTemperature.h> // DS18B20
 
#define ONE_WIRE_BUS 11
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature DS18B20(&oneWire);
DeviceAddress DS18B20Address;

#define encoderA    12 // энкодер - поворот вправо (об землю)
#define encoderB    13 // энкодер - поворот влево (об землю)
#define encoderK    A3 // энкодер - кнопка (об землю)
#define BeepPin     9 // пищалка
#define BeepToneNo  2000 // тон звука "No", герц
#define BeepToneYes 4000 // тон звука "Yes", герц
#define BeepToneNoDuration 200 // длительность звука "No", мс
#define BeepToneYesDuration 200 // длительность звука "Yes", мс
#define Relay  10 // нога, к которой подключено реле
#define RelayOn LOW // полярность сигнала включения реде (HIGH/LOW)
 
// LCD connection RS, E, D4, D5, D6, D7
// R/W - to ground
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
 
byte block1[8] = {
  0x06,0x09,0x09,0x06,0x00,0x04,0x0E,0x1F }; // значок градуса с пламенем снизу
byte block2[8] = {
  0x06,0x09,0x09,0x06,0x00,0x00,0x00,0x00 }; // значок градуса
 
 String currStr = "";
boolean isStringMessage = false;  // Переменная принимает значение True, если текущая строка является сообщением
boolean SMSsent = false;
boolean SMSsenti = false;
boolean SMSDelete=false;
boolean TimeSMSzapros=false;

#define TstatTimerMax 120 //минимальная пауза между включениями горелки, сек
unsigned int TstatTimer = 20; //таймер паузы между включениями/выключениями, начальная установка 20 сек для устаканивания системы после сброса
 
float DS18B20Temperature = 0; //сырая температура от датчика
float Temperature = 0; //вычисленная температура с коррекцией
float DS18B20TempTmp; //времянка
byte DS18B20iteration = 0; //счётчик измерений температуры для усреднения
 
float TstatTemp = 23; //температура термостатирования, может изменяться настройками
float TemperatureCorr = 0; //коррекция температуры, может изменяться настройками
float Hysteresis = 0.1; // гистерезис термостата, может изменяться настройками
float HysteresisOld; 

int Hours = 0; // времянка часов RTC для отображения и установки
int Minutes = 0; // времянка минут RTC для отображения и установки
int Seconds;

boolean PrintYesNo = false; // показывать ли после времени Yes/No (косвенно - указание на режим установка/отображение)
boolean SetH = false; // выделение часов при отображении
boolean SetM = false; // выделение минут при отображении
boolean SetYesNo = false; // выделение Yes/No при установке часов
 
boolean blink500ms = false; // мигающий бит, инвертируется каждые 500мс
boolean plus1sec = false; // ежесекундно взводится
boolean Zvonok = true;
boolean BeepEnabled = true; 
byte MenuTimeoutTimer;
 
byte Timer1Hours = 0;
byte Timer1Minutes = 0;
boolean Timer1Enabled = false;
boolean Timer1Activated = false;
float Timer1Temp = 23; //температура термостатирования по таймеру1, может изменяться настройками
byte Timer2Hours = 0;
byte Timer2Minutes = 0;
boolean Timer2Enabled = false;
boolean Timer2Activated = false;
float Timer2Temp = 23; //температура термостатирования по таймеру2, может изменяться настройками
byte Timer3Hours = 0;
byte Timer3Minutes = 0;
boolean Timer3Enabled = false;
boolean Timer3Activated = false;
float Timer3Temp = 23; //температура термостатирования по таймеру3, может изменяться настройками
float AlarmTemp = 20; // температура для замерзательного орала
 
// encoder vars
static boolean rotating=false;      // debounce management
boolean A_set = false;             
boolean B_set = false;
boolean encoderR = false;
boolean encoderL = false;
 
// EEPROM addresses
#define TstatTempEEaddr 0 // EE - адрес для сохранения температуры термостатирования, 4 байта(float)!
#define TemperatureCorrEEaddr 4 // EE - адрес для сохранения коррекции температуры, 4 байта(float)!
#define HysteresisEEaddr 8 // EE - адрес для сохранения гистерезиса, 4 байта(float)!
#define Timer1HoursEEaddr 12 // EE - адрес для сохранения часов таймера 1 (byte)
#define Timer1MinutesEEaddr 13 // EE - адрес для сохранения минут таймера 1 (byte)
#define Timer1EnabledEEaddr 14 // EE - адрес для сохранения статуса таймера 1 (boolean)
#define Timer1TempEEaddr 15 // EE - адрес для сохранения температуры таймера 1, 4 байта(float)!
#define Timer2HoursEEaddr 19 // EE - адрес для сохранения часов таймера 2 (byte)
#define Timer2MinutesEEaddr 20 // EE - адрес для сохранения минут таймера 2 (byte)
#define Timer2EnabledEEaddr 21 // EE - адрес для сохранения статуса таймера 2 (boolean)
#define Timer2TempEEaddr 22 // EE - адрес для сохранения температуры таймера 2, 4 байта(float)!
#define Timer3HoursEEaddr 26 // EE - адрес для сохранения часов таймера 3 (byte)
#define Timer3MinutesEEaddr 27 // EE - адрес для сохранения минут таймера 3 (byte)
#define Timer3EnabledEEaddr 28 // EE - адрес для сохранения статуса таймера 3 (boolean)
#define Timer3TempEEaddr 29 // EE - адрес для сохранения температуры таймера 3, 4 байта(float)!
#define BeepEnabledEEaddr 33 // EE - адрес для сохранения признака разрешения звука (boolean)
#define AlarmTempEEaddr 34 // EE - адрес для сохранения значения недопустимого снижения температуры, 4 байта(float)!
 
// ===== SETUP ========================================================================
void setup() {
  Serial.begin(19200);
  pinMode(Relay, OUTPUT);
  digitalWrite(Relay, HIGH);
  lcd.begin(16, 2);
  lcd.createChar(1, block1);
  lcd.createChar(2, block2);
  pinMode(encoderA, INPUT);
  digitalWrite(encoderA, HIGH);
  pinMode(encoderB, INPUT);
  digitalWrite(encoderB, HIGH);
  pinMode(encoderK, INPUT);
  digitalWrite(encoderK, HIGH);
  attachInterrupt(0, doEncoderA, CHANGE);   // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(1, doEncoderB, CHANGE);  // encoder pin on interrupt 1 (pin 3)
  Timer1.initialize(500000); // Timer0 interrupt - set a timer of length 500000 microseconds
  Timer1.attachInterrupt( timerIsr ); // attach the service routine here
  if (EEPROM.read(Timer1HoursEEaddr) > 23) { // если первая запись однокристалки - записать начальные значения в EE
    EEPROM.write(BeepEnabledEEaddr, BeepEnabled);
    EEPROM_float_write(TstatTempEEaddr, TstatTemp);
    EEPROM_float_write(TemperatureCorrEEaddr, TemperatureCorr);
    EEPROM_float_write(HysteresisEEaddr, Hysteresis);
    EEPROM.write(Timer1HoursEEaddr, Timer1Hours);
    EEPROM.write(Timer1MinutesEEaddr, Timer1Minutes);
    EEPROM.write(Timer1EnabledEEaddr, Timer1Enabled);
    EEPROM_float_write(Timer1TempEEaddr, Timer1Temp);
    EEPROM.write(Timer2HoursEEaddr, Timer2Hours);
    EEPROM.write(Timer2MinutesEEaddr, Timer2Minutes);
    EEPROM.write(Timer2EnabledEEaddr, Timer2Enabled);
    EEPROM_float_write(Timer2TempEEaddr, Timer2Temp);
    EEPROM.write(Timer3HoursEEaddr, Timer3Hours);
    EEPROM.write(Timer3MinutesEEaddr, Timer3Minutes);
    EEPROM.write(Timer3EnabledEEaddr, Timer3Enabled);
    EEPROM_float_write(Timer3TempEEaddr, Timer3Temp);
    EEPROM_float_write(AlarmTempEEaddr, AlarmTemp);
  }
  BeepEnabled = EEPROM.read(BeepEnabledEEaddr);
  TstatTemp = EEPROM_float_read(TstatTempEEaddr);
  TemperatureCorr = EEPROM_float_read(TemperatureCorrEEaddr);
  Hysteresis = EEPROM_float_read(HysteresisEEaddr);
  Timer1Hours = EEPROM.read(Timer1HoursEEaddr);
  Timer1Minutes = EEPROM.read(Timer1MinutesEEaddr);
  Timer1Enabled = EEPROM.read(Timer1EnabledEEaddr);
  Timer1Temp = EEPROM_float_read(Timer1TempEEaddr);
  Timer2Hours = EEPROM.read(Timer2HoursEEaddr);
  Timer2Minutes = EEPROM.read(Timer2MinutesEEaddr);
  Timer2Enabled = EEPROM.read(Timer2EnabledEEaddr);
  Timer2Temp = EEPROM_float_read(Timer2TempEEaddr);
  Timer3Hours = EEPROM.read(Timer3HoursEEaddr);
  Timer3Minutes = EEPROM.read(Timer3MinutesEEaddr);
  Timer3Enabled = EEPROM.read(Timer3EnabledEEaddr);
  Timer3Temp = EEPROM_float_read(Timer3TempEEaddr);
  AlarmTemp = EEPROM_float_read(AlarmTempEEaddr);
 
  DS18B20.begin();
  DS18B20.getAddress(DS18B20Address, 0);
  DS18B20.setResolution(DS18B20Address, 12);
  DS18B20.setWaitForConversion(false);
  DS18B20.requestTemperatures();
  NastroykaGSM();
  delay(5000);
  tone(BeepPin,2000,50);
  delay(50);
  tone(BeepPin,3000,50);
  delay(50);
  tone(BeepPin,4000,50);
  delay(50);
  startOneSMS(); Serial.print("TERMOSTAT-ON"); EndSMS();
}
 
// ===== MAIN CYCLE ===================================================================
void loop()

{ 
  sms_read();
  
  lcd.setCursor(8, 0); //инфо на LCD
  if ((Temperature < AlarmTemp)&(blink500ms)) {
  lcd.print(F("*"));
  } else {
  lcd.print(F(" "));
  }
  lcd.print(F("t="));
  if (Temperature < 10) {
    lcd.print(F(" "));
  }
  lcd.print(Temperature,1);
  lcd.write(0x02); // значок градуса
 
  // если таймер 1 включен - надпись светится, если сработал - мигает
  lcd.setCursor(0, 1); //инфо на LCD
  if ((Timer1Enabled)&!((Timer1Activated)&(blink500ms))) {
    lcd.print(F("T1"));
  }
  else {
    lcd.print(F("  "));
  }
 
  // если таймер 2 включен - надпись светится, если сработал - мигает
  lcd.setCursor(3, 1); //инфо на LCD
  if ((Timer2Enabled)&!((Timer2Activated)&(blink500ms))) {
    lcd.print(F("T2"));
  }
  else {
    lcd.print(F("  "));
  }
 
  // если таймер 3 включен - надпись светится, если сработал - мигает
  lcd.setCursor(6, 1); //инфо на LCD
  if ((Timer3Enabled)&!((Timer3Activated)&(blink500ms))) {
    lcd.print(F("T3"));
  }
  else {
    lcd.print(F("  "));
  }
 
  lcd.setCursor(9, 1); //инфо на LCD
  lcd.print(F("s="));
  lcd.print(TstatTemp,1);
  if ( digitalRead(Relay) == RelayOn ) {
    lcd.write(0x01); // значок градуса с пламенем
  }
  else {
    lcd.write(0x02); // значок градуса
  }
 
  // печатаем текущее время
  PrintYesNo = false;
  PrintRTC(0,0);
 
  // термостатирование
  if ( TstatTimer == 0 )
  {
    if ( Temperature > ( TstatTemp + Hysteresis ) ) // гистерезис
    {
      if ( digitalRead(Relay) == RelayOn ) // если горелка включена -
      {
        digitalWrite(Relay, !RelayOn); // выключить горелку
        TstatTimer = TstatTimerMax; // горелку держать выключённой не менее заданного в TstatTimerMax времени
      }
    }
    if (Temperature < TstatTemp)
    {
      if ( digitalRead(Relay) == !RelayOn ) // если горелка выключена -
      {
        digitalWrite(Relay, RelayOn); // включить горелку
        TstatTimer = TstatTimerMax; // горелку держать включённой не менее заданного в TstatTimerMax времени
      }
    }
  }
   
  // если прошла 1 секунда - делаем ежесекундные дела
  if (plus1sec) {
    plus1sec = false; // сбрасываем до следующей секунды
    // обновляем часы
    RTC.readClock();
    Hours=RTC.getHours();
    Minutes=RTC.getMinutes();
    Seconds=RTC.getSeconds();
 
    // измеряем температуру воздуха
    DS18B20TempTmp = DS18B20.getTempCByIndex(0); // получить температуру от датчика
    DS18B20.requestTemperatures();  // запустить новое измерение
    if (DS18B20TempTmp != -127)
    {
    DS18B20Temperature += DS18B20TempTmp; // суммируем для усреднения
    DS18B20iteration ++;
    if (DS18B20iteration == 10)
      {
      DS18B20iteration = 0;
      Temperature = (DS18B20Temperature / 10) + TemperatureCorr; //усреднённая + коррекция
      DS18B20Temperature = 0;
      }
    }
     
    // если уставку термостата поменяли вручную - запись её в EE, не чаще 1 раза в минуту
    //(экономия ресурса EE)
    if ((EEPROM_float_read(TstatTempEEaddr) != TstatTemp)&(Seconds == 0)) {
      EEPROM_float_write(TstatTempEEaddr, TstatTemp);
    }
 
    // проверка Timer1 и изменение уставки термостата при совпадении   
    if ((Hours == Timer1Hours)&(Minutes == Timer1Minutes)&(Timer1Enabled)&(Seconds == 0)) { // время T1 совпадает с RTC
      Timer1Activated = true;
      Timer2Activated = false;
      Timer3Activated = false;
      TstatTemp = Timer1Temp;
      EEPROM_float_write(TstatTempEEaddr, TstatTemp);
      if (BeepEnabled) {
        tone(BeepPin,4000,5);
      }
    }
 
    // проверка Timer2 и изменение уставки термостата при совпадении   
    if ((Hours == Timer2Hours)&(Minutes == Timer2Minutes)&(Timer2Enabled)&(Seconds == 0)) { // время T2 совпадает с RTC
      Timer1Activated = false;
      Timer2Activated = true;
      Timer3Activated = false;
      TstatTemp = Timer2Temp;
      EEPROM_float_write(TstatTempEEaddr, TstatTemp);
      if (BeepEnabled) {
      tone(BeepPin,4000,5);
      }
    }
 
    // проверка Timer3 и изменение уставки термостата при совпадении   
    if ((Hours == Timer3Hours)&(Minutes == Timer3Minutes)&(Timer3Enabled)&(Seconds == 0)) { // время T3 совпадает с RTC
      Timer1Activated = false;
      Timer2Activated = false;
      Timer3Activated = true;
      TstatTemp = Timer3Temp;
      EEPROM_float_write(TstatTempEEaddr, TstatTemp);
      if (BeepEnabled) {
      tone(BeepPin,4000,5);
      }
    }
 
 
   if ((Temperature < AlarmTemp)&& (!SMSsent)){
      tone(BeepPin,4000,5);startOneSMS();Serial.print("TREVOGA!!! ");Serial.print("TT=");Serial.print(Temperature);EndSMS();
      SMSsent = true;SMSsenti = false;}
    
   if ((Temperature > AlarmTemp)&& (!SMSsenti)){
      tone(BeepPin,4000,5);startOneSMS();Serial.print("TREVOGA-OFF  ");Serial.print("TT=");Serial.print(Temperature);EndSMS();
      SMSsenti = true;SMSsent = false;}
    
   if(Hours == 0 && Minutes == 2 && SMSDelete==false){
      Serial.print("AT+CMGDA=«DEL ALL»\r"); SMSDelete=true;} // Удаляем в 0:02 все СМС.
      if(Minutes != 2){SMSDelete=false;} 
    
   if(Hours == 10 && Minutes == 0 && TimeSMSzapros==false){
      SMSzapros(); TimeSMSzapros=true;} // в 12:00 отправляем СМС о состоянии.
      if(Minutes != 0){TimeSMSzapros=false;} // сброс флага TimeSMSzapros
  }
 
  // обработка поворота энкодера на лету (ручное изменение уставки температуры))
  rotating = true;  // reset the debouncer
  if ((encoderR)^(encoderL)) {
    if (encoderR) {
      TstatTemp += 0.1;
    }
    else
    {
      TstatTemp -= 0.1;
    }
    TstatTemp = constrain(TstatTemp, 10, 35);
    encoderR = false;
    encoderL = false;
    Timer1Activated = false;
    Timer2Activated = false;
    Timer3Activated = false;
    }
 
  // ================ по нажатию кнопки энкодера - меню настроек ====================
  if(digitalRead(encoderK) == 0) {
    MenuTimeoutTimer = 10; //таймер таймаута, секунд
    lcd.clear();
    lcd.setCursor(0, 0); //инфо на LCD
    lcd.print(F("< SETUP >")); 
    if (BeepEnabled) {
      tone(BeepPin,4000,50);
    }
    delay(200);
    int menuitem = 0;
    
 
    do {
      rotating = true;  // reset the debouncer
      if ((encoderR)^(encoderL)) {
        MenuTimeoutTimer = 10; //таймер таймаута, секунд
        if (encoderR) { menuitem += 1; }
        else  { menuitem -= 1; }
        if ( menuitem > 9 ) { menuitem = 0; } // границы пунктов меню
        if ( menuitem < 0 ) { menuitem = 9; }
        encoderR = false;
        encoderL = false;
      }   
    
     // индикация пункта меню (номер пункта - в menuitem)
      lcd.setCursor(0, 1); //инфо на LCD
      switch(menuitem)
      {
      case 0:
        lcd.print(F("0.BACK          ")); 
        break;
      case 1:
        lcd.print(F("1.TIMER1 SET    ")); 
        break;
      case 2:
        lcd.print(F("2.TIMER2 SET    ")); 
        break;
      case 3:
        lcd.print(F("3.TIMER3 SET    ")); 
        break;     
      case 4:
        lcd.print(F("4.CLOCK SET     ")); 
        break;
      case 5:
        lcd.print(F("5.HYSTERESIS SET")); 
        break;
      case 6:
        lcd.print(F("6.T-CORRECT SET ")); 
        break;
      case 7:
        lcd.print(F("7.SOUND SET     ")); 
        break;
      case 8:
        lcd.print(F("8.T-ALARM SET   ")); 
        break; 
      case 9:
        lcd.print(F("ZVONOK   ")); 
        break;
            }
      if (MenuTimeoutTimer == 0) {
        menuitem = 0;
      }
 
    }
    while ((digitalRead(encoderK)==1)^(MenuTimeoutTimer==0));
    // если нажата кнопка энкодера или таймаут - обработка пункта меню (номер пункта - в menuitem)
    if (BeepEnabled) {
      tone(BeepPin,4000,50);
    }
    switch(menuitem)
    {
    // ====== пункт 0 - выход
    case 0:
      if (BeepEnabled) {
        tone(BeepPin,BeepToneNo,BeepToneNoDuration);
      } //звук "NO"
      break; // case 0 out
 
    // ====== пункт 1 - установка Timer1
    case 1:
      MenuTimeoutTimer = 30; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP TIMER1")); 
      delay(200);
      Hours=Timer1Hours;
      Minutes=Timer1Minutes;
      SetYesNo = false;
      PrintYesNo = true;
      SetTime(0,1); // в позиции 0,1 - запрос ввода времени
      if (MenuTimeoutTimer != 0) {
        if (SetYesNo) // если при установке времени выбрано "Yes"
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
          }
          Timer1Hours = Hours;
          Timer1Minutes = Minutes;
          Timer1Enabled = true;
          EEPROM.write(Timer1HoursEEaddr, Timer1Hours);
          EEPROM.write(Timer1MinutesEEaddr, Timer1Minutes);
          EEPROM.write(Timer1EnabledEEaddr, Timer1Enabled);
 
          MenuTimeoutTimer = 10; //таймер таймаута, секунд
          lcd.clear();
          lcd.setCursor(0, 0); //инфо на LCD
          lcd.print(F("Timer1 Temp. Set")); 
          delay(200);
          do {
            lcd.setCursor(0,1);
            if (blink500ms) {
              lcd.print(F("     ")); 
            }
            else {
              lcd.print(Timer1Temp, 1); 
              lcd.write(0x02); // значок градуса
            }
            rotating = true;  // reset the debouncer
            if (encoderR) {
              Timer1Temp += 0.1;
              encoderR = false;
            }
            if (encoderL) {
              Timer1Temp -= 0.1;
              encoderL = false;
            }
            Timer1Temp = constrain(Timer1Temp, 10, 35); // крайние значения
          }
          while ((digitalRead(encoderK)==1)^(MenuTimeoutTimer==0));
 
          if (MenuTimeoutTimer != 0) { // если после выбора температуры нажата кнопка энкодера
            EEPROM_float_write(Timer1TempEEaddr, Timer1Temp);
            if (BeepEnabled) {
              tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
            }
          }
          else { // если не нажата - используем старую температуру
            Timer1Temp = EEPROM_float_read(Timer1TempEEaddr);
            if (BeepEnabled) {
              tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
            }
          }      
 
        }
        else // если при установке времени выбрано "No"
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
          }
          Timer1Enabled = false;
          EEPROM.write(Timer1EnabledEEaddr, Timer1Enabled);
          Timer1Hours = EEPROM.read(Timer1HoursEEaddr);
          Timer1Minutes = EEPROM.read(Timer1MinutesEEaddr);
        }
      }
      else {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 1 out
 
    // ====== пункт 2 - установка Timer2
    case 2:
      MenuTimeoutTimer = 30; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP TIMER2")); 
      delay(200);
      Hours=Timer2Hours;
      Minutes=Timer2Minutes;
      SetYesNo = false;
      PrintYesNo = true;
      SetTime(0,1); // в позиции 0,1 - запрос ввода времени
      if (MenuTimeoutTimer != 0) {
        if (SetYesNo) // если при установке времени выбрано "Yes"
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
          }
          Timer2Hours = Hours;
          Timer2Minutes = Minutes;
          Timer2Enabled = true;
          EEPROM.write(Timer2HoursEEaddr, Timer2Hours);
          EEPROM.write(Timer2MinutesEEaddr, Timer2Minutes);
          EEPROM.write(Timer2EnabledEEaddr, Timer2Enabled);
 
          MenuTimeoutTimer = 10; //таймер таймаута, секунд
          lcd.clear();
          lcd.setCursor(0, 0); //инфо на LCD
          lcd.print(F("Timer2 Temp. Set")); 
          delay(200);
          do {
            lcd.setCursor(0,1);
            if (blink500ms) {
              lcd.print(F("     ")); 
            }
            else {
              lcd.print(Timer2Temp, 1); 
              lcd.write(0x02); // значок градуса
            }
            rotating = true;  // reset the debouncer
            if (encoderR) {
              Timer2Temp += 0.1;
              encoderR = false;
            }
            if (encoderL) {
              Timer2Temp -= 0.1;
              encoderL = false;
            }
            Timer2Temp = constrain(Timer2Temp, 10, 35); // крайние значения
          }
          while ((digitalRead(encoderK)==1)^(MenuTimeoutTimer==0));
 
          if (MenuTimeoutTimer != 0) { // если после выбора температуры нажата кнопка энкодера
            EEPROM_float_write(Timer2TempEEaddr, Timer2Temp);
            if (BeepEnabled) {
              tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
            }
          }
          else { // если не нажата - используем старую температуру
            Timer2Temp = EEPROM_float_read(Timer2TempEEaddr);
            if (BeepEnabled) {
              tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
            }
          }      
 
        }
        else // если при установке времени выбрано "No"
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
          }
          Timer2Enabled = false;
          EEPROM.write(Timer2EnabledEEaddr, Timer2Enabled);
          Timer2Hours = EEPROM.read(Timer2HoursEEaddr);
          Timer2Minutes = EEPROM.read(Timer2MinutesEEaddr);
        }
      }
      else {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 2 out
 
    // ====== пункт 3 - установка Timer3
    case 3:
      MenuTimeoutTimer = 30; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP TIMER3")); 
      delay(200);
      Hours=Timer3Hours;
      Minutes=Timer3Minutes;
      SetYesNo = false;
      PrintYesNo = true;
      SetTime(0,1); // в позиции 0,1 - запрос ввода времени
      if (MenuTimeoutTimer != 0) {
        if (SetYesNo) // если при установке времени выбрано "Yes"
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
          }
          Timer3Hours = Hours;
          Timer3Minutes = Minutes;
          Timer3Enabled = true;
          EEPROM.write(Timer3HoursEEaddr, Timer3Hours);
          EEPROM.write(Timer3MinutesEEaddr, Timer3Minutes);
          EEPROM.write(Timer3EnabledEEaddr, Timer3Enabled);
 
          MenuTimeoutTimer = 10; //таймер таймаута, секунд
          lcd.clear();
          lcd.setCursor(0, 0); //инфо на LCD
          lcd.print(F("Timer3 Temp. Set")); 
          delay(200);
          do {
            lcd.setCursor(0,1);
            if (blink500ms) {
              lcd.print(F("     ")); 
            }
            else {
              lcd.print(Timer3Temp, 1); 
              lcd.write(0x02); // значок градуса
            }
            rotating = true;  // reset the debouncer
            if (encoderR) {
              Timer3Temp += 0.1;
              encoderR = false;
            }
            if (encoderL) {
              Timer3Temp -= 0.1;
              encoderL = false;
            }
            Timer3Temp = constrain(Timer3Temp, 10, 35); // крайние значения
          }
          while ((digitalRead(encoderK)==1)^(MenuTimeoutTimer==0));
 
          if (MenuTimeoutTimer != 0) { // если после выбора температуры нажата кнопка энкодера
            EEPROM_float_write(Timer3TempEEaddr, Timer3Temp);
            if (BeepEnabled) {
              tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
            }
          }
          else { // если не нажата - используем старую температуру
            Timer3Temp = EEPROM_float_read(Timer3TempEEaddr);
            if (BeepEnabled) {
              tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
            }
          }      
 
        }
        else // если при установке времени выбрано "No"
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
          }
          Timer3Enabled = false;
          EEPROM.write(Timer3EnabledEEaddr, Timer3Enabled);
          Timer3Hours = EEPROM.read(Timer3HoursEEaddr);
          Timer3Minutes = EEPROM.read(Timer3MinutesEEaddr);
        }
      }
      else {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 3 out
 
    // ====== пункт 4 - установка RTC
    case 4:
      MenuTimeoutTimer = 30; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP CLOCK")); 
      delay(200);
      RTC.readClock();
      Hours=RTC.getHours();
      Minutes=RTC.getMinutes();
      SetYesNo = false;
      PrintYesNo = true;
      SetTime(0,1); // в позиции 0,1 - запрос ввода времени
      if (MenuTimeoutTimer != 0) {
        if (SetYesNo)
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
          }
          RTC.setHours(Hours);
          RTC.setMinutes(Minutes);
          RTC.setSeconds(0);
          RTC.setClock();
        }
        else
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
          }
        }
      }
      else {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 4 out
 
    // ====== пункт 5 - установка гистерезиса
    case 5:
      MenuTimeoutTimer = 30; //таймер таймаута, секунд
      HysteresisOld = Hysteresis;
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP HYSTERESIS")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print("   "); 
        }
        else {
          lcd.print(Hysteresis, 1); 
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
          Hysteresis += 0.1;
          encoderR = false;
        }
        if (encoderL) {
          Hysteresis -= 0.1;
          encoderL = false;
        }
        Hysteresis = constrain(Hysteresis, 0.1, 1); // крайние значения
      }
      while ((digitalRead(encoderK)==1)^(MenuTimeoutTimer==0));
      if (MenuTimeoutTimer != 0) {
        EEPROM_float_write(HysteresisEEaddr, Hysteresis); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
        Hysteresis = HysteresisOld;
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 5 out
 
    // ====== пункт 6 - установка коррекции температуры
    case 6:
      MenuTimeoutTimer = 30; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP T-CORRECT ")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print(F("    ")); 
        }
        else {
          if (TemperatureCorr >= 0) {
            lcd.print(F("+"));
          }
 
          lcd.print(TemperatureCorr, 1); 
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
          TemperatureCorr += 0.1;
          encoderR = false;
        }
        if (encoderL) {
          TemperatureCorr -= 0.1;
          encoderL = false;
        }
        TemperatureCorr = constrain(TemperatureCorr, -10, 10); // крайние значения
      }
      while ((digitalRead(encoderK)==1)^(MenuTimeoutTimer==0));
 
      if (MenuTimeoutTimer != 0) {
        EEPROM_float_write(TemperatureCorrEEaddr, TemperatureCorr); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
        TemperatureCorr = EEPROM_float_read(TemperatureCorrEEaddr);
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 6 out     
 
    // ====== пункт 7 - вкл/выкл звука
    case 7:
      MenuTimeoutTimer = 30; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SOUND SET       ")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (BeepEnabled) {
          lcd.print(F("BEEP ON         ")); 
        }     
        else {
          lcd.print(F("BEEP OFF        ")); 
        }
 
        rotating = true;  // reset the debouncer
        if ((encoderR)^(encoderL)) {
          BeepEnabled = !BeepEnabled;
          encoderR = false;
          encoderL = false;
        }
      }
      while ((digitalRead(encoderK)==1)^(MenuTimeoutTimer==0));
 
      if (MenuTimeoutTimer != 0) {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
        EEPROM.write(BeepEnabledEEaddr, BeepEnabled);
      }
      if (MenuTimeoutTimer == 0) {
        BeepEnabled = EEPROM.read(BeepEnabledEEaddr);
      }
      break; // case 7 out
 
      // ====== пункт 8 - установка коррекции температуры 
    case 8:
      MenuTimeoutTimer = 30; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("ALARM-TEMP SET  ")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print(F("    ")); 
        }
        else {
          if (AlarmTemp >= 0) {
            lcd.print(F("+"));
          }
 
          lcd.print(AlarmTemp, 0); 
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
          AlarmTemp += 1;
          encoderR = false;
        }
        if (encoderL) {
          AlarmTemp -= 1;
          encoderL = false;
        }
        AlarmTemp = constrain(AlarmTemp, 5, 40); // крайние значения
      }
      while ((digitalRead(encoderK)==1)^(MenuTimeoutTimer==0));
 
      if (MenuTimeoutTimer != 0) {
        EEPROM_float_write(AlarmTempEEaddr, AlarmTemp); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
        AlarmTemp = EEPROM_float_read(AlarmTempEEaddr);
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 8 out
      
      case 9:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("ZVONOK SET       ")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (Zvonok) {
          lcd.print(F("YES         ")); 
        }     
        else {
          lcd.print(F("NO        ")); 
        }
 
        rotating = true;  // reset the debouncer
        if ((encoderR)^(encoderL)) {
          Zvonok = !Zvonok;
          encoderR = false;
          encoderL = false;
        }
      }
      while ((digitalRead(encoderK)==1)^(MenuTimeoutTimer==0));
 
      if (MenuTimeoutTimer != 0) {
        if (Zvonok) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
         Dozvon();
        }
        
      }
      
      break; // case 7 out
    }
 
    delay(200);
    lcd.clear();
  }
}
 
// ===== SUBROUTINES ==================================================================
 
// ========================================
void SetTime(char x, char y)
{
  // ========= set hours
  SetH = true;
  do {
    PrintRTC(x,y);
    rotating = true;  // reset the debouncer
    if (encoderR) {
      Hours += 1;
      if(Hours > 23) {
        Hours = 0;
      };
      encoderR = false;
    }
    if (encoderL) {
      Hours -= 1;
      if(Hours < 0) {
        Hours = 23;
      };
      encoderL = false;
    }
  }
  while ((digitalRead(encoderK)==1)^(MenuTimeoutTimer==0));
  if (BeepEnabled) {
    tone(BeepPin,4000,50); //звук "YES"
  }
  SetH = false;
  delay(200);
  // ========= set minutes
  SetM = true;
  do {
    PrintRTC(0,1);
    rotating = true;  // reset the debouncer
    if (encoderR) {
      Minutes += 1;
      if(Minutes > 59) {
        Minutes = 0;
      };
      encoderR = false;
    }
    if (encoderL) {
      Minutes -= 1;
      if(Minutes < 0) {
        Minutes = 59;
      };
      encoderL = false;
    }
  }
  while ((digitalRead(encoderK)==1)^(MenuTimeoutTimer==0));
  if (BeepEnabled) {
    tone(BeepPin,4000,50); //звук "YES"
  }
  if (PrintYesNo) {
    SetM = false;
    delay(200);
    // ========= set yes-no
    SetYesNo = false;
    do {
      PrintRTC(0,1);
      rotating = true;  // reset the debouncer
      if ((encoderR)||(encoderL)) {
        SetYesNo = !SetYesNo;
        encoderR = false;
        encoderL = false;
      }
    }
    while ((digitalRead(encoderK)==1)^(MenuTimeoutTimer==0));
    delay(200);
  }
 
}
 
// ========================================
void PrintRTC(char x, char y)
{
  lcd.setCursor(x,y);
  if (SetH&&blink500ms) {
    lcd.print(F("  "));
  }
  else {
    if (Hours < 10) {
      lcd.print(F("0"));
    }   
    lcd.print(Hours);
  }
 
  // мигающее двоеточие, если не в режиме установки времени
  if (!(SetH||SetM||PrintYesNo||blink500ms))
  {
    lcd.print(F(" "));
  }
  else {
    lcd.print(F(":"));
  }
 
  if (SetM&&blink500ms) {
    lcd.print(F("  "));
  }
  else {
    if (Minutes < 10) {
      lcd.print(F("0"));
    }   
    lcd.print(Minutes);
  }
  lcd.print(F(" "));
 
  if (PrintYesNo) {
    lcd.print(F("["));
    if (!(SetH||SetM||blink500ms))
    {
      lcd.print(F("   "));
    }
    else {
      if (SetYesNo)
      {
        lcd.print(F("YES"));
      }
      else {
        lcd.print(F("NO "));
      }
    }
    lcd.print(F("]"));
  }
 
}
 
// ========= чтение/запись float в EE =====
void EEPROM_float_write(int addr, float val) // запись в ЕЕПРОМ
{
  byte *x = (byte *)&val;
  for(byte i = 0; i < 4; i++) EEPROM.write(i+addr, x[i]);
}
 
float EEPROM_float_read(int addr) // чтение из ЕЕПРОМ
{  
  byte x[4];
  for(byte i = 0; i < 4; i++) x[i] = EEPROM.read(i+addr);
  float *y = (float *)&x;
  return y[0];
}
// ========================================
 
// ============================ Encoder interrupts =============================
// Interrupt on A changing state
void doEncoderA(){
  if ( rotating ) {
    delay (1) ;  // wait a little until the bouncing is done
  }
  // Test transition, did things really change?
  if( digitalRead(encoderA) != A_set ) {  // debounce once more
    A_set = !A_set;
    // adjust counter + if A leads B
    if ( A_set && !B_set )
    {
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      if (BeepEnabled) {
        tone(BeepPin,4000,5);
      }
      encoderR = true;
      rotating = false;  // no more debouncing until loop() hits again
    }
  }
}
// Interrupt on B changing state, same as A above
void doEncoderB(){
  if ( rotating ) {
    delay (1);
  }
  if( digitalRead(encoderB) != B_set ) {
    B_set = !B_set;
    //  adjust counter - 1 if B leads A
    if( B_set && !A_set ) {
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      if (BeepEnabled) {
        tone(BeepPin,4000,5);
      }
      encoderL = true;
      rotating = false;
    }
  }
}
// ============================ Timer0 interrupt =============================
// run every 500ms
void timerIsr()
{
  blink500ms = !blink500ms; // инверсия мерцающего бита
  if(blink500ms) {
    plus1sec = true; // ежесекундно взводится
    if (TstatTimer != 0) {
      TstatTimer --; // ежесекундный декремент этого таймера
    }
    if (MenuTimeoutTimer != 0) {
      MenuTimeoutTimer --; // ежесекундный декремент этого таймера
    }
  }
}
void sms_read() //_____Цикл чтения входящих СМС-сообщений______________     
{
    if (!Serial.available()) return;
 
    char currSymb = Serial.read();    
    if ('\r' == currSymb)
       {
         if (isStringMessage)
           {
    if (!currStr.compareTo("ZVONOK"))   { Dozvon();} 
    
    if (!currStr.compareTo("ZAPROS"))   { SMSzapros();}   
    if (!currStr.compareTo("+30"))   { TstatTemp = 30;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}
    if (!currStr.compareTo("+29"))   { TstatTemp = 29;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}
    if (!currStr.compareTo("+28"))   { TstatTemp = 28;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}
    if (!currStr.compareTo("+27"))   { TstatTemp = 27;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}
    if (!currStr.compareTo("+26"))   { TstatTemp = 26;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}
    if (!currStr.compareTo("+25"))   { TstatTemp = 25;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}
    if (!currStr.compareTo("+24"))   { TstatTemp = 24;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}       // Передача параметров по СМС
    if (!currStr.compareTo("+23"))   { TstatTemp = 23;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}                                      
    if (!currStr.compareTo("+22"))   { TstatTemp = 22;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}
    if (!currStr.compareTo("+21"))   { TstatTemp = 21;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}
    if (!currStr.compareTo("+20"))   { TstatTemp = 20;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}
    if (!currStr.compareTo("+19"))   { TstatTemp = 19;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}
    if (!currStr.compareTo("+18"))   { TstatTemp = 18;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}
    if (!currStr.compareTo("+17"))   { TstatTemp = 17;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}
    if (!currStr.compareTo("+16"))   { TstatTemp = 16;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}
    if (!currStr.compareTo("+15"))   { TstatTemp = 15;startOneSMS();Serial.print(TstatTemp);Serial.print(" TstatTemp-OK");}
    if (!currStr.compareTo("+0.1"))   { Hysteresis = 0.1;startOneSMS();Serial.print(Hysteresis);Serial.print(" Hysteresis-OK");}
    if (!currStr.compareTo("+0.2"))   { Hysteresis = 0.2;startOneSMS();Serial.print(Hysteresis);Serial.print(" Hysteresis-OK");}
    if (!currStr.compareTo("+0.3"))   { Hysteresis = 0.3;startOneSMS();Serial.print(Hysteresis);Serial.print(" Hysteresis-OK");}
    if (!currStr.compareTo("+0.4"))   { Hysteresis = 0.4;startOneSMS();Serial.print(Hysteresis);Serial.print(" Hysteresis-OK");}
    if (!currStr.compareTo("+0.5"))   { Hysteresis = 0.5;startOneSMS();Serial.print(Hysteresis);Serial.print(" Hysteresis-OK");}
    if (!currStr.compareTo("+1"))   { Hysteresis = 1;startOneSMS();Serial.print(Hysteresis);Serial.print(" Hysteresis-OK");}
    EndSMS();
    
        isStringMessage = false;
        
           }
         else { if (currStr.startsWith("+CMT")) {     // если текущая строка начинается с "+CMT",
                          isStringMessage = true;}}    // то следующая строка является сообщением
        currStr = "";
      } 
    else if ('\n' != currSymb) { currStr += String(currSymb);}
}

void SMSzapros() // СМС отчет для хозяина
{
    startOneSMS();    
    if ((Timer1Enabled)&!(Timer1Activated)) {Serial.print("TIMER 1  ");Serial.print("T1=");Serial.print(Timer1Temp);Serial.print("/TU=");
  Serial.print(Temperature);Serial.print(" HIST=");Serial.print(Hysteresis);}
    if ((Timer2Enabled)&!(Timer1Activated)) {Serial.print("TIMER 2  ");Serial.print("T2=");Serial.print(Timer2Temp);Serial.print("/TU=");
  Serial.print(Temperature);Serial.print(" HIST=");Serial.print(Hysteresis);}
    if ((Timer3Enabled)&!(Timer1Activated)) {Serial.print("TIMER 3  ");Serial.print("T3=");Serial.print(Timer3Temp);Serial.print("/TU=");
  Serial.print(Temperature);Serial.print(" HIST=");Serial.print(Hysteresis);}
   else {Serial.print("TIMER OFF  ");Serial.print("TSET/TT=");Serial.print(TstatTemp);Serial.print("/");Serial.print(Temperature);Serial.print(" HIST=");Serial.print(Hysteresis);}  
    if (digitalRead(12)==HIGH) {Serial.println("  NAGREV-OFF"); } else { Serial.println("  NAGREV-ON");}
    EndSMS();                                 
}

void NastroykaGSM()
{
  Serial.print("AT+CMGF=1\r");         //устанавливает текстовый режим смс-сообщения
    delay(300);
  Serial.print("AT+IFC=1,1\r");       //устанавливает программный контроль потоком передачи данных
    delay(300);
  Serial.print("AT+CPBS=\"SM\"\r");    //открывает доступ к данным телефонной книги SIM-карты
    delay(300);
  Serial.print("AT+GSMBUSY=1,1\r");   //запрет всех входящих звонков
    delay(300);
  Serial.print("AT+CMGD=4,\r");  //Очищаем накопившиеся СМС
    delay(300);
  Serial.print("AT+CNMI=1,2,2,1,0\r"); //включает оповещение о новых сообщениях
    delay(300);
}

void startOneSMS() //__________________Цикл подготовки модуля к отправке СМС-сообщений по первому номеру
{
      Serial.print("AT+CMGF=1\r");
      delay(100);
      Serial.println("AT + CMGS = \"+79206888901\""); 
      delay(100);
}
void EndSMS() //__________________Цикл окончания и отправки СМС-сообщения_______________________
{
   delay(100);
   Serial.println((char)26);    // Команда отправки СМС
   delay(5000);
}
void Dozvon() //__________________Цикл дозвона абоненту (для аудиоконтроля)___________________
{
Serial.println("AT+CMIC=0,12");    // Команда для установки чувствительности микрофона Поэкспериментировать с цифрой.
// 0,- это канал микрофона (1,2,3),15 это уровень см.инструкцию к СИМ900
delay(200);

Serial.println("ATD+79206888901;");  // Набираем номер
}

 

balbes323
Offline
Зарегистрирован: 06.12.2016

только это не умный дом, а термостат.

vlad219i
Offline
Зарегистрирован: 05.03.2014

Красиво. Стимпанк... :-)

 

Rukey
Offline
Зарегистрирован: 19.05.2016

olezhan36 пишет:

demonik-13 пишет:

Отличный термостатик получился. Автору премного благодарностей! Если кому интересно есть код на два модуля с радиосвязью по RF24.

Два модуля-это два разных канала,  с независимым термостатированием??

Если да, то мне очень нужно.

Rukey скажите где брать такие корпуса??? Они идеальны. Я так понимаю, держатели под каждый модуль вы приклеиваете самостоятельно. И по сути это конструктор. Откройте для меня тайну, плиз!!

 

Спасибо. Корпус проектировал в инвенторе, печатал на 3д принтере. Территориально работаю в Москве, могу распечатать. Ну если проблема, можно файлы выложить для самостоятельной печати, если из другого города есть где распечатать.

olezhan36
Offline
Зарегистрирован: 29.02.2016

У меня при вращении энкодера значения на экране только увеличиваются. Независимо от направления вращения.

энкодер с фиксацией положения https://ru.aliexpress.com/item/Rotary-Encoder-Module-Brick-Sensor-Development-for-Arduino/32452759168.html?spm=2114.13010608.0.0.1YTNVw

В чем может быть причина? 

контакты "clk" "DT" вешаю на А4 и А5 соответственно. Пробовал менять местами-результат тот же.

olezhan36
Offline
Зарегистрирован: 29.02.2016

olezhan36 пишет:

 

контакты "clk" "DT" вешаю на А4 и А5 соответственно. Пробовал менять местами-результат тот же.

Ошибся-туда часы. Энкодер на 2 и3 пины.

vlad219i
Offline
Зарегистрирован: 05.03.2014

От 2 и 3 пина на землю параллельно энкодеру по керамическому конденсатору (примерно 0,01мкФ) подключите - должно помочь.

olezhan36
Offline
Зарегистрирован: 29.02.2016

Вот плод моих ночных бдений в свободное от работы и семьи время.

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

За основу были взяты 3 скетча:

1.  скетч автора этой темы

2.https://www.youtube.com/watch?v=mFtgO3kWA4g

3.http://cxem.net/arduino/arduino166.php для работы со сдвиговым регистром.

vlad219i написал отличный скетч, но бывает так (как у меня), что система отопления состоит не только из одного котла. Например, у меня есть котел, к которому через гидрострелку подключены радиаторы, теплый пол, бойлер косвенного нагрева. Есть 3 насоса+насос рециркуляции ГВС. И всем этим хозяйством нужно управлять. Вот я и решил немного допилить скетч vlad219i.

Изменения:

-изменение температуры термостатирования перенесено в меню

-вращение энкодера до входа в меню теперь переключает  каналы термостатирования

-реле основное вкл/выкл котел, замыкает  и размыкает контакты термостата котла на плате.

-3 свободных аналоговых входа использованы для подключения сдвигового регистра 74HC595. Т.о. имеем 8 цифровых выходов, к которым подключены реле для управления  8 каналами. По каждому из каналов скетч работает как у vlad219i-задается температура и поддерживается, сохранено время миниманальной паузы между включениями/выключениями. Таймеры сохранены.

Когда по каждому из 8 каналов нет запроса на тепло, то выключается основное реле и котел гаснет.

Таким образом можно регулировать температуру в 8-ми комнатах отдельно, да ещё и по расписанию. Если конечно есть исполнительные устройсва в этих комнатах.

Я планирую управлять температурой в комнатах (один датчик на все комнаты) с помощью вкл/выкл насоса радиаторов. Аналогично для теплого пола и бойлера косвенного нагрева.

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

Будет ли экономия существенной я узнаю только следующей зимой. А пока это только мечты...

/*
Суточный термостат для котла Baxi Mainfour 240F
 >Encoder control
 >DS18B20 thermal sensor
 >LCD1602 parallel
 >DS1307 RTC
 */
 
//#include <avr\eeprom.h>
#include <Wire.h> // i2c (для RTC)
#include <RealTimeClockDS1307.h> // RTC
#include <EEPROMex.h> // EE
#include <LiquidCrystal.h> // LCD 16*2
#include <TimerOne.h> // прерывания по таймеру1
 
#include <OneWire.h> // 1wire для DS18B20
#include <DallasTemperature.h> // DS18B20

#define ONE_WIRE_BUS A1
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature DS18B20(&oneWire);
DeviceAddress DS18B20Address;
 
#define encoderA    2 // энкодер - поворот вправо (об землю)
#define encoderB    3 // энкодер - поворот влево (об землю)
#define encoderK    13 // энкодер - кнопка (об землю)
#define BeepPin     12 // пищалка
#define BeepToneNo  2000 // тон звука "No", герц
#define BeepToneYes 4000 // тон звука "Yes", герц
#define BeepToneNoDuration 200 // длительность звука "No", мс
#define BeepToneYesDuration 200 // длительность звука "Yes", мс
#define RelayMain  11  // нога, к которой подключено реле, которое вык/вкл котел
#define RelayOn LOW // полярность сигнала включения реле (HIGH/LOW)
#define qtyChannal 8  //количество каналов/датчиков
int channalOn;  //количество включенных реле (каналов, температура которых ниже заданной).
// LCD connection RS, E, D4, D5, D6, D7
// R/W - to ground
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

//для регистра 74HС595
#define clock 16
#define data 17
#define latch 14
boolean Relay[8];


 
byte block1[8] = {
  0x06,0x09,0x09,0x06,0x00,0x04,0x0E,0x1F }; // значок градуса с пламенем снизу
byte block2[8] = {
  0x06,0x09,0x09,0x06,0x00,0x00,0x00,0x00 }; // значок градуса

#define serialenabled // раскомментировать для выдачи в порт отладочной инфы
 
#define TstatTimerMax 180 //минимальная пауза между включениями горелки, сек
unsigned int TstatTimer[qtyChannal]  ; //таймер паузы между включениями/выключениями, начальная установка 20 сек для устаканивания системы после сброса

float DS18B20Temperature[qtyChannal] ; //времянка
float Temperature[qtyChannal] ; //вычисленная температура с коррекцией
float DS18B20TempTmp[qtyChannal]; //сырая температура от датчиков
byte DS18B20iteration = 0; //счётчик измерений температуры для усреднения
 
float TstatTemp[qtyChannal] ; //температура термостатирования, может изменяться настройками
float TstatTempOld[qtyChannal];
float TemperatureCorr[qtyChannal]; //коррекция температуры, может изменяться настройками
float Hysteresis[qtyChannal] ; // гистерезис термостата, может изменяться настройками
float HysteresisOld[qtyChannal];
float AlarmTemp[qtyChannal] ; // температура для замерзательного орала
 

int Hours = 0; // времянка часов RTC для отображения и установки
int Minutes = 0; // времянка минут RTC для отображения и установки
int Seconds;
boolean PrintYesNo = false; // показывать ли после времени Yes/No (косвенно - указание на режим установка/отображение)
boolean SetH = false; // выделение часов при отображении
boolean SetM = false; // выделение минут при отображении
boolean SetYesNo = false; // выделение Yes/No при установке часов
 
boolean blink500ms = false; // мигающий бит, инвертируется каждые 500мс
boolean plus1sec = false; // ежесекундно взводится
 
boolean BeepEnabled = true;
int channel=0;   //номер канала
 
byte MenuTimeoutTimer;
 
 // адреса градусников.
DeviceAddress sensor []{ 
 {0x28, 0xFF, 0x08, 0x26, 0x34, 0x16, 0x04, 0x68},
 {0x28, 0xFF, 0x01, 0x4D, 0x34, 0x16, 0x04, 0x9F},
 {0x28, 0xFF, 0x75, 0x2D, 0x34, 0x16, 0x03, 0xFE},
 {0x28, 0xFF, 0x84, 0x4D, 0x34, 0x16, 0x04, 0x86},
 {0x28, 0xFF, 0x4D, 0x28, 0x34, 0x16, 0x03, 0xC5},
 {0x28, 0xFF, 0xF7, 0x4D, 0x34, 0x16, 0x04, 0xA5},
 {0x28, 0xFF, 0x8C, 0x30, 0x34, 0x16, 0x04, 0x8D},
 {0x28, 0xFF, 0xFC, 0x49, 0x34, 0x16, 0x04, 0xDB},
 {0x28, 0xFF, 0xAB, 0x30, 0x34, 0x16, 0x03, 0xA7},
 {0x28, 0xFF, 0x67, 0x40, 0x34, 0x16, 0x04, 0x8F},
};
// структура для суточных таймеров (8 байт)
struct buffer_template {
  byte Hours;
  byte Minutes;
  float Temperature;
  boolean Enabled;
  boolean Activated;
};
buffer_template Timer[4][qtyChannal] = {0, 0, 23.0, false, false}; //объявление 4-х суточных таймеров для каждого из 9 каналов и их начальные значения
 

// encoder vars
static boolean rotating=false;      // debounce management
boolean A_set = false;             
boolean B_set = false;
boolean encoderR = false;
boolean encoderL = false;
 
// EEPROM
EEMEM float TstatTempEE[qtyChannal]; //EE температура термостатирования
EEMEM float TemperatureCorrEE[qtyChannal]; // EE коррекция температуры
EEMEM float HysteresisEE[qtyChannal]; // EE гистерезис
EEMEM boolean BeepEnabledEE; // EE признак разрешения звука
EEMEM float AlarmTempEE[qtyChannal]; // EE значение недопустимого снижения температуры
EEMEM buffer_template TimerEE[4][qtyChannal]; // EE структуры для 4 суточных таймеров
 
// ===== SETUP ========================================================================
void setup() {
  
 
#ifdef serialenabled
  Serial.begin(9600);
#endif
// задаем начальные значения
for (int j=0; j<qtyChannal; j++){
  TstatTimer[j]=20  ; //таймер паузы между включениями/выключениями, начальная установка 20 сек для устаканивания системы после сброса
  DS18B20Temperature[j]=0 ; //времянка
  Temperature[j]=0 ; //вычисленная температура с коррекцией
}
  pinMode(RelayMain, OUTPUT);
  digitalWrite(RelayMain, HIGH);
  lcd.begin(16, 2);
  lcd.createChar(1, block1);
  lcd.createChar(2, block2);
  pinMode(encoderA, INPUT);
  digitalWrite(encoderA, HIGH);
  pinMode(encoderB, INPUT);
  digitalWrite(encoderB, HIGH);
  pinMode(encoderK, INPUT);
  digitalWrite(encoderK, HIGH);
  attachInterrupt(0, doEncoderA, CHANGE);   // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(1, doEncoderB, CHANGE);  // encoder pin on interrupt 1 (pin 3)
  Timer1.initialize(500000); // Timer0 interrupt - set a timer of length 500000 microseconds
  Timer1.attachInterrupt( timerIsr ); // attach the service routine here
  EEPROM.setMaxAllowedWrites(32767);
  //для сдвигового регистра
  pinMode(clock, OUTPUT);
  pinMode(data, OUTPUT);
  pinMode(latch, OUTPUT);
  digitalWrite(latch, HIGH);
 cleanreg();   
  if ((digitalRead(encoderK)) == 0)
  { // если первая запись однокристалки (зажата кнопка при включении питания)- записать начальные значения в EE
    lcd.setCursor(0, 0); //инфо на LCD
    lcd.print(F("Cold start..."));
    for (int j=0; j<qtyChannal; j++){
    for (int i=0; i<4; i++)
    {
           Timer[i][j].Hours = Timer[i][j].Minutes = 0;
           Timer[i][j].Temperature = 23.0;
           Timer[i][j].Enabled = Timer[i][j].Activated = false;
      EEPROM.updateBlock(int(&TimerEE[i][j]), Timer[i][j]);
     
    }
      //присвоение начальных значений
  
  TstatTemp[j]=25 ; //температура термостатирования, может изменяться настройками
  TemperatureCorr[j]=0; //коррекция температуры, может изменяться настройками
  Hysteresis[j]=0.5 ; // гистерезис термостата, может изменяться настройками
  AlarmTemp[j]=20 ; // температура для замерзательного орала  
    //запись начальных значений в еепром
    EEPROM.updateFloat(int(&TstatTempEE[j]), TstatTemp[j]);
    EEPROM.updateByte(int(&BeepEnabledEE), BeepEnabled);
    EEPROM.updateFloat(int(&TemperatureCorrEE[j]), TemperatureCorr[j]);
    EEPROM.updateFloat(int(&HysteresisEE[j]), Hysteresis[j]);
    EEPROM.updateFloat(int(&AlarmTempEE[j]), AlarmTemp[j]);
    }
    tone(BeepPin,2000,50);
    delay(50);
    tone(BeepPin,3000,50);
    delay(50);
    tone(BeepPin,4000,50);
    delay(1000); 
  }
  lcd.clear();
  lcd.setCursor(0, 0); //инфо на LCD
  lcd.print(F("Read settings..."));
 //чтение еепром
  BeepEnabled = EEPROM.readByte(int(&BeepEnabledEE));
  for (int j=0; j<qtyChannal; j++) {
  TstatTemp[j] = EEPROM.readFloat(int(&TstatTempEE[j]));
  TemperatureCorr[j] = EEPROM.readFloat(int(&TemperatureCorrEE[j]));
  Hysteresis[j] = EEPROM.readFloat(int(&HysteresisEE[j]));
  AlarmTemp[j] = EEPROM.readFloat(int(&AlarmTempEE[j]));
 
 
  for (int i=0; i<4; i++)
  {
    EEPROM.readBlock(int(&TimerEE[i][j]), Timer[i][j]);
    
  }
  }
  DS18B20.begin();
 
  tone(BeepPin,4000,50);
  delay(100); 
  tone(BeepPin,4000,50);
  delay(1000); 
  lcd.clear();
  RTC.start();
 
} 
// ===== MAIN CYCLE ===================================================================
void loop() {
  lcd.setCursor(6, 0);//инфо на LCD
  lcd.print(F("ch"));
  lcd.print(channel);
  lcd.setCursor(9, 0); 
  if ((Temperature[channel] < AlarmTemp[channel])&(blink500ms)) {
    lcd.print(F("*"));
  }
  else {
    lcd.print(F(" "));
  }
  lcd.print(F("t="));
  if (Temperature[channel] < 10) {
    lcd.print(F(" "));
  }
  lcd.print(Temperature[channel],1);
  lcd.write(0x02); // значок градуса
 
  // если таймер включен - надпись светится, если сработал - мигает, обрабатываем все 4 таймера
  lcd.setCursor(0, 1); //инфо на LCD
  for (int i=0;i<4;i++){
    if ((Timer[i][channel].Enabled)&!((Timer[i][channel].Activated)&(blink500ms))) {
      lcd.print(F("T"));
      lcd.print(i+1);
    }
    else {
      lcd.print(F("  "));
    }
  }
 
  lcd.setCursor(9, 1); //инфо на LCD
  lcd.print(F("s="));
  lcd.print(TstatTemp[channel],1);
  if ( Relay[channel] == RelayOn ) {
    lcd.write(0x01); // значок градуса с пламенем
  }
  else {
    lcd.write(0x02); // значок градуса
  }
 
  // печатаем текущее время
  PrintYesNo = false;
  PrintRTC(0,0);
  Termostat();// термостатирование каждого канала в отдельности 
  
  //вкл и выключение котла
  channalOn=qtyChannal;   //количество вкл каналов=количеству каналов
 for (int j=0; j<qtyChannal; j++){
    if (Relay[j]==!RelayOn) //если канал выключен
      channalOn-=1;
    }
  if (channalOn==0) {  //если нет включенных реле (нет запроса на тепло ни по одному из каналов)
  if ( digitalRead(RelayMain) == RelayOn)  //если котел включен
  digitalWrite(RelayMain, !RelayOn);    //выключить котел
  }
 
  if (channalOn>0) {  //если есть включенные реле (есть запроса на тепло хотя бы по одному из каналов)
  if ( digitalRead(RelayMain) == !RelayOn)  //если котел выключен
  digitalWrite(RelayMain, RelayOn);    //включить котел
  }
 
  // если прошла 1 секунда - делаем ежесекундные дела
  if (plus1sec) {
    plus1sec = false; // сбрасываем до следующей секунды
    // обновляем часы
    RTC.readClock();
    Hours=RTC.getHours();
    Minutes=RTC.getMinutes();
    Seconds=RTC.getSeconds();

    getTemp(); // читаем температуру с датчиков  
    
 
    // проверка таймеров и изменение температуры термостатирования при совпадении (активации таймера)
     for (int j=0; j<qtyChannal; j++){
    for (int i=0;i<4;i++)
    {
      if ((Hours == Timer[i][j].Hours)&(Minutes == Timer[i][j].Minutes)&(Timer[i][j].Enabled)&(Seconds == 0)) { // время T совпадает с RTC
      Timer[0][j].Activated = Timer[1][j].Activated = Timer[2][j].Activated = Timer[3][j].Activated = false;
        Timer[i][j].Activated = true;
        TstatTemp[j] = Timer[i][j].Temperature;
        EEPROM.updateFloat(int(&TstatTempEE[j]), TstatTemp[j]);
        if (BeepEnabled) {
          tone(BeepPin,4000,5);
        }
        break; // это чтобы статус Activated остался взведённым
      }
    }
   }
    // дебаг-инфо - в терминал
    
#ifdef serialenabled
Serial.print(F("iteration ="));
Serial.print (DS18B20iteration);
Serial.print(F("  channalOn="));
Serial.print (channalOn); 
if (digitalRead(RelayMain) == RelayOn) Serial.println(F("  MAIN Relay ON    "));
else Serial.println(F("  MAIN Relay Of "));
    for (int j=0; j<qtyChannal; j++){
    Serial.print(F("sensor"));
    Serial.print(j,1);
    Serial.print(F(" Temp="));
    Serial.print(Temperature[j], 1);
    Serial.print(F(" TstatTemp="));
    Serial.print(TstatTemp[j], 1);
    Serial.print(F(" Hyster="));
    Serial.print(Hysteresis[j], 1);
    Serial.print(F("  ("));
    Serial.print(DS18B20Temperature[j], 1);
    Serial.print(F("   "));
    Serial.print(DS18B20TempTmp[j], 1);
    Serial.print(F(",corr "));
    Serial.print(TemperatureCorr[j], 1);
    Serial.print(F("),Timer="));
    Serial.print(TstatTimer[j]);
   
   if (Relay[j]==RelayOn) Serial.println(F(" ON    "));
   else Serial.println(F(" OF   "));
    }
    Serial.println(F("     "));
   

#endif
 
  for (int j=0; j<qtyChannal; j++){
    if (Temperature[j] < AlarmTemp[j]) 
      tone(BeepPin,4000,5);
    }
  
}
  // обработка поворота энкодера на изменение канала))
  rotating = true;  // reset the debouncer
  if ((encoderR)|(encoderL)) {
    if (encoderR) {
      channel += 1;
    }
    else
    {
      channel -= 1;
    }
    if ( channel > qtyChannal-1) {      // количество каналов, т.к считаем с нуля
          channel = 0;
        } 
        if ( channel < 0 ) {
          channel = qtyChannal-1;
        }
    encoderR = false;
    encoderL = false;
   
  }
  // ================ по нажатию кнопки энкодера - меню настроек ====================
  if(digitalRead(encoderK) == 0) {
    MenuTimeoutTimer = 10; //таймер таймаута, секунд
    lcd.clear();
    lcd.setCursor(0, 0); //инфо на LCD
    lcd.print(F("< SETUP >  CH")); 
    lcd.print(channel);
    if (BeepEnabled) {
      tone(BeepPin,4000,50);
    }
    delay(200);
    int menuitem = 0;
   
   
    do {              //обработка пунктов меню (по выбранному каналу)
      rotating = true;  // reset the debouncer
      if ((encoderR)|(encoderL)) {
        MenuTimeoutTimer = 10; //таймер таймаута, секунд
        if (encoderR) {
          menuitem += 1;
        }
        else  {
          menuitem -= 1;
        }
        if ( menuitem > 10 ) {
          menuitem = 0;
        } // границы пунктов меню
        if ( menuitem < 0 ) {
          menuitem = 10;
        }
        encoderR = false;
        encoderL = false;
      }
 
      // индикация пункта меню (номер пункта - в menuitem)
      lcd.setCursor(0, 1); //инфо на LCD
      switch(menuitem)
      {
      case 0:
        lcd.print(F("0.T-ON Relay   "));
        break;
      case 1:
        lcd.print(F("1.TIMER1 SET    ")); 
        break;
      case 2:
        lcd.print(F("2.TIMER2 SET    ")); 
        break;
      case 3:
        lcd.print(F("3.TIMER3 SET    ")); 
        break;     
      case 4:
        lcd.print(F("4.TIMER4 SET    ")); 
        break;     
      case 5:
        lcd.print(F("5.CLOCK SET     ")); 
        break;
      case 6:
        lcd.print(F("6.HYSTERESIS SET")); 
        break;
      case 7:
        lcd.print(F("7.T-CORRECT SET ")); 
        break;
      case 8:
        lcd.print(F("8.SOUND SET     ")); 
        break;
      case 9:
        lcd.print(F("9.T-ALARM SET   ")); 
        break; 
      case 10:
         lcd.print(F("10.BACK          ")); 
        break;         
      }
      if (MenuTimeoutTimer == 0) {
        menuitem = 10;
      }
 
    }
    while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
    // если нажата кнопка энкодера или таймаут - обработка пункта меню (номер пункта - в menuitem)
    if (BeepEnabled) {
      tone(BeepPin,4000,50);
    }
    switch(menuitem)
    {
      // ====== пункт 0 - выход
    case 10:
      if (BeepEnabled) {
        tone(BeepPin,BeepToneNo,BeepToneNoDuration);
      } //звук "NO"
      break; // case 0 out
 
      // ====== пункт 1 - установка Timer1
    case 1:
      TimerXSetup(0);
      break; // case 1 out
 
      // ====== пункт 2 - установка Timer2
    case 2:
      TimerXSetup(1);
      break; // case 2 out
 
      // ====== пункт 3 - установка Timer3
    case 3:
      TimerXSetup(2);
      break; // case 3 out
 
      // ====== пункт 4 - установка Timer4
    case 4:
      TimerXSetup(3);
      break; // case 4 out
 
      // ====== пункт 5 - установка RTC
    case 5:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP CLOCK")); 
      delay(200);
      RTC.readClock();
      Hours=RTC.getHours();
      Minutes=RTC.getMinutes();
      SetYesNo = false;
      PrintYesNo = true;
      SetTime(0,1); // в позиции 0,1 - запрос ввода времени
      if (MenuTimeoutTimer != 0) {
        if (SetYesNo)
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
          }
          RTC.setHours(Hours);
          RTC.setMinutes(Minutes);
          RTC.setSeconds(0);
          RTC.setClock();
          RTC.start();
        }
        else
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
          }
        }
      }
      else {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 5 out
 
      // ====== пункт 6 - установка гистерезиса
    case 6:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      HysteresisOld[channel] = Hysteresis[channel];
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP HYSTERESIS")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print("   "); 
        }
        else {
          lcd.print(Hysteresis[channel], 1); 
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
          Hysteresis[channel] += 0.1;
          encoderR = false;
        }
        if (encoderL) {
          Hysteresis[channel] -= 0.1;
          encoderL = false;
        }
        Hysteresis[channel] = constrain(Hysteresis[channel], 0.1, 20); // крайние значения
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
      if (MenuTimeoutTimer != 0) {
        EEPROM.updateFloat(int(&HysteresisEE[channel]), Hysteresis[channel]); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
        Hysteresis[channel] = HysteresisOld[channel];
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 6 out
 
      // ====== пункт 7 - установка коррекции температуры
    case 7:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP T-CORRECT ")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print(F("    ")); 
        }
        else {
          if (TemperatureCorr[channel] >= 0) {
            lcd.print(F("+"));
          }
 
          lcd.print(TemperatureCorr[channel], 1); 
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
          TemperatureCorr[channel] += 0.1;
          encoderR = false;
        }
        if (encoderL) {
          TemperatureCorr[channel] -= 0.1;
          encoderL = false;
        }
        TemperatureCorr[channel] = constrain(TemperatureCorr[channel], -8, 8); // крайние значения
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
 
      if (MenuTimeoutTimer != 0) {
        EEPROM.updateFloat(int(&TemperatureCorrEE[channel]), TemperatureCorr[channel]); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
        TemperatureCorr[channel] = EEPROM.readFloat(int(&TemperatureCorrEE[channel]));
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 7 out     
 
      // ====== пункт 8 - вкл/выкл звука
    case 8:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SOUND SET       ")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (BeepEnabled) {
          lcd.print(F("BEEP ON         ")); 
        }     
        else {
          lcd.print(F("BEEP OFF        ")); 
        }
 
        rotating = true;  // reset the debouncer
        if ((encoderR)|(encoderL)) {
          BeepEnabled = !BeepEnabled;
          encoderR = false;
          encoderL = false;
        }
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
 
      if (MenuTimeoutTimer != 0) {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
        EEPROM.updateByte(int(&BeepEnabledEE), BeepEnabled);
      }
      if (MenuTimeoutTimer == 0) {
        BeepEnabled = EEPROM.readByte(int(&BeepEnabledEE));
      }
      break; // case 8 out
 
      // ====== пункт 9 - установка предупреждалки о холоде
    case 9:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("ALARM-TEMP SET  ")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print(F("    ")); 
        }
        else {
          if (AlarmTemp[channel] >= 0) {
            lcd.print(F("+"));
          }
 
          lcd.print(AlarmTemp[channel], 0); 
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
          AlarmTemp[channel] += 1;
          encoderR = false;
        }
        if (encoderL) {
          AlarmTemp[channel] -= 1;
          encoderL = false;
        }
        AlarmTemp[channel] = constrain(AlarmTemp[channel], 15, 30); // крайние значения
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
 
      if (MenuTimeoutTimer != 0) {
        EEPROM.updateFloat(int(&AlarmTempEE[channel]), AlarmTemp[channel]); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
        AlarmTemp[channel] = EEPROM.readFloat(int(&AlarmTempEE[channel]));
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 9 out
      
      // ====== пункт 10 - установка температуры термостатирования
   case 0:
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      TstatTempOld[channel]=TstatTemp[channel];
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP T-ON Raley")); 
      delay(200);
      do {
        lcd.setCursor(0,1);
        if (blink500ms) {
          lcd.print("   "); 
        }
        else {
          lcd.print(TstatTemp[channel], 1); 
          lcd.write(0x02); // значок градуса
        }
        rotating = true;  // reset the debouncer
        if (encoderR) {
         TstatTemp[channel] += 1;
          encoderR = false;
        }
        if (encoderL) {
         TstatTemp[channel] -= 1;
          encoderL = false;
        }
        TstatTemp[channel] = constrain(TstatTemp[channel], 1, 80); // крайние значения
      }
      while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
      if (MenuTimeoutTimer != 0) {
        EEPROM.updateFloat(int(&TstatTempEE[channel]), TstatTemp[channel]); // запись в ЕЕПРОМ
        if (BeepEnabled) {
          tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
        }
      }
      else {
       TstatTemp[channel] = TstatTempOld[channel];
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
      break; // case 10 out
    }
 
    delay(200);
    lcd.clear();
  }
}
  
// ===== SUBROUTINES ==================================================================
 //функция очистки сдвигового регистра
 void cleanreg(){
  for(int i=0; i<8; i++) 
  Relay[i]=!RelayOn;
  digitalWrite(latch, LOW);
  shiftOut(data, clock, LSBFIRST, 0);
  digitalWrite(latch, HIGH);
  }
// ========================================
//функция управления сдвиговым регистром
 void sendpin(int pin, boolean state){
  
  Relay[pin]=state;
   
  byte value = 0;
  byte add = 1;
  for(int i=0; i<8; i++){
    if(Relay[i]==HIGH) value+=add;
    add*=2;
  }
  digitalWrite(latch, LOW);
  shiftOut(data, clock, MSBFIRST, value);
  digitalWrite(latch, HIGH);
}
// ========================================
 void getTemp(){   // измеряем температуру  на всех каналах
   DS18B20.requestTemperatures();    
  for (int j=0; j<qtyChannal; j++){ 
   DS18B20TempTmp[j] = DS18B20.getTempC(sensor[j]); 
   DS18B20Temperature[j] += DS18B20TempTmp[j]; // суммируем для усреднения
    }
    DS18B20iteration ++;
      if (DS18B20iteration == 10)
      {
        DS18B20iteration = 0;
        for (int j=0; j<qtyChannal; j++){
          Temperature[j] = (DS18B20Temperature[j] / 10) + TemperatureCorr[j]; //усреднённая + коррекция
          DS18B20Temperature[j] = 0;
          }
     }
   }
   // ========================================
   void Termostat() {//термостатирование
   for (int j=0; j<8; j++){
   
    if ( TstatTimer[j] == 0 )       //если пауза между вкл/выкл реле превысила TstatTimerMax;
    {
      if ( Temperature[j] > ( TstatTemp[j] + Hysteresis[j] ) ) // если температра канала > температура заданная+гистерезис
    {
       if ( Relay[j]==RelayOn ) // если реле включено -
      {
        sendpin(j, !RelayOn); // выключить реле; 
        TstatTimer[j] = TstatTimerMax; // реле держать выключённым не менее заданного в TstatTimerMax времени
      }
    }
    if (Temperature[j] < TstatTemp[j])       //если температура канала меньше заданной
    {
      if (  Relay[j]==!RelayOn ) // если реле выключено
      {
        sendpin(j, RelayOn); // включить реле
        TstatTimer[j] = TstatTimerMax; // реле держать включённым не менее заданного в TstatTimerMax времени
      }
    }
    }
   }
   }
// ========================================
void SetTime(char x, char y)
{
  // ========= set hours
  SetH = true;
  do {
    PrintRTC(x,y);
    rotating = true;  // reset the debouncer
    if (encoderR) {
      Hours += 1;
      if(Hours > 23) {
        Hours = 0;
      };
      encoderR = false;
    }
    if (encoderL) {
      Hours -= 1;
      if(Hours < 0) {
        Hours = 23;
      };
      encoderL = false;
    }
  }
  while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
  if (BeepEnabled) {
    tone(BeepPin,4000,50); //звук "YES"
  }
  SetH = false;
  delay(200);
  // ========= set minutes
  SetM = true;
  do {
    PrintRTC(0,1);
    rotating = true;  // reset the debouncer
    if (encoderR) {
      Minutes += 1;
      if(Minutes > 59) {
        Minutes = 0;
      };
      encoderR = false;
    }
    if (encoderL) {
      Minutes -= 1;
      if(Minutes < 0) {
        Minutes = 59;
      };
      encoderL = false;
    }
  }
  while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
  if (BeepEnabled) {
    tone(BeepPin,4000,50); //звук "YES"
  }
  if (PrintYesNo) {
    SetM = false;
    delay(200);
    // ========= set yes-no
    SetYesNo = false;
    do {
      PrintRTC(0,1);
      rotating = true;  // reset the debouncer
      if ((encoderR)||(encoderL)) {
        SetYesNo = !SetYesNo;
        encoderR = false;
        encoderL = false;
      }
    }
    while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
    delay(200);
  }
 
}
 
// ========================================
void PrintRTC(char x, char y)
{
  lcd.setCursor(x,y);
  if (SetH&&blink500ms) {
    lcd.print(F("  "));
  }
  else {
    if (Hours < 10) {
      lcd.print(F("0"));
    }   
    lcd.print(Hours);
  }
 
  // мигающее двоеточие, если не в режиме установки времени
  if (!(SetH||SetM||PrintYesNo||blink500ms))
  {
    lcd.print(F(" "));
  }
  else {
    lcd.print(F(":"));
  }
 
  if (SetM&&blink500ms) {
    lcd.print(F("  "));
  }
  else {
    if (Minutes < 10) {
      lcd.print(F("0"));
    }   
    lcd.print(Minutes);
  }
  lcd.print(F(" "));
 
  if (PrintYesNo) {
    lcd.print(F("["));
    if (!(SetH||SetM||blink500ms))
    {
      lcd.print(F("   "));
    }
    else {
      if (SetYesNo)
      {
        lcd.print(F("YES"));
      }
      else {
        lcd.print(F("NO "));
      }
    }
    lcd.print(F("]"));
  }
 
}
 
// ============================ Encoder interrupts =============================
// Interrupt on A changing state
void doEncoderA(){
  if ( rotating ) {
    delay (1) ;  // wait a little until the bouncing is done
  }
  // Test transition, did things really change?
  if( digitalRead(encoderA) != A_set ) {  // debounce once more
    A_set = !A_set;
    // adjust counter + if A leads B
    if ( A_set && !B_set )
    {
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      if (BeepEnabled) {
        tone(BeepPin,4000,5);
      }
      encoderR = true;
      rotating = false;  // no more debouncing until loop() hits again
    }
  }
}
// Interrupt on B changing state, same as A above
void doEncoderB(){
  if ( rotating ) {
    delay (1);
  }
  if( digitalRead(encoderB) != B_set ) {
    B_set = !B_set;
    //  adjust counter - 1 if B leads A
    if( B_set && !A_set ) {
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      if (BeepEnabled) {
        tone(BeepPin,4000,5);
      }
      encoderL = true;
      rotating = false;
    }
  }
}
// ============================ Timer0 interrupt =============================
// run every 500ms
void timerIsr()
{
  blink500ms = !blink500ms; // инверсия мерцающего бита
  if(blink500ms) {
    plus1sec = true; // ежесекундно взводится
    for (int j=0; j<qtyChannal; j++) {
    if (TstatTimer[j] != 0) {
      TstatTimer[j] --; // ежесекундный декремент этого таймера
    }
    }
    if (MenuTimeoutTimer != 0) {
      MenuTimeoutTimer --; // ежесекундный декремент этого таймера
    }
  
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void TimerXSetup(int X) {
      MenuTimeoutTimer = 10; //таймер таймаута, секунд
      lcd.clear();
      lcd.setCursor(0, 0); //инфо на LCD
      lcd.print(F("SETUP TIMER")); 
      lcd.print(X+1);  // выводим номер таймера на LCD
      delay(200);
      Hours=Timer[X][channel].Hours;
      Minutes=Timer[X][channel].Minutes;
      SetYesNo = false;
      PrintYesNo = true;
      SetTime(0,1); // в позиции 0,1 - запрос ввода времени
      if (MenuTimeoutTimer != 0) {
        if (SetYesNo) // если при установке времени выбрано "Yes"
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
          }
          Timer[X][channel].Hours = Hours;
          Timer[X][channel].Minutes = Minutes;
          Timer[X][channel].Enabled = true;
          EEPROM.updateBlock(int(&TimerEE[X][channel]), Timer[X][channel]);
 
          MenuTimeoutTimer = 10; //таймер таймаута, секунд
          lcd.clear();
          lcd.setCursor(0, 0); //инфо на LCD
          lcd.print(F("Timer"));
          lcd.print(X+1);
          lcd.print(F(" Temp. Set")); 
          delay(200);
          do {
            lcd.setCursor(0,1);
            if (blink500ms) {
              lcd.print(F("     ")); 
            }
            else {
              lcd.print(Timer[X][channel].Temperature, 1); 
              lcd.write(0x02); // значок градуса
            }
            rotating = true;  // reset the debouncer
            if (encoderR) {
              Timer[X][channel].Temperature += 0.1;
              encoderR = false;
            }
            if (encoderL) {
              Timer[X][channel].Temperature -= 0.1;
              encoderL = false;
            }
            Timer[X][channel].Temperature = constrain(Timer[X][channel].Temperature, 10, 80); // крайние значения
          }
          while ((digitalRead(encoderK)==1)|(MenuTimeoutTimer==0));
 
          if (MenuTimeoutTimer != 0) { // если после выбора температуры нажата кнопка энкодера
          EEPROM.updateBlock(int(&TimerEE[X][channel]), Timer[X][channel]);
            if (BeepEnabled) {
              tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
            }
          }
          else { // если не нажата - используем старую температуру
          EEPROM.readBlock(int(&TimerEE[X][channel]), Timer[X][channel]);
            if (BeepEnabled) {
              tone(BeepPin,BeepToneYes,BeepToneYesDuration); //звук "YES"
            }
          }      
 
        }
        else // если при установке времени выбрано "No"
        {
          if (BeepEnabled) {
            tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
          }
          EEPROM.readBlock(int(&TimerEE[X][channel]), Timer[X][channel]);
          Timer[X][channel].Enabled = false;
          EEPROM.updateBlock(int(&TimerEE[X][channel]), Timer[X][channel]);
        }
      }
      else {
        if (BeepEnabled) {
          tone(BeepPin,BeepToneNo,BeepToneNoDuration); //звук "NO"
        }
      }
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~````
 

 

Ahtme
Offline
Зарегистрирован: 08.03.2017

leshak пишет:

А вообще, лично бы я пострелся ставить что-то что может самостоятельно "зажигать гас". Я бы ставил только больше/меньше. Причем, как правильно заметил JollyBiber нужен еще контроль что "горении идет".

Если погасло (не важно по каким причинам), все перекрывать, слать SMS хозяину, и... по хорошему, еще бы и себя обесточить.

А уж он пусть приходить и разбирается "чего погасло". И запуск только "в ручном режиме".

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

А меня задавила. Имелся работающий котёл (примерно 13 лет эксплуатации). Имелась в нем одна доработка  - заглушен контур ГВ, так как оказался самой не надёжной частью и требовал замены уплотнительных резинок. С заглушеным контуром ГВ проработал примерно 9 лет. А осенью 16го автоматика стала выдавать ошибки бредовые. Вызвал ремонтника профессионала, который с ходу сказал, что так долго не живут, мало того, что заказать такую же автоматику не получится, так и железо-то наверняка всё покрыто накипью и прочими отложениями, и насос та уже стаскался... короче есть у них на складе новые котлы из старой партии, и потому они его могут мне продать всего за 750 евросов...

И тут меня жаба и задавила. Я сам занимаюсь установкой и настройкой готового оборудования и автоматики отопления и ГВС в частных и многоквартирных домах. А тут ещё ради интереса прошлой весной собрал на Ардуино простой термостат с управлением сервоприводом трехходового клапана.

В общем как про 750 евро услышал, у меня сразу мысль родилась, попробовать Ардуино подружить с моим котлом. И так как я в схемотекнике не опытен был, и до этого паял только лампочки на новогодней гирлянде, поэтому прект ещё не закончил. Но много сделано, котёл запускается, датчик ионизаци простой как две копейки собрал сам, температуру нагнали - выключили клапана. Сбой электричества, свет пропал - даже при горящем котле - клапана закрылись. И так уже два месяца работает черновой вариант.

Ардуино - 6 евро. Остальное выпаяно из старой автоматики и старого же компьютерного монитора с кинескопом. Бонусом тернеровка серого вещества, новые знания и бесценный опыт.

И никакого привлечения военокосмического комплекса с нанотехнологиями не надо. Просто надо понимать, с чем имеешь дело.

А ещё... Хотел сказать по поводу диферамб в сторону наружнего датчика и погодозависимых графиков...

Во всех автоматиках, по крайней мере, что у нас в Эстонии продают, предусмотрен вход для подключения комнатного датчика. Именно с таким датчиком система работает более эффективно (читай экономно) и комфортней для потребителя. Устанавливать датчик конечно надо с учетом конкретного помещения и ньюансов там не мало.

Утверждать, что погодозависимого управления по графику достаточно для системы отопления дома - можно. Но утверждение, что это лучше управления по температуре внутреннего воздуха противоречит практике.

pev0270
Offline
Зарегистрирован: 02.11.2015

Ahtme пишет:

1. А осенью 16го автоматика стала выдавать ошибки бредовые.

2. Я сам занимаюсь установкой и настройкой готового оборудования и автоматики отопления и ГВС в частных и многоквартирных домах. А тут ещё ради интереса прошлой весной собрал на Ардуино простой термостат с управлением сервоприводом трехходового клапана.

3. И так как я в схемотекнике не опытен был, и до этого паял только лампочки на новогодней гирлянде, поэтому прект ещё не закончил. Но много сделано, котёл запускается, датчик ионизаци простой как две копейки собрал сам, температуру нагнали - выключили клапана. Сбой электричества, свет пропал - даже при горящем котле - клапана закрылись. И так уже два месяца работает черновой вариант.

4. Ардуино - 6 евро. Остальное выпаяно из старой автоматики и старого же компьютерного монитора с кинескопом. Бонусом тернеровка серого вещества, новые знания и бесценный опыт.

5. А ещё... Хотел сказать по поводу диферамб в сторону наружнего датчика и погодозависимых графиков...

Во всех автоматиках, по крайней мере, что у нас в Эстонии продают, предусмотрен вход для подключения комнатного датчика. Именно с таким датчиком система работает более эффективно (читай экономно) и комфортней для потребителя. Устанавливать датчик конечно надо с учетом конкретного помещения и ньюансов там не мало.

6. Утверждать, что погодозависимого управления по графику достаточно для системы отопления дома - можно. Но утверждение, что это лучше управления по температуре внутреннего воздуха противоречит практике.

1. Автоматика показывает ошибки, а вы ее на помойку и все напрямую подключили... однако... (храни вас Господь)

2. Это хорошо когда разбираешься в теме. Вон хирург Леонид Рогозов сам себе аппендикс вырезал, но у него была делема жизнь или смерть. Вы тоже по этому пути собрались? Напишите в своем посте большими буквами "так делать нельзя" все делаете на свой страх и риск...

3. В схеммотехнике придется разбираться и в программировании тоже, ибо просто при пропадании электричества закрыть газ это мало...

4. Да-да 6 евро это стоимость кристала для любителей сделать что то своими руками. В нашем случае все делают устройство которое заставляет котел работать так как нам кажется правильно. Причем мы не лезем в защиту котла, а тупо используем контакты выведенные производителем для наружного таймера. При выходе из строя нашего изделия котел будет работать как и прежде в штатном режиме... Это единственно правильное решение, ибо 97% тех кто делает что то на ардуино, не знает (да это и не нужно) как работает автоматика того же котла...

5. Посмотрел температуру в Эстонии по зимним месяцам, смешно стало... Эффективно и экономно не одно и тоже... (читай п.6)

6. Зачем греть воду до максимума когда за окном +5? Проблема регулировки по температуре помещения это черезмерно горячие батареи, как следствие сухой воздух в помещении, как следствие статическое напряжение на всем... Ну и еще много чего можно сказать в зависимости от строения и степени его утепления и т.д.

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

uragan
Offline
Зарегистрирован: 23.02.2015

По п. 6 не понятно. Как связана сухость воздуха в помещении с температурой батарей, если в помещении нормальная температура?

pev0270
Offline
Зарегистрирован: 02.11.2015

uragan пишет:

Как связана сухость воздуха в помещении с температурой батарей, если в помещении нормальная температура?

На прямую. В отопительный сезон горячие батареи сушат воздух, проветривание помещения в мороз также сушит воздух. Так что температура в помещении ни при чем. А чем больше температура батареи тем интенсивнее происходит "осушение". Как то так...

В п. 6 можно еще указать что при инерционной системе при регулировании по температуре в помещении могут окна начать "плакать"... своего рода увлажнение... :)

Просто нельзя утверждать что по температуре в помещении регулировка самая-самая... (и она подтверждена практикой в конкретной климатической зоне) Очень много разных факторов влияют на комфорт в помещении в период отопления.