Контроль влажности подвала Arduino pro mini

AnDrEyKa_111
Offline
Зарегистрирован: 09.01.2019

У нас в Крыму морозы до -35 встречаются крайне редко, но обычные -5...-10 он вполне нормально переживал. При этом работал и что-то там показывал на экране - вероятно тепловыделение работает в качестве обогрева :) Корпус у меня практически герметичный.

 

miketk
Offline
Зарегистрирован: 10.06.2020

Спасибо. А под датчики bme280 никто не переделывал? Почитал, что датчики dht22 деградируют в течении пары лет.

Andrey007
Offline
Зарегистрирован: 25.08.2020

AnDrEyKa_111, как с Вами связаться для консультации?

AnDrEyKa_111
Offline
Зарегистрирован: 09.01.2019

Andrey007 пишет:

AnDrEyKa_111, как с Вами связаться для консультации?

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

Andrey007
Offline
Зарегистрирован: 25.08.2020

Это мое первое изделие на ардуино и скетчи у меня пока на нуле. Буду делать и учиться по пути.

Мне нужно конструкцию на подвал + гараж, соответственно два вентилятора (по одному на каждое помещение) и три датчика.

1. В Вашей схеме какой карлсон куда (где подвал и где верхний и какой из датчиков куда (нужно три, я так понимаю).

2. Можно ли получить прошивку, где масштабируется график, как на видео в ютубе?

3. Ну и стена в выводе на экран меня не интересует, как поправить?

4. Поправлять коэффициенты датчиков нужно, или заведется и так с приемлемой точностью?

AnDrEyKa_111
Offline
Зарегистрирован: 09.01.2019

1. У меня вообще двухскоростной вентилятор, то есть с тремя контактами. А на схеме первый - это вентилятор, а второй - нагреватель. Кто где - настраивается в скетче, там если в дефайны внимательно посмотреть и почитать описание, то всё будет понятно.

2. Да, вот моя последняя версия (для удобства опубликую отдельно в сл. сообщении)

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

4. Нет, калибровка датчиков - это отдельная процедура и она обязательна. Она вроде была где-то в этой теме описана. 

 

AnDrEyKa_111
Offline
Зарегистрирован: 09.01.2019
/*
Описание
1. Часы реального времени. Данные приходят от головного блока. Потом обновлются по таймеру (). Пришедшие данные имеют более высокий приоритет
1.1 Установить библиотеку  Time library - <a href="https://github.com/PaulStoffregen/Time" rel="nofollow">https://github.com/PaulStoffregen/Time</a>  
У нее не удачное имя (винды регистронезависимы и имя совпадает с системным -- среда глючит)
переименовать в stmTime  директорию, файлы и ссылки в них !!!
*/

// - ОПЦИИ -------------------------------
//#define DEBUG                                   // Отладочную  информацию в ком порт посылает  
//#define DEBUG2                                   // Отладочную  информацию в ком порт посылает  
//#define DEBUG3                                  
//#define DEMO                                      // Признак демонстрации - данные с датчиков генерятся рандом
//#define BEEP                                      // Использовать пищалку
//#define RADIO                                     // Признак использования радио модуля
//#define HEATER                                    // Признак использования нагревателя
//#define INFOSCREEN                                // Куски с информационным экраном
#define SENSORS_QUANTITY 3                        // Предусмотрено три варианта опции: 2, либо 3, либо 4 сенсора температуры
#define SERVO                                     // Использование заслонок с серво-приводами
#define VERSION "v.076 09.02.20"                  // - базовая версия (оригинальная) - Ver. 0.75 05/01/16
                                                  // на её основе создана версия от AnDrEyKa 
//#define ID             0x21                     // уникально Идентификатор устройства (тип) - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
#if ID == 0x21                                    // Место установки блока - отоображается как заголовок колонки в таблице. Не более 6 символов а то не влезает  
  #define LABEL "Гараж"
#elif ID == 0x22     
  #define LABEL "Подвал"
#else 
  #define LABEL "none"
#endif

#pragma GCC optimize ("-Os")
//#pragma pack(push, 1)     // выравнивание по одному байту ????????

#include <SPI.h>
#include <EEPROM.h>
#include <avr/wdt.h>
#include <leOS.h>      // Шедуллер задач
#include <dht.h>       // Дачик влажности и температуры
#include "Ucglib.h"    // ВНИМАНИЕ использовать библиотеку не познее 1.01 справка <a href="https://code.google.com/p/ucglib/wiki/" rel="nofollow">https://code.google.com/p/ucglib/wiki/</a>
#include "rusFont.h"   // Русские шрифты
#ifdef SERVO
  #include <Adafruit_PWMServoDriver.h>                     
#endif
#ifdef RADIO
  #include "nRF24L01.h"  // Беcпроводной модуль надо использовать библиотеку <a href="http://tmrh20.github.io/RF24" rel="nofollow">http://tmrh20.github.io/RF24</a>
  #include "RF24.h"      // Беcпроводной модуль используются не стандартные функции <a href="https://github.com/TMRh20" rel="nofollow">https://github.com/TMRh20</a>
#endif

// Макросы для работы с портами  скорость и место
#define SetOutput(port,bit)       DDR ## port |= _BV(bit)
#define SetInput(port,bit)        DDR ## port &= ~_BV(bit)
#define SetBit(port,bit)          PORT ## port |= _BV(bit)
#define ClearBit(port,bit)        PORT ## port &= ~_BV(bit)
#define WritePort(port,bit,value) PORT ## port = (PORT ## port & ~_BV(bit)) | ((value & 1) << bit)
#define ReadPort(port,bit)        (PIN ## port >> bit) & 1
#define PullUp(port,bit)          { SetInput(port,bit); SetBit(port,bit); }
#define Release(port,bit)         { SetInput(port,bit); ClearBit(port,bit); }
// Мои макросы
#define MOTOR_BIT                 0            // бит мотора в packet.flags
#define HEAT_BIT                  1            // бит калорифера в packet.flags
#define ABS_H_BIT                 2            // бит кодирования влажности абс или % в packet.flags
#define MODE_BIT                  5            // первый бит режима в packet.flags
//#define MASK_MODE_BITS            0xe0         // маска для выделения номера режима в packet.flags
#define SET_MODE_BITS             packet.flags |= (settingRAM.mode<<MODE_BIT)  // запись номера режима в packet.flags
#define FLAG_ABS_H_ON             packet.flags |= (1<<ABS_H_BIT)   // бит ABS_H_BIT установить в 1
#define FLAG_ABS_H_OFF            packet.flags &= ~(1<<ABS_H_BIT)  // бит ABS_H_BIT установить в 0
#define FLAG_ABS_H_CHECK          packet.flags & (1<<ABS_H_BIT)    // бит ABS_H_BIT проверить на 1

#define FLAG_MOTOR_ON             packet.flags |= (1<<MOTOR_BIT)   // бит мотора установить в 1
#define FLAG_MOTOR_OFF            packet.flags &= ~(1<<MOTOR_BIT)  // бит мотора установить в 0
#define FLAG_MOTOR_CHECK          packet.flags & (1<<MOTOR_BIT)    // бит мотора проверить на 1
#define MOTOR_ON                  { WritePort(C,0,HIGH); FLAG_MOTOR_ON;  }   // включить мотор
#define MOTOR_OFF                 { WritePort(C,0,LOW) ; FLAG_MOTOR_OFF; }   // выключить мотор

#ifdef SERVO
bool MotorFastSpeed=false;
#define MOTOR_FAST_SPEED                  { WritePort(C,2,HIGH); MotorFastSpeed=true;}   // Переключить вент в быстрый режим
#define MOTOR_SLOW_SPEED                  { WritePort(C,2,LOW); MotorFastSpeed=false;}    // Переключить вент в медленный режим
#endif

#define FLAG_HEAT_ON              packet.flags |= (1<<HEAT_BIT)   // бит калорифера установить в 1
#define FLAG_HEAT_OFF             packet.flags &= ~(1<<HEAT_BIT)  // бит калорифера установить в 0
#define FLAG_HEAT_CHECK           packet.flags & (1<<HEAT_BIT)    // бит калорифера проверить на 1
#define HEAT_ON                   { WritePort(C,3,HIGH); FLAG_HEAT_ON;  }   // включить калорифер
#define HEAT_OFF                  { WritePort(C,3,LOW); FLAG_HEAT_OFF; }   // выключить калорифер


// - КОНСТАНТЫ --------------------------------------
//#define dH_OFF          20                     // Гистерезис абсолютной влажности в сотых грамма на куб - переведена в переменную как 1/4 от дельта
#define dT_OFF          20                     // Гистерезис температуры в сотых градуса
#define TEMP_LOW       150                     // Температура подвала критическая - может быть замораживание (в сотых градуса) - система выключается и включается нагреватель
// СИСТЕМАТИЧЕСКИЕ ОШИБКИ ДАТЧИКОВ для каждого ID свой. ОШИБКИ ДОБАВЛЯЮТСЯ!!
#if ID == 0x21      // ОТКАЛИБРОВАНО не менять
    #define TOUT_ERR      +0                  // Ошибка уличного  датчика температуры в сотых долях градуса
    #define TIN_ERR       +0                  // Ошибка гаражного датчика температуры в сотых долях градуса
    #define TPIT_ERR      +0                  // Ошибка ямного    датчика температуры в сотых долях градуса
    #define TWALL_ERR     +0                  // Ошибка стенового датчика температуры в сотых долях градуса
    #define HOUT_ERR      -0                  // Ошибка уличного  датчика влажности в сотых долях %
    #define HIN_ERR       +0                  // Ошибка гаражного датчика влажности в сотых долях %
    #define HPIT_ERR      -0                  // Ошибка ямного    датчика влажности в сотых долях %
    #define HWALL_ERR     +0                  // Ошибка стенового датчика влажности в сотых долях %
#elif ID == 0x22     // ОТКАЛИБРОВАНО не менять
    #define TOUT_ERR      +0                  // Ошибка уличного датчика температуры в сотых долях градуса +50
    #define TIN_ERR       +0                  // Ошибка домового датчика температуры в сотых долях градуса +50
    #define HOUT_ERR      -0                  // Ошибка уличного датчика влажности в сотых долях %         -260
    #define HIN_ERR       +0                  // Ошибка домового датчика влажности в сотых долях %         +480
#elif defined DEBUG3     
    #define TOUT_ERR      -2550               // Ошибка уличного  датчика температуры в сотых долях градуса
    #define TIN_ERR       -3644               // Ошибка гаражного датчика температуры в сотых долях градуса
    #define TPIT_ERR      -3000               // Ошибка ямного    датчика температуры в сотых долях градуса
    #define TWALL_ERR     0                   // Ошибка стенового датчика температуры в сотых долях градуса
    #define HOUT_ERR      0                   // Ошибка уличного  датчика влажности в сотых долях %
    #define HIN_ERR       52                  // Ошибка гаражного датчика влажности в сотых долях %
    #define HPIT_ERR      0                   // Ошибка ямного    датчика влажности в сотых долях %
    #define HWALL_ERR     0                   // Ошибка стенового датчика влажности в сотых долях %
#else 
    #define TOUT_ERR      -1                  // Ошибка уличного  датчика температуры в сотых долях градуса
    #define TIN_ERR       3                   // Ошибка гаражного датчика температуры в сотых долях градуса
    #define TPIT_ERR      -2                  // Ошибка ямного    датчика температуры в сотых долях градуса
    #define TWALL_ERR     0                   // Ошибка стенового датчика температуры в сотых долях градуса
    #define HOUT_ERR      0                   // Ошибка уличного  датчика влажности в сотых долях %
    #define HIN_ERR       52                  // Ошибка гаражного датчика влажности в сотых долях %
    #define HPIT_ERR      0                   // Ошибка ямного    датчика влажности в сотых долях %
    #define HWALL_ERR     0                   // Ошибка стенового датчика влажности в сотых долях %
#endif 

// Сохранённая погрешность по разным номерам датчиков, над NaCl H%=75.3
// 1 T=       H=-1460
// 2 T=       H=-960
// 3 T=-34    H=-2460     - на выброс
// 4 T=       H=-900
// 5 T=       H=430       - 2305 #1
// 6 T=-1     H=26        - 2305 #2
// 7 T=3      H=78        - 2305 #3
// 8 T=-2     H=-104      - 2305 #4
// 9 T=       H=

// - ВРЕМЕНА ---------------------------------------
//#ifdef DEMO                                     // Для демо все быстрее и случайным образом
#if defined(DEMO) || defined(DEBUG3)
    #define NUM_SAMPLES      3                  // Число усреднений измерений датчика ТОЛЬКО целые тысячи для точности часов
    #define TIME_SCAN_SENSOR 2000               // Время опроса датчиков мсек, для демки быстрее
    #define TIME_PRINT_CHART 4000               // Время вывода точки графика мсек, для демки быстрее
    #define TIME_HOUR        50000              // Число мсек в часе, для демки быстрее   
#else   
   #define NUM_SAMPLES      10                  // Число усреднений измерений датчика
   #define TIME_SCAN_SENSOR 3000                // Время опроса датчиков мсек ТОЛЬКО целые тысячи для точности часов
   #define TIME_PRINT_CHART 300000              // Время вывода точки графика мсек
   #define TIME_HOUR        3600000             // Число мсек в часе
#endif


#define RESET_TFT_KEY   9999                    // Очень длительное нажатие на клавишу мсек, сброс контроллера дисплея и появляется основной экран данные сохраняются
#define LONG_KEY        2000                    // Длительное нажатие кнопки мсек, появляется Экран инфо
#define SHORT_KEY       100                     // Короткое нажатие кнопки более мсек
#define RESET_TEMP_KEY  4000                    // Время нажатия кнопки более мсек для сброса температур
#define NRF24_CHANEL    100                     // Номер канала nrf24

//  НОГИ к которым прицеплена переферия (SPI используется для TFT и NRF24 - 11,12,13)
#define PIN_HEAT      17                         // Ножка куда повешен калорифер A3 (port C3)
#ifdef BEEP
    #define PIN_BEEP  15                         // Ножка куда повешена пищалка A1 (port C1)
#endif
#define PIN_RELAY     14                         // Ножка на которую повешено реле (SSR) вентилятора - аналоговый вход A0 через резистор 470 ом (port C0)
#define PIN_SPEED     16                         // Ножка куда повешено реле переключения скоростей вентилятора A2 (port C2)
#define PIN_CS        10                         // TFT дисплей spi CS
#define PIN_CD        9                          // TFT дисплей spi CD
#define PIN_RESET     8                          // TFT дисплей spi Reset  дисплей
#define PIN_DHT22a    7                          // Первый датчик DHT22   OUT УЛИЦА
#define PIN_DHT22b    6                          // Второй датчик DHT22   IN  ДОМ
#define PIN_DHT22c    5                          // Третий датчик DHT22     PIT  ЯМА
#define PIN_DHT22d    4                          // Четвёртый датчик DHT22  WALL  СТЕНЫ
#define PIN_KEY       3                          // Кнопка, повешена на прерывание, что бы ресурсов не тратить (port D3)
#define PIN_IRQ_NRF24 2                          // Ножка куда заведено прерывание от NRF24 (пока не используется)

// Настройки
#define NUM_SETTING    8                        // Число вариантов настроек 
#define BLOCK_OFF      0                        // Выключено (вариант настроек)
#define HOOD_ON        1                        // Режим вытяжки (вариант настроек)
#define COOLING        2                        // Режим охлаждение (вариант настроек)

// Константы для масивов графиков
#define CHART_QUANTITY 2
#define DOTS_X_QUANTITY 120
#define DOTS_Y_QUANTITY 100
#define OUT 0
#define IN 1
#define PIT 2
#define WALL 3
#define TEMP 0
#define ABS_HUM 1
#define REL_HUM 2


//#include   "stmTime.h"     // Time library - <a href="https://github.com/PaulStoffregen/Time" rel="nofollow">https://github.com/PaulStoffregen/Time</a>
//#define TZ "UTC+3"       // Часовой пояс
#ifdef RADIO
unsigned long tt=0;        // Время пришедшее от головного блока если 0 то вроемя не приходило ничего не выводим
#endif
// АЦП ----------------------------------------
const long ConstADC=1126400;                    // Калибровка встроенного АЦП (встроенный ИОН) по умолчанию 1126400 дальше измеряем питание и смотрим на дисплей    

Ucglib_ILI9341_18x240x320_HWSPI ucg(/*cd=*/PIN_CD, /*cs=*/PIN_CS, /*reset=*/PIN_RESET); // Аппаратный SPI на дисплей ILI9341
leOS myOS;                                      // многозадачность
dht DHT;                                        // Датчики температура влажность
bool infoScreen=false;                          // Признак отображениея иформационного экрана  1 - на экран ничего не выводится кроме информационного экрана
bool flagKey=false;                             // Флаг нажатия клавиши
bool pressKey=false;                            // Флаг необходимости обработки кнопки
unsigned long time_key=0;                       // Время нажатия копки
long time_key_res_temp=-1;                      // время нажатия кнопки для сброса температур  -1 кнопка не нажата
long time_key_res_tft=-1;                       // время нажатия кнопки для сброса дисплея  -1 кнопка не нажата
byte last_error=100;                            // Предыдущая ошибка чтения датчиков
byte dH_OFF=20;                                 // Гистерезис абсолютной влажности в сотых грамма на куб - переведена в переменную как 1/4 от дельта


struct type_setting_eeprom                     // Структура для сохранения данных в eeprom
{
  byte mode = 0;                             // Какой режим работы блока
  unsigned long hour_unit=0;                 // мото часы блок измеряется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR
  unsigned long hour_motor=0;                // мото часы мотора измеряется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR
#ifdef HEATER
  unsigned long hour_heat=0;                 // мото часы нагревателя измеряется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR
#endif
  int16_t Min[SENSORS_QUANTITY], Max[SENSORS_QUANTITY];
  int16_t tick_eeprom=0;                     // Переменная для хранения времени записи статистики раз в один час хранится в NUM_SAMPLES*TIME_SCAN_SENSOR сравнение с TIME_HOUR
     
}; 
  
type_setting_eeprom settingRAM;                 // Рабочая копия счетчиков в памяти
type_setting_eeprom settingEEPROM EEMEM;        // Копия счетчиков в eeprom - туда пишем 


// Пакет передаваемый, используется также для хранения результатов. 
struct type_packet_NRF24   // Версия 2.4!! адаптация для stm32 Структура передаваемого пакета 32 байта - 32 максимум
    {
        byte DHT_error[SENSORS_QUANTITY];                         // Ошибка разряды: 0-1 первый датчик (00-ок) 2-3 второй датчик (00-ок) 4 - радиоканал
        int16_t   Measurement[3][SENSORS_QUANTITY]={{0}}; // Текущие температуры в сотых градуса !!! место экономим
        uint8_t   flags=0x00;                   // байт флагов  
                                                // 0 бит - мотор включен/выключен 
                                                // 1 бит - нагреватель включен/выключен
                                                // 2 бит -[1 - dH_min задается в сотых грамма на м*3] [0 - dH_min задается в ДЕСЯТЫХ процента от absHIn]
                                                // 3-4 - пока пусто
                                                // 5-7 - номер настройки = settingRAM.mode до 8 настроек, надо передавать, что бы на приемнике восстановить
        uint8_t   dH_min;                       // Порог включения вентилятора по РАЗНИЦЕ абсолютной влажности в сотых грамма на м*3 или в ДЕСЯТЫХ % см flags:2
        uint8_t   T_min;                        // Порог выключения вентилятора по температуре в ДЕСЯТЫХ долях градуса, только положительные значения
#ifdef RADIO
        byte id=ID;                             // Идентификатор типа устройства - старшие 4 бита, вторые (младшие) 4 бита серийный номер устройства
        uint8_t   count=0;                      // циклический счетчик отправленных пакетов нужен что бы на приемнике проверять качество связи
#endif
//        char note[14] = LABEL;                  // Примечание не более 13 байт + "0" байт Русские буквы в два раза меньше т.к. UTF-8
} packet; 

struct type_SensorError{
  int16_t Temp;
  int16_t RelHum;
} SensorError[SENSORS_QUANTITY];
    
struct type_sensors                             // структура для усреднения измерений
{
       int  num=0;                              // сколько отсчетов уже сложили не болле NUM_SAMPLES
       long Sum[2][SENSORS_QUANTITY]={{0}};
       int Measurement[3][SENSORS_QUANTITY]={{0}};
       byte Pin[SENSORS_QUANTITY];
} sensors;

// Массивы для графиков
bool Fan[DOTS_X_QUANTITY];
#ifdef HEATER
  bool Heat[DOTS_X_QUANTITY];
#endif

struct type_chart{
  int8_t Dots[SENSORS_QUANTITY][DOTS_X_QUANTITY];
  int16_t Max,                         //минимальная и максимальная границы графика
      Min,
      MinCaptionStep;    // Минимальный шаг подписей на графике - должен быть кратный 100, иначе деление на 100 будет давать неверный zoom и график будет неправльно рисоваться
  byte Zoom,
      Captions, 
      CaptionStepDots;
} Chart[CHART_QUANTITY];

uint8_t posChart=0;       // Позиция в массиве графиков - начало вывода от 0 до 120-1
uint8_t TimeChart=0;      // Время до вывода очередной точки на график. 
bool ChartMotor=false;    // Признак работы мотора во время интервала графика если мотор был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте
#ifdef HEATER
  bool ChartHeat=false;     // Признак работы нагревателя во время интервала графика если нагреватель был включен на любое время (даже одно измерение) то на графике фон меняется в этом месте
#endif

#ifdef  RADIO    // Радио модуль NRF42l  
   RF24 radio(PIN_CE, PIN_CSN);  // определение управляющих ног
   bool send_packet_ok=false;    // признак удачной отправки последнего пакета 
   unsigned long err_ask=0;      // число не полученных ответов
#endif 

#ifdef SERVO
  Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
  bool MainScreen=false;

  class class_valve{
    private:
    byte Valve_num;
    public:
    int Opened, Closed;

    public:
    void Init(byte Num, int O, int C){
      Valve_num = Num;
      Opened = O;
      Closed = C;
      Open();
    }
    
    void Open(){
      pwm.setPWM(Valve_num, 0, Opened);
      delay(500);
    }
  
    void Close(){
      pwm.setPWM(Valve_num, 0, Closed);
      delay(500);    }
  };

  class class_area{
    private:
    byte Valve_quantity;
    byte Area_num;
    public:
    class_valve *Valves;
    bool State;

    public:
    void Init(byte n, byte q){
      Valves = (class_valve *) malloc(q * sizeof(class_valve));
      Valve_quantity = q;
      Area_num = n;
    }    

    void Open(){
      for (byte i=0; i<Valve_quantity; i++)
        Valves[i].Open();    
      State=true;
      UpdateScreen();
    }
  
    void Close(){
      for (byte i=0; i<Valve_quantity; i++)
        Valves[i].Close();    
      State=false;
      UpdateScreen();
    }

    void UpdateScreen(){
      if (MainScreen){
        if(State)
          ucg.setColor(0, 200, 0);
        else
          ucg.setColor(200, 0, 0);
        ucg.drawHLine(211-55*Area_num, 48, 54);      
      }
    }
  } Area[SENSORS_QUANTITY-1];
#endif

///////////////////////////////////////////////////////////////////////////////////////////////////////////
// ПРОГРАММА
///////////////////////////////////////////////////////////////////////////////////////////////////////////

void setup(){
#if defined(DEBUG) || defined(DEBUG2) || defined(DEBUG3)
   Serial.begin(9600);
 #ifdef  DEBUG
   Serial.println(F("DEBUG MODE")); 
   #ifdef  BEEP  
     Serial.println(F("BEEP ON")); 
   #else         
     Serial.println(F("BEEP OFF"));
   #endif
   #ifdef  RADIO 
     Serial.println(F("RADIO ON")); 
   #else         
     Serial.println(F("RADIO OFF"));
   #endif
 #endif
#endif 

reset_sum();

#ifdef  RADIO    // Радио модуль NRF42l  первичная настройка
   radio.begin();
   radio.setDataRate(RF24_250KBPS);         //  выбор скорости RF24_250KBPS RF24_1MBPS RF24_2MBPS
   radio.setPALevel(RF24_PA_MAX);           // выходная мощность передатчика
   radio.setChannel(NRF24_CHANEL);          //тут установка канала
   radio.setCRCLength(RF24_CRC_16);         // использовать контрольную сумму в 16 бит
   radio.setAutoAck(true);                  // выключить аппаратное потверждение
// radio.enableDynamicPayloads();           // разрешить Dynamic Payloads
   radio.enableAckPayload();                // Разрешить ответ приемника AckPayload
   radio.setRetries(50,10);                  // Количество пауза и количество повторов 
  // Рекомендуют первые 2-4 байта адреса устанавливать в E7 или 18 он проще детектируется чипом
  radio.openWritingPipe(0xE7E7E7E7E1LL);    // передатчик 
  radio.openReadingPipe(1,0xE7E7E7E7D2LL);  // приемник
  radio.startListening();
#endif
  #ifdef BEEP
     SetOutput(C,1);                      //  Настройка ноги для динамика
     WritePort(C,1,LOW);
  #endif
  SetInput(D,3);                        //  Включена кнопка
  WritePort(D,3,HIGH);
  SetOutput(C,0);                       //  Подключить Реле вентилятора
  WritePort(C,0,LOW);
  SetOutput(C,3);                       //  Подключить Калорифер
  WritePort(C,3,LOW);
#ifdef SERVO
  SetOutput(C,2);                       //  Подключить реле переключения скоростей вентилятора
  WritePort(C,2,LOW);
#endif

  sensors.Pin[OUT]=PIN_DHT22a;
  sensors.Pin[IN]=PIN_DHT22b;
#if SENSORS_QUANTITY >= 3
  sensors.Pin[PIT]=PIN_DHT22c;
#endif
#if SENSORS_QUANTITY >= 4  
  sensors.Pin[WALL]=PIN_DHT22d;
#endif

  for (byte j=0; j<SENSORS_QUANTITY; j++){
    pinMode(sensors.Pin[j], OUTPUT);          
    digitalWrite(sensors.Pin[j], HIGH);
  }

  SensorError[IN].Temp=TIN_ERR;
  SensorError[IN].RelHum=HIN_ERR;
  SensorError[OUT].Temp=TOUT_ERR;
  SensorError[OUT].RelHum=HOUT_ERR;

#if SENSORS_QUANTITY >= 3
  SensorError[PIT].Temp=TPIT_ERR;
  SensorError[PIT].RelHum=HPIT_ERR;
#endif
#if SENSORS_QUANTITY >= 4   
  SensorError[WALL].Temp=TWALL_ERR;
  SensorError[WALL].RelHum=HWALL_ERR;
#endif
  
  Chart[TEMP].Max=2000;
  Chart[TEMP].Min=-2000;
  Chart[ABS_HUM].Max=2000;
  Chart[ABS_HUM].Min=0;
  Chart[TEMP].MinCaptionStep=500;     // Минимальный шаг подписей на графике - должен быть кратный 100, иначе деление на 100 будет давать неверный zoom и график будет неправльно рисоваться
  Chart[ABS_HUM].MinCaptionStep=100;  // Минимальный шаг подписей на графике - должен быть кратный 100, иначе деление на 100 будет давать неверный zoom и график будет неправльно рисоваться
  for (byte i=0; i<CHART_QUANTITY; i++){
    for (byte j=0; j<SENSORS_QUANTITY; j++)
      for (byte k=0; k<DOTS_X_QUANTITY; k++)
        Chart[i].Dots[j][k]=-128;
    Chart[i].Zoom=(Chart[i].Max-Chart[i].Min)/DOTS_Y_QUANTITY;
  }
  for (byte k=0; k<DOTS_X_QUANTITY; k++){
    Fan[k]=false;
#ifdef HEATER
    Heat[k]=false;
#endif
}

  for (byte j=0; j<SENSORS_QUANTITY; j++){
    settingRAM.Max[j]=-3500;
    settingRAM.Min[j]=3500;
  }
  reset_ili9341();                      // сброс дисплея
  readEeprom();                         // Прочитать настройки 
  byte tmp=ReadPort(D,3);
  if (tmp==0){                          // Если при включении нажата кнопка то стираем Eeprom  
    settingRAM.mode=0;
    settingRAM.hour_unit=0;
    settingRAM.hour_motor=0;
#ifdef HEATER
    settingRAM.hour_heat=0;
#endif
    for (byte j=0; j<SENSORS_QUANTITY; j++){
      settingRAM.Max[j]=-3500;
      settingRAM.Min[j]=3500;
    }
    ucg.setColor(255, 255, 255);
    print_StrXY(10,50,F("Сброс настроек и счетчиков"));
    for(byte i=0;i<3;i++){
      delay(1000);
      ucg.print(F(" ."));
    }
    writeEeprom();       // Запись в EEPROM  
    delay(1000);
    ucg.clearScreen();
  }  
#ifdef SERVO
  pwm.begin();
  pwm.setPWMFreq(60);  // Analog servos run at ~60 Hz updates

  Area[0].Init(0, 1);    // Задаём количество серво-клапанов в каждой зоне
  Area[1].Init(1, 2);

  // Клапаны гаража
  Area[0].Valves[0].Init(0, 100, 335);  // Настраиваем соответсвие между клапанами в зонах и выводами на плате драйвера, настраиваем положение "Открыто" и "закрыто" для каждого клапана
  // Клапаны ямы
  Area[1].Valves[0].Init(1, 100, 335);
  Area[1].Valves[1].Init(2, 100, 335);

  for(byte i=0; i<SENSORS_QUANTITY-1; i++){
    Area[i].Open();}
#endif

#ifndef  DEBUG3
  boot_test();         // Тестирование исполнительных устройств
#endif
#ifdef SERVO
  MainScreen = true;
#endif
  wdt_enable(WDTO_8S); // Сторожевой таймер Для тестов не рекомендуется устанавливать значение менее 8 сек.
  // Запуск задач по таймеру
  myOS.begin();
  myOS.addTask(measurement,TIME_SCAN_SENSOR);    // Измерение 
  attachInterrupt(1, scanKey, CHANGE);           // КНОПКА Прерывания по обоим фронтам
  print_static();                                // распечатать таблицу
  packet.Measurement[TEMP][IN]=TEMP_LOW+1;       // Для исключения включения обогревателя до измерений
#if SENSORS_QUANTITY >= 3
  packet.Measurement[TEMP][PIT]=TEMP_LOW+1;       // Для исключения включения обогревателя до измерений
#endif
  Setting();                                     // Применить настройки
  measurement();                                 // Считать данные
  #ifdef BEEP
    beep(300);
    delay(300);
    beep(600);
  #endif
  
  resetKey();
}

void loop()
{
  wdt_reset();                        // Сбросить сторожевой таймер
// Обработка нажатия кнопки
// 1. Высший приоритет - сброс контроллера дисплея если он зависнeт - держать долго сброс пройдет отпустить кнопку
  if ((flagKey==true)&&(pressKey!=true)){
    if (time_key_res_tft==-1) time_key_res_tft=millis();     // первый вход - нажата кнопка запомнить время нажатия
    else if (millis()>time_key_res_tft+RESET_TFT_KEY){     // кнопка нажата более RESET_TFT_KEY сек сбрасываем дисплей
      reset_ili9341();               // сброс дисплея 
      ucg.setColor(255, 255, 255);
      print_StrXY(10,100,F("Сброс дисплея"));
      print_StrXY(10,129,F("Кнопку нужно отпустить"));
      delay(1000);
      reset_ili9341();               // сброс дисплея   
      print_static();                // распечатать таблицу
      Setting();                     // Применить настройки
      print_data();                  // вывод усредненных значений 
      print_status();                // панель состояния
      printChart();                  // показать график
      infoScreen=false;              // нет информационного экрана
#ifdef BEEP
      beep(400);                  // Звук нажатия на клавишу
#endif
      resetKey();
    }  
  }
  // 2. Второй приоритет - сброс памяти температур работает только в информационном экране
#ifdef INFOSCREEN
  if ((flagKey==true)&&(pressKey!=true)&&(infoScreen==true)) // Во время информационнного экрана нажата кнопка - подготовка и стирание температур min max
  {  
    if (time_key_res_temp==-1) time_key_res_temp=millis(); // первый вход - нажата кнопка для стирания температур 
    else if (millis()>time_key_res_temp+RESET_TEMP_KEY){   // кнопка нажата более RESET_TEMP_KEY сек сбрасываем температуры
      for (byte j=0; j<SENSORS_QUANTITY; j++){
        settingRAM.Max[j]=3500;
        settingRAM.Min[j]=-3500;
      }
      writeEeprom();                                 // Сбросили температуры сохраняем в EEPROM  
#ifdef BEEP
      beep(500);                                // Звук нажатия на клавишу
#endif
      printInfo();                                   // Обновить экран с новыми температурами
      resetKey();
    }
  } 
#endif
  else  // 3. Остальной обработчик нажатия на кнопок - Кнопку надо нажать и отпустить
  if ((flagKey!=false)&&(pressKey==true)){   // Кнопка была нажата и отпущена
  //  if (flagKey!=false)                    // Обработчик кнопки
#ifdef INFOSCREEN
    if ((time_key>=LONG_KEY)&&(infoScreen!=true))  // Длительное нажатие кнопки и информационный экран не показан
       printInfo();                                 // Вывод информационного экрана
    else if (time_key >= SHORT_KEY){                // Короткое нажатие кнопки все остальное игнорируется
#endif
#ifdef BEEP
      beep(30);                             // Звук нажатия на клавишу
#endif
#ifdef INFOSCREEN
      if (infoScreen==true)  clearInfo();                    // если информационный экран показан то стереть его
      else {
#endif
        if (settingRAM.mode >= (NUM_SETTING-1) ) settingRAM.mode=0; //  Кольцевой счетчик настроек
        else settingRAM.mode++;                      // В противном случае следующая настройка
      Setting(); 
#ifdef INFOSCREEN
      }  
    }
#endif
    resetKey();                                                //  Сброс состояния кнопки
  }
}

void resetKey(void)  // Сброс состояния кнопки
{
  flagKey=false;                                             
  pressKey=false;
  time_key_res_temp=-1;   // сброс состояния сброса температур
  time_key_res_tft=-1;    // сброс состояния сброса дисплея
}  

void print_static()  // Печать статической картинки 
{  
  byte i, j;
  cli();
  // Заголовок 
  ucg.setColor(0, 0, 180); 
  ucg.drawBox(0, 0, 320-1, 23);
//  ucg.setColor(250, 250, 250);
//  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
//  print_StrXY(2,19,F("Управление климатом гаража"));
  //ucg.print( hex(packet.id >> 4));
  //ucg.print( hex(packet.id&0x0f));
   
  #ifdef DEMO
    ucg.print(F(" demo"));
  #endif
  
  // Таблица для данных
  ucg.setColor(0, 200, 0);
  for (i=0;i<5;i++)
    ucg.drawHLine(0,25+23*i,320-1);
  for (i=0;i<SENSORS_QUANTITY;i++)
    ucg.drawVLine(265-i*55, 25, 24+23*3);

  // Заголовки таблиц
  ucg.setColor(255, 255, 0);

  // В зависимости от id разные надписи - привязка местоположения блока к ID
//  ucg.setPrintPos(180+30-9,25+0+18); 
//  ucg.print(packet.note); 
  print_StrXY(250+20,25+0+18,F("Улица"));
  print_StrXY(215,25+0+18,F("Гараж"));
#if SENSORS_QUANTITY >= 3
  print_StrXY(160,25+0+18,F("Яма"));
#endif
#if SENSORS_QUANTITY >= 4   
  print_StrXY(105,25+0+18,F("Стены"));
#endif

  print_StrXY(0,25+23*1+18,F("Темп. *C")); 
  print_StrXY(0,25+23*2+18,F("Отн. вл. %")); 
  print_StrXY(0,25+23*3+18,F("Абс.вл. г/м3")); 

  // Графики
  ucg.setColor(210, 210, 210);
//  ucg.drawHLine(1,240-2,130);
//  ucg.drawVLine(1,135,105);
//  ucg.drawHLine(10+154,240-2,130);
//  ucg.drawVLine(10+154,135,105);
  print_StrXY(7,135+0,F("Температура")); 
  print_StrXY(7+162,135+0,F("Абс. влажность")); 
   
  // надписи на графиках
  for(i=0;i<CHART_QUANTITY;i++){
    print_chart_axes(i);
    // Горизонтальная шкала по часам
    for(j=0;j<=DOTS_X_QUANTITY;j+=12){
      ucg.drawPixel(5+DOTS_X_QUANTITY-j+162*i,238);
      ucg.drawPixel(5+DOTS_X_QUANTITY-j+162*i,239);
    }
  }
#ifdef SERVO
  for(i=0; i<SENSORS_QUANTITY-1; i++)
    Area[i].UpdateScreen();
#endif    
}

void print_status(void) // Печать панели статуса Значки на статус панели
{
  if (infoScreen==true) return;        // если отображен информационный экран то ничего не выводим  
   cli();
 // 1. печать ошибки чтения датчиков
   print_error_DHT();
 // 2. Признак включения мотора или нагревателя
  if (FLAG_MOTOR_CHECK)         ucg.setColor(0, 240, 0); 
#ifdef HEATER
  else if (FLAG_HEAT_CHECK)     ucg.setColor(240, 0, 0);
#endif
  else                          ucg.setColor(0, 40, 0);
  ucg.drawBox(290-32, 5, 14, 14);

#ifdef SERVO
  if (FLAG_MOTOR_CHECK){
    ucg.setColor(0, 40, 0);
    ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
    if(MotorFastSpeed){
      print_StrXY(290-30,19,F("2"));
    }else{
      print_StrXY(290-30,19,F("1"));
    }
  }
#endif
  
  #ifdef  RADIO 
  byte temp;
  // 3. Признак удачной передачи информации по радиоканалу - получение квитанции
      if (send_packet_ok==true)  ucg.setColor(0, 240, 0); 
      else                       ucg.setColor(0, 40, 0);
      ucg.setPrintDir(3);
      ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
      print_StrXY(290-40,20,F(">>")); 
      ucg.setPrintDir(0);
     // ucg.setFontMode(UCG_FONT_MODE_SOLID);
  // 4.  Вывод времени на экран
  if (tt>0) // выводим если было первоначальная установка от головного блока 
     {
      ucg.setColor(0, 0, 180);  // 
      ucg.drawBox(180, 0, 45, 23);
      ucg.setColor(250, 250, 0);
      ucg.setPrintPos(180,19);
      temp=hour(tt);
      if (temp<10) ucg.print("0");
      ucg.print(temp);
      ucg.print(":");
      temp=minute(tt);
      if (temp<10) ucg.print("0");
      ucg.print(temp);
     }
  #endif
  sei();
}  

void print_error_DHT(void) // Печать ошибки чтения датчиков выводится при каждом чтении датчика
{
  if (infoScreen==true) return;        // если отображен информационный экран то ничего не выводим  
 // 1. печать ошибки чтения датчиков
  for (byte j=0; j<SENSORS_QUANTITY; j++){
    if (packet.DHT_error[j]!=last_error) {      // если статус ошибки поменялся то надо вывести если нет то не выводим - экономия время и нет мерцания
      cli();
      last_error=packet.DHT_error[j]; 
      ucg.setColor(0, 0, 180);         // Сначала стереть
      ucg.drawBox(280, 0, 36, 18);
      ucg.setPrintPos(280,18); 
      ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
      if (packet.DHT_error[j]>0) { 
        ucg.setColor(255, 100, 100); 
        print_StrXY(280,18,F("0x"));
        ucg.print( hex(packet.DHT_error[j] >> 4));
        ucg.print( hex(packet.DHT_error[j] & 0x0f));       
      }
      else  { ucg.setColor(200, 240, 0);   ucg.print(F(" ok ")); } 
    sei(); 
    }   
  }
} 

//  вывод на экран данных (то что меняется)
void print_data()
{ 
  byte y=66, dx=55, dy=23;
  int16_t x=270;
  // Статистика по моточасам, время ведется в интервалах вывода данных = NUM_SAMPLES*TIME_SCAN_SENSOR а потом пересчитывается в часы при выводе.
  settingRAM.hour_unit++;
  if (FLAG_MOTOR_CHECK) settingRAM.hour_motor++;  // если мотор включен
#ifdef HEATER
  if (FLAG_HEAT_CHECK)  settingRAM.hour_heat++;   // если нагреватель включен
#endif
  
  if (infoScreen==true) return;                  // если отображен информационный экран то ничего не выводим  
  cli();
 // Печать значений 
  ucg.setFontMode(UCG_FONT_MODE_SOLID);
  for (byte j=0; j<SENSORS_QUANTITY; j++){
    switch (j){
      case 0:  
        ucg.setColor(0, 250, 100);  // Цвет УЛИЦЫ
        break;
      case 1:
        ucg.setColor(250, 0, 100);  // Цвет ДОМА
        break;
      case 2:
        ucg.setColor(0, 0, 255);    // Цвет ЯМЫ
        break;
      case 3:
        ucg.setColor(255, 255, 0);  // Цвет СТЕН
    }
    print_floatXY(x-j*dx, y+0*dy,((float)packet.Measurement[TEMP][j])/100);
    print_floatXY(x-j*dx, y+1*dy,((float)packet.Measurement[REL_HUM][j])/100);
    print_floatXY(x-j*dx, y+2*dy,((float)packet.Measurement[ABS_HUM][j])/100);
  }
  sei();
}  

void print_chart_axes(byte ChartNum){
  int16_t j, CaptionStepDots, CaptionStep, Caption;
  uint8_t x=132, y=237, dx=162;

  ucg.setColor(0, 0, 0); 
  ucg.drawBox(x+ChartNum*dx, 130, 35, 110);
  
  ucg.setColor(210, 210, 210);
  ucg.setPrintPos(x+ChartNum*dx, y+3);
  if (Chart[ChartNum].Min%100==0)
    ucg.print(Chart[ChartNum].Min/100);
  else 
    ucg.print((float)Chart[ChartNum].Min/100, 1);  

  Chart[ChartNum].Captions = (Chart[ChartNum].Max-Chart[ChartNum].Min)/Chart[ChartNum].MinCaptionStep;
  while (Chart[ChartNum].Captions>4){
    if (Chart[ChartNum].Captions>4)
      Chart[ChartNum].Captions/=2;
  }
  CaptionStep=(int)(( (Chart[ChartNum].Max-Chart[ChartNum].Min)/(Chart[ChartNum].Captions) )/Chart[ChartNum].MinCaptionStep)*Chart[ChartNum].MinCaptionStep;
  Chart[ChartNum].CaptionStepDots=CaptionStep/Chart[ChartNum].Zoom;
  for(j=y+8-Chart[ChartNum].CaptionStepDots, Caption=Chart[ChartNum].Min+CaptionStep; j>=y+8-DOTS_Y_QUANTITY; j-=Chart[ChartNum].CaptionStepDots, Caption+=CaptionStep){
    ucg.setPrintPos(x+ChartNum*dx, j);
    if (Caption%100==0)
      ucg.print(Caption/100);
    else 
      ucg.print((float)Caption/100, 1);
  }
}

// Печать графика на экране, добавляется одна точка и график сдвигается 
void printChart() 
{
  byte i, j, k, y=237, yi, dx=162, xi, Zoom_old;
  int16_t delta_Chart=0, Min_tmp=0, Max_tmp=0, x=5+DOTS_X_QUANTITY;
//  ChartMotor=false;
//  ChartHeat=false;
// ----------------- занесение новых значений в массив значений графиков и масштабирование всех значени если графики выходят за пределы
  for (i=0; i<CHART_QUANTITY; i++){
    for (j=0; j<SENSORS_QUANTITY; j++)        //Переводим значение в единицах сенсаора в значение в точках графика
      // Работаем через кольцевой буфер, Добавить новую точку в кольцевой буфер
      if ( (int)((packet.Measurement[i][j]-Chart[i].Min)/Chart[i].Zoom) > 127) //защита от переполнения
        Chart[i].Dots[j][posChart]=127;
      else
        Chart[i].Dots[j][posChart]=(packet.Measurement[i][j]-Chart[i].Min)/Chart[i].Zoom;
    //Ищем в массивах данных минимум и максимум
    Max_tmp=Chart[i].Dots[TEMP][posChart];
    Min_tmp=Chart[i].Dots[TEMP][posChart];
    for (k=0; k<DOTS_X_QUANTITY; k++)
      for (j=0; j<SENSORS_QUANTITY; j++){
        if ( (Chart[i].Dots[j][k]>-128) && (Chart[i].Dots[j][k]>Max_tmp) ) 
          Max_tmp=Chart[i].Dots[j][k];
        if ( (Chart[i].Dots[j][k]>-128) && (Chart[i].Dots[j][k]<Min_tmp) ) 
          Min_tmp=Chart[i].Dots[j][k];
      }
      
    // Вычисляем границы графика, кратные MinCaptionStep 
    if (Min_tmp*Chart[i].Zoom+Chart[i].Min<0)     // поправка минимума для отрицательных значений графика
      Min_tmp=(int16_t)((Min_tmp*Chart[i].Zoom+Chart[i].Min)/Chart[i].MinCaptionStep)*Chart[i].MinCaptionStep-Chart[i].MinCaptionStep;
    else 
      Min_tmp=(int16_t)((Min_tmp*Chart[i].Zoom+Chart[i].Min)/Chart[i].MinCaptionStep)*Chart[i].MinCaptionStep;
      
    if (Max_tmp*Chart[i].Zoom+Chart[i].Min<0)     // поправка максимума для отрицательных значений графика
      Max_tmp=(int16_t)((Max_tmp*Chart[i].Zoom+Chart[i].Min)/Chart[i].MinCaptionStep)*Chart[i].MinCaptionStep;
    else
      Max_tmp=(int16_t)((Max_tmp*Chart[i].Zoom+Chart[i].Min)/Chart[i].MinCaptionStep)*Chart[i].MinCaptionStep+Chart[i].MinCaptionStep;

#ifdef DEBUG3
  Serial.print("\nMin_tmp="); Serial.print(Min_tmp); Serial.print(" Max_tmp="); Serial.print(Max_tmp); 
#endif

    // Если границы графика сдвинулись - пересчитать значения в таблицах графиков
    if ((Min_tmp!=Chart[i].Min)||(Max_tmp!=Chart[i].Max)){
      Zoom_old=Chart[i].Zoom;
      Chart[i].Zoom=(Max_tmp-Min_tmp)/DOTS_Y_QUANTITY;
      for(k=0;k<DOTS_X_QUANTITY;k++){
        for (j=0; j<SENSORS_QUANTITY; j++)
          if(Chart[i].Dots[j][k]>-128)
            Chart[i].Dots[j][k] = (Chart[i].Dots[j][k] * Zoom_old + Chart[i].Min - Min_tmp) / Chart[i].Zoom;
      }
      Chart[i].Max=Max_tmp;
      Chart[i].Min=Min_tmp;
      print_chart_axes(i);
    }
  }
  
  if (ChartMotor) Fan[posChart]=true; else Fan[posChart]=false;
#ifdef HEATER
  if (ChartHeat) Heat[posChart]=true; else Heat[posChart]=false;
#endif

  //Отрисовка графиков - начало
  if (infoScreen==false){                 // если отображен информационный экран то ничего не выводим
    cli();  
    for (i=0; i<CHART_QUANTITY; i++){
      for (xi=0; xi<DOTS_X_QUANTITY; xi++){
        // Вычислить координаты текущей точки в кольцевом массиве значений. Изменяются от 0 до 120-1
        if (posChart<xi) k=DOTS_X_QUANTITY+posChart-xi; else k=posChart-xi;
        // нарисовать фон в зависимости от статуса мотора
#ifdef HEATER
        if (Heat[k]==true) // Heat
          ucg.setColor(90, 60, 0);     // Нагреватель был ключен - бледно желтый
        else 
#endif
        if (Fan[k]==true) 
          ucg.setColor(0, 60, 90);     // Мотор был ключен - бледно синий
        else
          ucg.setColor(0, 0, 0);       // все выключено
        ucg.drawVLine(x-xi+dx*i, y-DOTS_Y_QUANTITY, DOTS_Y_QUANTITY); 
        ucg.setColor(180, 180, 180);  
        if (xi%6==0) // Пунктирные линии графика - каждые 6 пикселей (т.е. каждые 30 минут)
          for(yi=0; yi<=DOTS_Y_QUANTITY; yi+=Chart[i].CaptionStepDots)
            ucg.drawPixel(x-xi+dx*i,y-yi);

        // Вывод точки нужного цвета на график
        for (j=0; j<SENSORS_QUANTITY; j++){
          switch (j){
            case 0:  
              ucg.setColor(0, 250, 100);  // Цвет УЛИЦЫ
              break;
            case 1:
              ucg.setColor(250, 0, 100);  // Цвет ДОМА
              break;
            case 2:
              ucg.setColor(0, 0, 255);    // Цвет ЯМЫ
              break;
            case 3:
              ucg.setColor(255, 255, 0);  // Цвет СТЕН
          }
          ucg.drawPixel(x-xi+dx*i,y-Chart[i].Dots[j][k]);
        }
      }
    }
    sei();
  }
  //Отрисовка графиков - конец
     
#if defined(DEBUG) //|| defined(DEBUG2)
// Serial.print(F("\nPit  T="));Serial.print((float)packet.tPit/100.0);Serial.print(F(" H=")); Serial.print((float)packet.relHPit/100.0); Serial.print(F("% abs H=")); Serial.println((float)packet.absHPit/100.0);
// Serial.print(F("\npacket.tIn="));Serial.print((float)packet.tPit);Serial.print(F(" tMinChart=")); Serial.print((float)tMinChart); Serial.print(F(" tMaxChart=")); Serial.print((float)tMaxChart); Serial.print(F(" tchart_zoom=")); Serial.println((float)tchart_zoom); Serial.print(F(" delta_tChart=")); Serial.println(delta_tChart);

//    Serial.print("\n\ntIn="); Serial.print(packet.Measurement[TEMP][IN]); Serial.print(" tOut="); Serial.print(packet.Measurement[TEMP][OUT]); 
//    Serial.print(" tPit="); Serial.print(packet.Measurement[TEMP][PIT]); Serial.print(" tWall="); Serial.print(packet.Measurement[TEMP][WALL]);
    Serial.print("\nChart[TEMP].Min="); Serial.print(Chart[TEMP].Min); Serial.print(" Chart[TEMP].Max="); Serial.print(Chart[TEMP].Max); Serial.print(" Chart[TEMP].Zoom="); Serial.print(Chart[TEMP].Zoom); Serial.print(" Chart[TEMP].CaptionStepDots="); Serial.print(Chart[TEMP].CaptionStepDots);

//  Serial.print("\nMax: "); Serial.print(settingRAM.tOutMax); Serial.print(" "); Serial.print(settingRAM.tInMax); Serial.print(" "); Serial.print(settingRAM.tPitMax); Serial.print(" "); Serial.print(settingRAM.tWallMax);
//  Serial.print("\nMin: "); Serial.print(settingRAM.tOutMin); Serial.print(" "); Serial.print(settingRAM.tInMin); Serial.print(" "); Serial.print(settingRAM.tPitMin); Serial.print(" "); Serial.print(settingRAM.tWallMin);
// Serial.print(F("tInChart[posChart]="));Serial.print(tInChart[posChart]);
// Serial.print(F(" tOutChart[posChart]="));Serial.print(tOutChart[posChart]);
/// Serial.print(F(" tPitChart[posChart]="));Serial.print(tPitChart[posChart]);
// Serial.print(F(" tWallChart[posChart]="));Serial.print(tWallChart[posChart]);
// beep(100);
#endif
  if (posChart<120-1) posChart++; else posChart=0;            // Изменили положение в буфере и Замкнули буфер
} 

// ---ПЕРЕДАЧА ДАННЫХ ЧЕРЕЗ РАДИОМОДУЛЬ -----------------------------
#ifdef  RADIO    // Радио модуль NRF42l
void send_packet()
{       radio.powerUp();
        radio.stopListening();     // Остановить приемник
        delay(2);
        cli();
          radio.writeBlocking(&packet,sizeof(packet),200);  // Writes 1 payload to the buffers
          send_packet_ok=radio.txStandBy();
          if ( radio.isAckPayloadAvailable() )   // Ждем получения -- основной блок передает текущее время
          radio.read(&tt,sizeof(tt));            //... и имеем переменную tt с временем от приемника.
        sei(); 
         if (send_packet_ok!=true) err_ask++;            // квитанция не получена добавить счетчик
         #ifdef BEEP
           if (send_packet_ok==true) beep(30);           // Пакет передан успешно
         #endif
         #ifdef  DEBUG  
           if (send_packet_ok==true)  Serial.println(F("Packet sending ++++++++++"));
           else                       Serial.println(F("Packet NOT sending -----------"));
         #endif   
        radio.startListening();    // Включить приемник
        packet.count++;            // при переполнении сам сбросится
 }  
#endif 

// Чтение датчика возвращает код ошибки:
// DHTLIB_OK                   0
// DHTLIB_ERROR_CHECKSUM       1
// DHTLIB_ERROR_TIMEOUT        2
// DHTLIB_ERROR_CONNECT        3
// DHTLIB_ERROR_ACK_L          4
// DHTLIB_ERROR_ACK_H          5
byte readDHT(byte pin)
{
//delay(5);
cli();
  byte err=-1*DHT.read22(pin); // Чтение датчика
sei();  
return err; 
} 

// Измерение и обработка данных чтение датчиков --------------------------------------------------------------------------------
void measurement()
{ bool Error=false;
  byte j;
  myOS.pauseTask(measurement);        // Обязательно здесь, а то датчики плохо читаются мешает leos
  wdt_reset();                        // Сбросить сторожевой таймер
 
#ifdef DEBUG
  Serial.print(F("Sensor read samples:")); Serial.println(sensors.num); 
#endif       
  for (j=0; j<SENSORS_QUANTITY; j++){
    packet.DHT_error[j]=readDHT(sensors.Pin[j]);
    if(packet.DHT_error[j]!=0) Error=true;
#ifdef  DEMO
    DHT.temperature=packet.Measurement[TEMP][j]/100+random(-20,30)/10.0; 
    if (DHT.temperature>20) DHT.temperature=19;
    if (DHT.temperature<-10) DHT.temperature=-9;
    DHT.humidity=packet.Measurement[REL_HUM][j]/100+(float)random(-5,8);
    if (DHT.humidity>96) DHT.humidity=90;
    if (DHT.humidity<1) DHT.humidity=10;
    packet.DHT_error[j]=0; // в Демо режиме
#endif       
    sensors.Measurement[TEMP][j]=(int)(DHT.temperature*100.0)+SensorError[j].Temp;  // Запомнить результаты для суммирования с учетом ошибок
    sensors.Measurement[REL_HUM][j]=(int)(DHT.humidity*100.0)+SensorError[j].RelHum;  
#ifdef DEBUG
    Serial.print(F("Sensor #"));Serial.print(j);Serial.print(F(": T="));Serial.print((float)sensors.Measurement[TEMP][j]/100);Serial.print(F(" H=")); Serial.print((float)sensors.Measurement[REL_HUM][j]/100); Serial.print(F("% error=")); Serial.println(packet.DHT_error[j]);
#endif
  }
  print_error_DHT();          // Вывод ошибки чтения датчика при каждом чтении контроль за качеством связи с датчиками
  
//  if (!Error){                 // Если чтение без ошибок у ВСЕХ датчиков  копим сумму для усреднения
  for (j=0; j<SENSORS_QUANTITY; j++){
    if (packet.DHT_error[j] == 0){                 // Если чтение без ошибок у ВСЕХ датчиков  копим сумму для усреднения
      sensors.Sum[0][j]=sensors.Sum[0][j]+sensors.Measurement[TEMP][j];
      sensors.Sum[1][j]=sensors.Sum[1][j]+sensors.Measurement[REL_HUM][j];
    }
  }
  sensors.num++;

  if (sensors.num>=NUM_SAMPLES){        // Пора усреднять и выводить значения 
    for (j=0; j<SENSORS_QUANTITY; j++){
      // вычисление средних значений
      packet.Measurement[TEMP][j]=sensors.Sum[0][j]/NUM_SAMPLES;
      packet.Measurement[REL_HUM][j]=sensors.Sum[1][j]/NUM_SAMPLES;
      packet.Measurement[ABS_HUM][j]=(int)(calculationAbsH((float)(packet.Measurement[TEMP][j]/100.0), (float)(packet.Measurement[REL_HUM][j]/100.0))*100.0);
    }
    reset_sum();                        // Сброс счетчиков и сумм
#if defined(DEBUG)// || defined(DEBUG2)
    Serial.println(F("\nAverage value >>>>>>>>>>"));
    Serial.print(F("IN   T="));Serial.print((float)packet.Measurement[TEMP][IN]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][IN]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][IN]/100.0);
    Serial.print(F("OUT  T="));Serial.print((float)packet.Measurement[TEMP][OUT]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][OUT]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][OUT]/100.0);
#if SENSORS_QUANTITY >= 3
    Serial.print(F("Pit  T="));Serial.print((float)packet.Measurement[TEMP][PIT]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][PIT]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][PIT]/100.0);
#endif
#if SENSORS_QUANTITY >= 4   
    Serial.print(F("Wall T="));Serial.print((float)packet.Measurement[TEMP][WALL]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][WALL]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][WALL]/100.0);
#endif
    Serial.println(F("Average value <<<<<<<<<<<\n"));
#endif  

    CheckON();                                // Проверка статуса вентилятора и включение
    print_data();                             // вывод усредненных значений 
    print_status();                           // панель состояния
    
    // Обновляем максимум и минимум температур в EEPROM
    // Cделано не честно - обновляем раз в 30 секунд (NUM_SAMPLES*TIME_SCAN_SENSOR) а пишем раз в час, могут пропасть данные при сбросе
    for (j=0; j<SENSORS_QUANTITY; j++){
      if (settingRAM.Max[j] < packet.Measurement[TEMP][j]) settingRAM.Max[j]=packet.Measurement[TEMP][j];
      else if (settingRAM.Min[j] > packet.Measurement[TEMP][j]) settingRAM.Min[j]=packet.Measurement[TEMP][j];
    }

    // Запись статистики в EEPROM
    // Пишем в EEPROM не каждый раз!! экономим ресурс записей 100000 мин.
    settingRAM.tick_eeprom++;
    if (((long)settingRAM.tick_eeprom*NUM_SAMPLES*TIME_SCAN_SENSOR)>=(long)TIME_HOUR){ // пора писать eeprom
      writeEeprom();  
      settingRAM.tick_eeprom=0;  
    } 

    if (FLAG_MOTOR_CHECK) ChartMotor=true; else ChartMotor=false;   // Признак того что надо показывать включение мотора на графике
#ifdef HEATER
    if (FLAG_HEAT_CHECK) ChartHeat=true; else ChartHeat=false;      // Признак того что надо показывать включение нагревателя на графике 
#endif
         
    TimeChart++;
    if ((long)((long)TimeChart*TIME_SCAN_SENSOR*NUM_SAMPLES)>=(long)TIME_PRINT_CHART){ // проврека не пора ли выводить график
      printChart(); 
      TimeChart=0; // Сдвиг графика и вывод новой точки
#if defined(DEBUG) || defined(DEBUG2)
      Serial.println(F("\nAverage value >>>>>>>>>>"));
      Serial.print(F("IN   T="));Serial.print((float)packet.Measurement[TEMP][IN]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][IN]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][IN]/100.0);
      Serial.print(F("OUT  T="));Serial.print((float)packet.Measurement[TEMP][OUT]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][OUT]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][OUT]/100.0);
  #if SENSORS_QUANTITY >= 3
      Serial.print(F("Pit  T="));Serial.print((float)packet.Measurement[TEMP][PIT]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][PIT]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][PIT]/100.0);
  #endif
  #if SENSORS_QUANTITY >= 4   
      Serial.print(F("Wall T="));Serial.print((float)packet.Measurement[TEMP][WALL]/100.0);Serial.print(F(" H=")); Serial.println((float)packet.Measurement[REL_HUM][WALL]/100.0); //Serial.print(F("% abs H=")); Serial.println((float)packet.Measurement[ABS_HUM][WALL]/100.0);
  #endif
      Serial.println(F("\n\n +++ Point add chart +++\n\n"));
#endif  
#ifdef BEEP
//      beep(50);
#endif
    } 
  }
#ifdef  RADIO       // Радио модуль NRF42l 
  // Часы работают если только был получено время от головного блока (хотя бы один раз) в противном случае они даже не показываются на экране
  if (tt>0) tt=tt+TIME_SCAN_SENSOR/1000;       // Обновить текущее время - что бы часы шли и без связи с базовым блоком  
#endif 
  myOS.restartTask(measurement);     // Пустить задачи
}


#ifdef SERVO
// Проверка статуса вытяжки, не пора ли переключится
void CheckON(void)
{
  int tmp=0;
  byte i, ClosedAreas=0, Need4Motor;
// 0.  Проверить замораживание подвала НАГРЕВАТЕЛЬ 
#if SENSORS_QUANTITY >= 3
  if (packet.Measurement[TEMP][PIT]<=TEMP_LOW) { // Контроль от промораживания подвала по идее здесь надо включать калорифер
#else
  if (packet.Measurement[TEMP][IN]<=TEMP_LOW) { // Контроль от промораживания подвала по идее здесь надо включать калорифер
#endif
    if(FLAG_MOTOR_CHECK){
      MOTOR_OFF;
    }
    delay(3000);
    for(i=0; i<SENSORS_QUANTITY-1; i++)
      Area[i].Close();
    MOTOR_SLOW_SPEED;
#ifdef HEATER
    HEAT_ON;
#endif
    return;
  }

#ifdef HEATER
#if SENSORS_QUANTITY >= 3
  if ((FLAG_HEAT_CHECK)&&(packet.Measurement[TEMP][PIT]>TEMP_LOW+dT_OFF+50)) 
#else
  if ((FLAG_HEAT_CHECK)&&(packet.Measurement[TEMP][IN]>TEMP_LOW+dT_OFF+50)) 
#endif  
    HEAT_OFF;    // Выключить калорифер когда температура поднимится на 0.5 градуса
#endif

// 1. Режимы не зависящие от влажности и температуры ВЫСШИЙ приоритет
// "Выключено"
  if(settingRAM.mode==BLOCK_OFF) {
    for(i=0; i<SENSORS_QUANTITY-1; i++)
      Area[i].Open();
    if(FLAG_MOTOR_CHECK){
      MOTOR_OFF;
    }
    MOTOR_SLOW_SPEED;
    return;
  }

// "Режим вытяжки"
  else if (settingRAM.mode==HOOD_ON ){ 
    for(i=0; i<SENSORS_QUANTITY-1; i++)
      Area[i].Open();
    MOTOR_FAST_SPEED;
    if(~FLAG_MOTOR_CHECK){
      MOTOR_ON;
    }
    return;
  }

// 2. Режим охлаждения (второй приоритет) температура внутри больше 10 градусов, темература снаружи меньше на dH_min градуса чем внутри, на влажность не смотрим
  else if (settingRAM.mode==COOLING){      // Режим охлаждение
    ClosedAreas=0;
    for(i=1; i<SENSORS_QUANTITY; i++){
      if(Area[i-1].State==false)
        ClosedAreas++;                                              // Считаем кол-во закрытых зон
    }
    if(ClosedAreas==SENSORS_QUANTITY-1){       //Если все зоны закрыты и вент работает останавливаем вент
      if(FLAG_MOTOR_CHECK)
        MOTOR_OFF;
      MOTOR_SLOW_SPEED;
    }
    Need4Motor=SENSORS_QUANTITY-1;                                  // Для подсчёта кол-ва зон, которым нужен вент
    for(i=1; i<SENSORS_QUANTITY; i++){
      if (packet.Measurement[TEMP][i]>(packet.T_min*10)){                      // Если в зоне i тепло и
        if ((packet.Measurement[TEMP][i]-packet.Measurement[TEMP][OUT])>packet.dH_min){ // Если в зоне теплее улицы на dH_min открываем клапаны и включаем вент
          if(Area[i-1].State==false){
            Area[i-1].Open();
            ClosedAreas--;
          }
          if(i==IN)
            MOTOR_FAST_SPEED;
          if(~FLAG_MOTOR_CHECK)
            MOTOR_ON;
        }else if ((packet.Measurement[TEMP][i]-packet.Measurement[TEMP][OUT])>dT_OFF){   // иначе если в зоне теплее улицы на dT - просто открываем клапаны
          if(Area[i-1].State==false){
            Area[i-1].Open();
            ClosedAreas--;
          }
        }else if((packet.Measurement[TEMP][i]-packet.Measurement[TEMP][OUT])<-dT_OFF){   // иначе если в зоне i холоднее улицы на dT - закрываем клапаны
          Need4Motor--;
          if(Area[i-1].State==true){
            ClosedAreas++;
            if((ClosedAreas==SENSORS_QUANTITY-1)&&(FLAG_MOTOR_CHECK)){      //Если все зоны закрыты и вент работает останавливаем вент и ждём остановки 3с
               MOTOR_OFF;
               MOTOR_SLOW_SPEED;
               delay(3000);
            }
            Area[i-1].Close();
          }
        }else if((packet.Measurement[TEMP][i]-packet.Measurement[TEMP][OUT])<=dT_OFF){     // иначе если отличие Т в зоне i от улицы в пределах гистерезиса, можно просто остановить вент
          if(i==IN)
            MOTOR_SLOW_SPEED;
          Need4Motor--;
        }
      }else if(Area[i-1].State==true){                                                        // иначе если в зоне i совсем холодно и клапаны открыты - закрываем
        ClosedAreas++;
        if((ClosedAreas==SENSORS_QUANTITY-1)&&(FLAG_MOTOR_CHECK)){      //Если все зоны закрыты и вент работает останавливаем вент и ждём остановки
           MOTOR_OFF;
           MOTOR_SLOW_SPEED;
           delay(3000);
        }
        Area[i-1].Close();
      }
    }
    if((Need4Motor==0)&&(FLAG_MOTOR_CHECK)){               // Если во всех зонах тепло, разница Т в пределах гистерезиса улице и вент работает, то останавливаем вент, клапаны оставляем открытыми
       MOTOR_OFF;
       MOTOR_SLOW_SPEED;
    } 
    return;                           // изменений нет выходим    
  }
    
// 3. В режиме осушения - проверка на достижение минимальной температуры помещения в режиме осушения - ВЫКЛЮЧИТЬ, третий приоритет
  ClosedAreas=0;
  for(i=1; i<SENSORS_QUANTITY; i++){
    if(Area[i-1].State==false)
      ClosedAreas++;                                              // Считаем кол-во закрытых зон
  }
  if((ClosedAreas==SENSORS_QUANTITY-1)&&(FLAG_MOTOR_CHECK)){       // Если все зоны закрыты и вент работает останавливаем вент
    MOTOR_OFF;
    MOTOR_SLOW_SPEED;
  }
  Need4Motor=SENSORS_QUANTITY-1;                                  // Здесь используется как признак, что везде слишком холодно
  for(i=1; i<SENSORS_QUANTITY; i++){
    if (packet.Measurement[TEMP][i]<=(packet.T_min*10)){               // Если в зоне i совсем холодно - закрываем клапаны
      if(Area[i-1].State==true){
        ClosedAreas++;
        if((ClosedAreas==SENSORS_QUANTITY-1)&&(FLAG_MOTOR_CHECK)){      // Если все зоны закрыты и вент работает - останавливаем вент и ждём остановки
           MOTOR_OFF;
           MOTOR_SLOW_SPEED;
           delay(3000);
        }
        Area[i-1].Close();
      }
      if(i==IN)
        MOTOR_SLOW_SPEED;
      Need4Motor--;
    }
  }
  if(Need4Motor==0) // Если во всех зонах слишком холодно, клапаны везде закрыли, мотор выключили - больше ничего не интересно, выходим
    return;
   
// 4. Режимы зависящие от температуры и влажности низший приоритет (что осталось)
// Расчитываем разность срабатывания по влажности
  ClosedAreas=0;
  for(i=1; i<SENSORS_QUANTITY; i++){
    if(Area[i-1].State==false)
      ClosedAreas++;                                              // Считаем кол-во закрытых зон
  }
  if((ClosedAreas==SENSORS_QUANTITY-1)&&(FLAG_MOTOR_CHECK)){       //Если все зоны закрыты и вент работает останавливаем вент
    MOTOR_SLOW_SPEED;
    MOTOR_OFF;
  }
  Need4Motor=SENSORS_QUANTITY-1;                                  // Для подсчёта кол-ва зон, которым нужен вент
  for(i=1; i<SENSORS_QUANTITY; i++){
    if(FLAG_ABS_H_CHECK)   tmp=packet.dH_min;                 // Если режимы используют абсолютную разность в сотых грамма на куб
    else tmp=(int)(packet.Measurement[ABS_HUM][i]*(packet.dH_min/10)/100);      // Если режимы используют ДЕСЯТЫЕ % от абсолютной разности внутренней температуры 
  
    if(packet.Measurement[TEMP][i]>(packet.T_min*10)){                                // темература в зоне i выше минимальной
      if((packet.Measurement[ABS_HUM][i]-packet.Measurement[ABS_HUM][OUT])>tmp){      // Если разница абс влажностей больше на дельту то открываем клапаны и ВКЛЮЧАЕМ мотор
        if(Area[i-1].State==false){
          Area[i-1].Open();
          ClosedAreas--;
        }
        if(i==IN)
          MOTOR_FAST_SPEED;
        if(~FLAG_MOTOR_CHECK)
          MOTOR_ON;
      }else if(packet.Measurement[ABS_HUM][i]-packet.Measurement[ABS_HUM][OUT]>dH_OFF){ // иначе если разница абс влажностей больше гистерезиса - просто открываем клапаны
        if(Area[i-1].State==false){
          Area[i-1].Open();
          ClosedAreas--;
        }
      }else if(packet.Measurement[ABS_HUM][i]-packet.Measurement[ABS_HUM][OUT]<-dH_OFF){ // иначе если в зоне более сухо чем на улице на значение гистерезиса - закрыть клапаны
        Need4Motor--;
        if(Area[i-1].State==true){
          ClosedAreas++;
          if((ClosedAreas==SENSORS_QUANTITY-1)&&(FLAG_MOTOR_CHECK)){      //Если все зоны закрыты и вент работает останавливаем вент и ждём остановки 3с
            MOTOR_OFF;
            MOTOR_SLOW_SPEED;
            delay(3000);
          }
          Area[i-1].Close();
        }
      }else if(packet.Measurement[ABS_HUM][i]-packet.Measurement[ABS_HUM][OUT]<=dH_OFF){ // иначе если разница абс влажностей в пределах гистерезиса - заслонки оставить как есть и можно отключать вент, а можно и крутить
        if(i==IN)
          MOTOR_SLOW_SPEED;
        Need4Motor--;
      }
    }else if(Area[i-1].State==true){                                                        // темература в зоне i меньше либо равна минимальной - закрываем клапаны зоны
      ClosedAreas++;
      if((ClosedAreas==SENSORS_QUANTITY-1)&&(FLAG_MOTOR_CHECK)){      //Если все зоны закрыты и вент работает останавливаем вент и ждём остановки
         MOTOR_OFF;
         MOTOR_SLOW_SPEED;
         delay(3000);
      }
      Area[i-1].Close();
    }
  }
  
//  ucg.setColor(210, 210, 210);
//  print_StrXY(1,155+0, F("Need4Motor: ")); ucg.print(Need4Motor);
//  print_StrXY(1,155+15,F("dH_OFF: "));     ucg.print(dH_OFF);
//  print_StrXY(1,155+30,F("tmp: "));        ucg.print(tmp); 
  
  if((Need4Motor==0)&&(FLAG_MOTOR_CHECK)){               // Если во всех зонах тепло и H в пределах гистерезиса и вент работает, то останавливаем вент, клапаны оставляем открытыми
     MOTOR_OFF;
     MOTOR_SLOW_SPEED;
  }
}

#else

// Проверка статуса вытяжки, не пора ли переключиться
void CheckON(void)
{
  int tmp=0;
// 0.  Проверить замораживание подвала НАГРЕВАТЕЛЬ 
  if (packet.Measurement[TEMP][IN]<=TEMP_LOW) { // Контроль от промораживания подвала по идее здесь надо включать калорифер
    MOTOR_OFF; 
#ifdef HEATER
    HEAT_ON; 
#endif
    return;
  }
  
#ifdef HEATER
  if ((FLAG_HEAT_CHECK)&&(packet.Measurement[TEMP][IN]>TEMP_LOW+dT_OFF+50)) 
    HEAT_OFF;    // Выключить калорифер когда температура поднимится на 0.5 градуса
#endif

// 1. Режимы не зависящие от влажности и температуры ВЫСШИЙ приоритет
  if ((settingRAM.mode==BLOCK_OFF)&&(~FLAG_MOTOR_CHECK))  return;
  if ((settingRAM.mode==BLOCK_OFF)&&(FLAG_MOTOR_CHECK))  { MOTOR_OFF ; return;}
  if ((settingRAM.mode==HOOD_ON )&&(FLAG_MOTOR_CHECK))   return;
  if ((settingRAM.mode==HOOD_ON )&&(~FLAG_MOTOR_CHECK))  { MOTOR_ON  ; return;}

// 2. Режим охлаждения (второй приоритет) температура внутри больше 10 градусов темература снаружи меньше на 2 градуса чем внутри, на влажность не смотрим
  if (settingRAM.mode==COOLING){          // Режим охлаждение
    if ((~FLAG_MOTOR_CHECK)&&(packet.Measurement[TEMP][IN]>(packet.T_min*10))&&((packet.Measurement[TEMP][IN]-packet.Measurement[TEMP][OUT])>packet.dH_min)) // dH_min используется не штатно для температуры
       {MOTOR_ON; return;}            // если мотор выключен, температура выше установленной и снаружи температура ниже на 2 градуса  то ВКЛЮЧЕНИЕ мотора
    if ((FLAG_MOTOR_CHECK)&&(packet.Measurement[TEMP][IN]<=packet.Measurement[TEMP][OUT]))   
       {MOTOR_OFF; return;}           // если мотор включен и темература внутри ниже наружней то ВЫКЛЮЧЕННИЕ мотора
    return;                           // изменений нет выходим    
  } 
    
// 3. В режиме осушения - проверка на достижение минимальной температуры помещения в режиме осушения - ВЫКЛЮЧИТЬ, третий приоритет
  if (packet.Measurement[TEMP][IN]<=(packet.T_min*10))
     {
       if (~FLAG_MOTOR_CHECK)   return;      // Мотор уже выключен, выходим
       else  { MOTOR_OFF; return;}           // выключить и выйти
     } 
   
// 4. Режимы зависящие от температуры и влажности низший приоритет (что осталось)
// Расчитываем разность срабатывания по влажности
if (FLAG_ABS_H_CHECK)   tmp=packet.dH_min;                 // Если режимы используют абсолютную разность в сотых грамма на куб
else tmp=(int)(packet.Measurement[ABS_HUM][IN]*(packet.dH_min/10)/100);      // Если режимы используют ДЕСЯТЫЕ % от абсолютной разности внутренней температуры 

 if ((~FLAG_MOTOR_CHECK)&&(packet.Measurement[TEMP][IN]>(packet.T_min*10))&&((packet.Measurement[ABS_HUM][IN]-tmp)>packet.Measurement[ABS_HUM][OUT])) 
        {MOTOR_ON; return;}        // мотор выключен, темература выше требуемой, абс влажность снаружи меньше на дельту то ВКЛЮЧЕНИЕ мотора
 if ((FLAG_MOTOR_CHECK)&&((packet.Measurement[TEMP][IN]<=(tmp+dT_OFF))||(packet.Measurement[ABS_HUM][IN]<(packet.Measurement[ABS_HUM][OUT]+dH_OFF)))) 
        {MOTOR_OFF; return;}       // мотор включен и (темература меньше либо равна заданной или абс влажность внутри ниже) то ВЫКЛЮЧЕННИЕ мотора
} 
#endif 

// Вывод информации о настройках и сохрание индекса настроек в eeprom ---------------------------------
void Setting()
{ // Настройка
  cli();
  ucg.setColor(0, 0, 180);  // 
  ucg.drawBox(0, 0, 280-1, 23);
  ucg.setColor(255, 255, 255);
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
//  ucg.setPrintPos(0,25+18); 
  ucg.setPrintPos(2,19); 
  switch (settingRAM.mode){
    case  BLOCK_OFF: ucg.print(F("Выключено"));              packet.dH_min=255; packet.T_min=255; FLAG_ABS_H_ON; dH_OFF=20; break; 
    case  HOOD_ON:   ucg.print(F("Режим вытяжки"));          packet.dH_min=0;   packet.T_min=0;   FLAG_ABS_H_ON; dH_OFF=20; break; 
    case  COOLING:   ucg.print(F("Охлаждение T>10 dT>2"));   packet.dH_min=200; packet.T_min=100; FLAG_ABS_H_ON; dH_OFF=(byte)(packet.dH_min/4); break; // dH_min используется не штатно для температуры     
    case  3:         ucg.print(F("Осушение T>+3 dH>1.0"));   packet.dH_min=100; packet.T_min=30;  FLAG_ABS_H_ON; dH_OFF=(byte)(packet.dH_min/4); break;
    case  4:         ucg.print(F("Осушение T>+3 dH>5%"));    packet.dH_min=50;  packet.T_min=30;  FLAG_ABS_H_OFF;dH_OFF=(byte)(packet.dH_min/4); break;
    case  5:         ucg.print(F("Осушение T>+4 dH>2.0"));   packet.dH_min=200; packet.T_min=40;  FLAG_ABS_H_ON; dH_OFF=(byte)(packet.dH_min/4); break;
    case  6:         ucg.print(F("Осушение T>+4 dH>10%"));   packet.dH_min=100; packet.T_min=40;  FLAG_ABS_H_OFF;dH_OFF=(byte)(packet.dH_min/4); break;
    case  7:         ucg.print(F("Осушение T>+5 dH>3.0"));   packet.dH_min=300; packet.T_min=50;  FLAG_ABS_H_ON; dH_OFF=(byte)(packet.dH_min/4); break;
  } 
  writeEeprom();       // Запись в EEPROM  новых настроек
  SET_MODE_BITS;       // В пакет для передачи добавили смену режима
//  MOTOR_OFF;           // Поменяли настройки - отключить мотор, пусть заново настройки сработают если потребуется
#ifdef HEATER
  HEAT_OFF;
#endif
  CheckON();           // Возможно надо включить мотор
  print_status();      // Показать панель состояния (смена настроек)
  sei();   
}

// Вывод float  с двумя десятичными знаком в координаты x y // для экономии места
void print_floatXY(int x,int y, float v)
{
  ucg.setPrintPos(x,y);
// ucg.print(F("-")); // Проверка числа с минусом
  ucg.print(v,1);
// ucg.print(F("  ")); // Стереть хвост от предыдущего числа
} 

// Вывод строки константы в координаты x y // для экономии места
void print_StrXY(int x,int y, const __FlashStringHelper* b)
{
  ucg.setPrintPos(x,y);
  ucg.print(b);
} 

#ifdef INFOSCREEN
void printInfo() // Окно с информацией о блоке, появляется при длительном нажатии на кнопку  и ее отпускании
{ 
  infoScreen=true;
  
  cli();
  ucg.setColor(250, 250, 250);  // 
  ucg.drawBox(5, 5, 320-1-10, 240-1-10);
  ucg.setColor(0, 50, 250);
  ucg.drawFrame(5+5, 5+5, 320-1-20, 240-1-20);
  
  ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT);
  ucg.setColor(0, 200, 10);
  print_StrXY(35,12+16,F("ОСУШИТЕЛЬ на Arduino Pro Mini"));
  
  ucg.setColor(0, 50, 50);
  print_StrXY(5+10,15+16*2,F("1 Напряжение питания В.")); 
  print_floatXY(5+222,15+16*2,readVcc()/1000.0);
 
  print_StrXY(5+10,15+16*3,F("2 Температура блока гр.")); 
  print_floatXY(5+222,15+16*3,GetTemp());
 
  print_StrXY(5+10,15+16*4,F("3 Мото часы блока")); 
  ucg.setPrintPos(5+222,15+16*4); 
  ucg.print(settingRAM.hour_unit/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR))); 
  
  print_StrXY(5+10,15+16*5,F("4 Мото часы вентилятора")); 
  ucg.setPrintPos(5+222,15+16*5); 
  ucg.print(settingRAM.hour_motor/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR))); 
  
#ifdef HEATER
  print_StrXY(5+10,15+16*6,F("5 Мото часы нагревателя")); 
  ucg.setPrintPos(5+222,15+16*6); 
  ucg.print(settingRAM.hour_heat/(TIME_HOUR/(NUM_SAMPLES*TIME_SCAN_SENSOR)));  
#endif
 
  print_StrXY(5+10,15+16*7,F("6 Мах температуры in/out")); 
  ucg.setPrintPos(5+222,15+16*7);
  ucg.print((float)settingRAM.Max[IN]/100,1); 
  ucg.print(F("/"));
  ucg.print((float)settingRAM.Max[OUT]/100,1); 
  
  print_StrXY(5+10,15+16*8,F("7 Мin температуры in/out")); 
  ucg.setPrintPos(5+222,15+16*8);
  ucg.print((float)settingRAM.Min[IN]/100,1); 
  ucg.print(F("/"));
  ucg.print((float)settingRAM.Min[OUT]/100,1); 
  
  print_StrXY(5+10,15+16*9,F("8 Канал NRF24l01+")); 
  ucg.setPrintPos(5+222,15+16*9); 
  ucg.print(NRF24_CHANEL); 
  
  print_StrXY(5+10,15+16*10,F("9 Гистерезис T/absH   ")); 
  ucg.print((float)dT_OFF/100.0);
  ucg.print(F("/"));
  ucg.print((float)dH_OFF/100.0);
  
#ifndef DEMO     // Уже места не хватает в DEMO. В DEMO кусок ниже не показывается и код сокращается
  print_StrXY(5+10,15+16*11,F("10 ERR Т/Н in:")); 
  ucg.print(TIN_ERR); 
  ucg.print(F("/")); 
  ucg.print(HIN_ERR); 
  ucg.print(F(" out:"));
  ucg.print(TOUT_ERR); 
  ucg.print(F("/")); 
  ucg.print(HOUT_ERR); 
 
  ucg.drawHLine(40,18+16*11,320-1-80);
 
  ucg.setColor(0, 0, 150);
  print_StrXY(5+10,19+16*12,F("СБРОС - Вкл. при нажатой кнопке.")); 
 
  ucg.setColor(250,80,80);
  print_StrXY(5+10,19+16*13,F(VERSION)); 
  
  #ifdef  RADIO     // Радио модуль NRF42l вывести число ошибок ask
     ucg.setColor(0, 150, 10);
     ucg.print(F(" AskErr:"));
     ucg.print(err_ask);
  #endif
  sei();
 #endif   // Сокращение кода
  #ifdef BEEP
   beep(40);
  #endif
 
} 

void clearInfo()  // Стереть информационный экран
{
  infoScreen=false;
  last_error=100;         // Признак обновления ошибки
  cli();
  ucg.setColor(0, 0, 0);  // залить черным
  ucg.drawBox(5, 5, 320-1-10, 240-1-10);
  print_static();
  Setting();  
  print_data();                            
  printChart();
  sei();
} 
#endif

// Чтение свободной памяти --------------------------------------------------------------------
int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

// Чтение внутреннего датчика температуры ---------------------------------------
double GetTemp(void)
{
  unsigned int wADC;
  double t;
  sei();  // Должны быть разрешены прерывания
  ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));
  ADCSRA |= _BV(ADEN);  
  delay(20);           
  ADCSRA |= _BV(ADSC);  
  while (bit_is_set(ADCSRA,ADSC));
  wADC = ADCW;
  t = (wADC - 324.31 ) / 1.22;
  return (t); 
}

// Чтение напряжения питания ----------------------------------------------
long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = ConstADC / result; // Back-calculate AVcc in mV
  return result;
} 

// Запись счетчиков в Eeprom --------------------------------------------------
void writeEeprom()
{ 
cli(); 
  eeprom_write_block((const void*)&settingRAM, (void*) &settingEEPROM, sizeof(settingRAM)); 
sei();
}

// Чтение счетчиков из Eeprom --------------------------------------------------
void readEeprom()
{
cli(); 
   eeprom_read_block((void*)&settingRAM, (const void*) &settingEEPROM, sizeof(settingRAM)); 
   if ((settingRAM.mode>(NUM_SETTING-1))||(settingRAM.mode<0)) settingRAM.mode=0;        // гарантированно попадаем в диапазон
sei();
}

void reset_sum()  // Сброс счетчиков накоплений
{
  sensors.num=0;  
  for (byte i=0; i<2; i++)
    for (byte j=0; j<SENSORS_QUANTITY; j++)
      sensors.Sum[i][j]=0;
}

char hex(byte x)  // Функция для вывода в hex
{
   if(x >= 0 && x <= 9 ) return (char)(x + '0');
   else                  return (char)('a'+x-10);
}

// Сброс дисплея --------------------------------------
bool reset_ili9341(void)
{
  pinMode(PIN_RESET, OUTPUT);                    // Сброс дисплея сигнал активным является LOW
  digitalWrite(PIN_RESET, LOW);  
  delay(100);
  digitalWrite(PIN_RESET, HIGH);  
  ucg.begin(UCG_FONT_MODE_TRANSPARENT);
  ucg.setFont(my14x10rus);  
//   ucg.setRotate90();
  ucg.setRotate270();
  ucg.clearScreen();
}

#ifdef BEEP  
void beep(int x)  // Пищать х мсек
{
  WritePort(C,1,HIGH);
  delay(x);
  WritePort(C,1,LOW);
} 
#endif

void boot_test(void)
{
  byte i;
  ucg.clearScreen();
  ucg.setColor(255, 255, 255);
  print_StrXY(10,50,F("Тест вентилятора"));
  MOTOR_ON;
  for(i=0;i<3;i++){
    delay(1000);
    ucg.print(F(" ."));
  }
#ifdef SERVO
  MOTOR_FAST_SPEED;
  for(i=0;i<3;i++){
    delay(1000);
    ucg.print(F(" ."));
  }
  MOTOR_SLOW_SPEED;
#endif
  MOTOR_OFF;      
  delay(1000);      
  #if ID == 0x21   // Только у 21 блока установлен нагреватель
#ifdef HEATER
    ucg.setColor(255, 255, 255);
    print_StrXY(10,70,F("Тест нагревателя"));
    HEAT_ON;
    for(i=0;i<5;i++){
      delay(1000);
      ucg.print(F(" ."));
    }
    HEAT_OFF;   
#endif
  #endif
  #ifdef SERVO
    print_StrXY(10,90,F("Тест сервоприводов"));
    for(i=0; i<SENSORS_QUANTITY-1; i++){
      ucg.print(F(" ."));
      Area[i].Close();
      delay(1000);
    }
    for(i=0; i<SENSORS_QUANTITY-1; i++){
      ucg.print(F(" ."));
      Area[i].Open();
      delay(1000);
    }
  #endif
  ucg.clearScreen();
}

// Функция переводит относительную влажность в абсолютную 
// t-температура в градусах Цельсия h-относительная влажность в процентах
float calculationAbsH(float t, float h)
{
  float temp;
  temp=pow(2.718281828,(17.67*t)/(t+243.5));
  return (6.112*temp*h*2.1674)/(273.15+t);
}

// Сканирование клавиш ------------------------------------------
void scanKey()
{  
  byte key,key1; 
  cli(); 
  key=ReadPort(D,3);                                // Прочитать кнопку 0 - нажата 
  delay(25);                                        // Задержка для подавления дребезга контактов
  key1=ReadPort(D,3);                               // читаем еще раз кнопку
  if (key!=key1) {resetKey();return;}               // если значения не равны то ложное срабатывание выходим
  if ((key==0)&&(flagKey==false)){                  // Если кнопка была нажата и флаг ещё не стоит
    flagKey=true;                                   // Кнопка нажата, ставим флаг и ждем обратного фронта
    time_key=millis();                              // Время нажатия запомнили
  }
  if ((key==1)&&(flagKey==true)){                   // Если кнопка отжата и флаг до сих пор стоит
    time_key=millis()-time_key;                     // Рассчитать время нажатия
    pressKey=true;                                  // Поставить признак обработки кнопки
  }
  sei();
 }

 

pav2000
Offline
Зарегистрирован: 15.12.2014

Создал тему по второй версии устройства 

http://arduino.ru/forum/proekty/kontrol-vlazhnosti-podvala-ver20-stm32-set

Основное отличие - использование stm32 и  удаленное управление через ethernet

 

fedoroff_alexander
Offline
Зарегистрирован: 12.04.2015

Вопрос. Такое решение способно прокачать подвал? Может по энергозатратам будет накладно. Самодельный осушитель воздуха на элементе Пельтье (usamodelkina.ru)

xxx333000
Offline
Зарегистрирован: 28.08.2016

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

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

А по моему, канальный вентилятор, тапа такого https://www.blauberg.ru/series/centro-m гораздо эффективнее справится. Попутно удалив ненужные газы, типа метана и радона. И по электричеству не накладно

fedoroff_alexander
Offline
Зарегистрирован: 12.04.2015

Там же вода должна что называется сочиться. В яме без гидроизоляции? Ни какой осушитель не поможет.