Программируемое реле на Ардуино

Идея банальна, понадобился контроллер для управления нагрузкой в доме:
1. Котел отопления.
2. Накопительный бойлер для водоснабжения.
3. Насос в скважине.

Читал массу увлекательных статей на тему ХХ на Ардуино, читая которые четко фиксировал в голове мысль «хочу Ардуино». Прикинув стоимость компонентов и готовых решений, посчитал явную выгоду от внедрения Ардуино.

image

Итак, программа минимум:


1. 4 реле, часы (RTC), ЖК экран;
2. Режимы работы каждого реле: включено, выключено, суточный таймер, одноразовое включение;
3. Кнопки управления для настройки времени и режимов реле;

В доме установлен двухтарифный счетчик, поэтому бойлер нагревает воду с 23 до 7 утра. Аналогично отопление: два из трех тэнов, по моей задумке будут включаться ночью. Управление температурой пока остается родное на штатном пульте. Одноразовое включение в качестве резерва пойдет на насос, программируем включение, например, на набор емкости или прокачку скважины, после чего реле переходит в режим выключено. Основная особенность: изготовлено законченное устройство, управляемое кнопками, и не требующее подключения к ПК.

Конечно, хотелось в перспективе все повесить на контроллер, так как для отопления целесообразно сделать 3 режима работы: день с 7 до 23 в целях экономии, ночь, разогрев к утреннему отключению с 5..6 до 7. Но пока реализована программа минимум.

Аппаратная часть:


При изготовлении была задача получить как можно более дешевое изделие, поэтому максимально присутствует колхоз. На Али были заказаны стартовый комплект для arduino Uno R3, 4 релейный модуль, жк-экран I2C 20*4, часы RTC DS1307 I2C, цифровой датчик температуры и влажности Dht21.

Поскольку все это видел первый раз пришлось осваивать. Общие понятия почерпнул с помощью гугла из:
http://habrahabr.ru/company/masterkit/blog/257747/
http://arduino.ru/Reference

Красивую схему подключения сделать не могу, не в чем. Во Fritzing к примеру, из компонентов только сам микроконтроллер.

Подключение реле и кнопок проблем не вызвало, единственно включил подтягивающие резисторы. Это есть в руководстве. В подключении ЖК экрана помогла ссылка https://arduino-info.wikispaces.com/LCD-Blue-I2C#v3. Потребовалось регулировка подстроечным резистором, «из коробки» экран не горел совсем, чем вызвал у меня легкое замешательство.

Часы потребовали только батарейку, подключил по типовой схеме http://zelectro.cc/RTC_DS1307_arduino

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

Подключение нескольких кнопок к аналоговому входу описано по ссылке http://arduino.net.ua/Arduino_articles/Arduino_proekty/Podkljuchenie%20knopok%20k%20odnomu%20analogovomu%20vhodu/, там же в комментариях описано как включить подтягивающие резисторы «pinMode (A2, INPUT_PULLUP);»

Управление классическое, «мониторное»: кнопки «меню», "+","-", «set».

Процесс сборки (с фото)
Взял монтажный щиток на 6 автоматов:

image

Поставил монтажные стойки под модули:

image

Прикрутил реле, часы, контроллер:

image

От принтера взял пару валиков и какую-то втулку. Втулка приклеена на двухсторонний скотч. На них будет крепиться еще одна плата, об этом ниже.

Блок питания взял от какого-то роутера Dlink, 5В 2А, ломать голову не стал запаял прямо на него USB провод:

image

Вырезал из пластика панель для крепления экрана:

image

Установил экран. Закрепил монтажными стойками, под клавиатурой — винт. Стойки подобраны по высоте с расчетом, что в них будет упираться крышка щитка, придавая жесткость конструкции. Втулка на блоке реле предотвращает продавливание платы вниз при нажатии кнопок.

image

Кнопки изначально планировал подключить к цифровым входам, но внезапно нашел модуль клавиатуры от монитора, который подошел как родной (схема кнопок от монитора).

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

Фото готового устройства:
image
image

Мигающий светодиод также присутствует.


В данный момент реле висит «на соплях», управляет бойлером, окончательный монтаж будет произведен после установки проводки и контакторов для котла отопления. Фазу с колодки тоже надо убрать, конечно, при монтаже проводки. Сейчас некогда, надо делать наружные работы по дому. Затраты на детали составили около 2 тысяч рублей.

Программная часть:


Программная часть далась нелегко: 90% времени ушло на написание меню, годный код удалось осилить только с третьей версией прошивки.

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

Второй подход был переводом кода на принципы ООП. За основу был взят определенный класс TMenu, от которого наследовались непосредственно элементы меню.

Вкратце. Указателю CurrentMenu присваивается адрес текущего элемента меню Основные элементы класса это бит ItemIsValue, который определяет является ли текущий элемент подменю или изменяемым значением и функции OnKey(), Increment(), Decrement() и Print(). Также класс меню содержит указатель на родительское меню и массив указателей. В общем использование наследования позволяло сделать произвольное многоуровневое меню, в принципе можно сказать, что это динамическое меню, только в в данной реализации оно формируется один раз при инициализации. Во всяком случае код легко редактируется, добавляются элементы меню. Жестокая реальность поставила меня на место. В UNO R3 на всю эту роскошь не хватает памяти.

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

Итак, Определен класс:
class TMenu
{
  public:
    byte _ItemsCount;
    TMenu *Parent;
    String *MenuName;
    boolean ItemIsValue;
    byte CurrentItem;

    TMenu **Items;
    String *ItemsName;
    byte ItemsCount(void) ;
    bool AddItem(TMenu *NewItem);

    virtual void Print(void);
    void OnKey(byte KeyNum);
    void ChangeItem(byte value);

    virtual void Increment(void);
    virtual void Decrement(void);

    virtual void OnSet(void);
    DateTime CheckDateTime(DateTime OldDate, int Increment, byte DatePart);
};


Класс содержит:
— число элементов меню (подменю или переменная), указатель на родительское меню (если указатель равен 0, то достигнут верх);
MenuName имя меню;
ItemIsValue описан выше
— номер позиции курсора в меню (CurrentItem);
— указатель на массив указателей Items. Адреса подменю. Если меню содержит редактируемые элементы, это значение равно 0;
— функция Print() вызывается из цикла loop от имени текущего меню «CurrentMenu->Print();» таким образом отрисовывается экран с нужным текстом.
— функция OnKey(byte KeyNum) также вызывается из цикла loop в блоке подавления дребезга контактов, он же декодер клавиатуры от монитора.
— функции ChangeItem(byte value), virtual void Increment(void), virtual void Decrement(void) вызываются из OnKey() и обрабатывают кнопки "+" и "-". ChangeItem() — это переборка элементов меню, Increment() и Decrement() — полиморфные, переборка значений текущей переменной.
— функция CheckDateTime(DateTime OldDate, int Increment, byte DatePart) проверяет введенную дату и время. Распознается вискозный год и количество дней в месяце 28/29, 30, 31. Исходя из логики в функцию передается текущая дата, +1 или -1 и индекс части даты/времени (0 — год, 5 — секунды)

Навигация по меню реализована присвоением адреса объекта указателю CurrentMenu:
— CurrentMenu = CurrentMenu->Items[CurrentMenu->CurrentItem]; вход в выбранное меню
— CurrentMenu = CurrentMenu->Parent; переход в предыдущее меню

Логика работы:

Цикл loop непрерывно опрашивает клавиатуру, проверяет настройки реле и мигает светодиодом.

Клавиатура опрашивается в качестве рудимента и по цифровым входам 2-6 (menu,-,+,set), к этим кодам пересчитываются значения аналоговых портов.
— при нажатии на кнопку «menu» вне меню происходит вызов меню, в противном случае переход на меню вверх;
— при нажатии "+" или "-" происходит циклическая переборка элементов меню или циклическое изменение текущего параметра. При нажатии кнопки "'set" вход в выбранное меню либо сохранение значения переменной во флеш с одновременным выбором следующего значения.

Дребезг подавляется программно, каждой кнопке присваивается счетчик нажатия и отпускания, который увеличивается в случае нажатия или отпускания. Опрос проводится 3 раза с интервалом 15 мс. Счетчик нажатия или отпускания увеличивается на 1 либо сбрасывается. Таким образом распознается дребезг как нажатия так и отпускания. Состояние отпускания фиксируется для однократного срабатывания при удержании кнопки.

В настройках реле проверяется режим работы, в режиме «Daily» вводится и проверяется только время, с точностью до минут. Правильно распознается время включения больше времени выключения, например, включение в 23 и выключение в 7. В режиме «Оnce» задается дата и время. Для удобства настройки планирую подключить пятую кнопку и задать на нее функцию установки текущей даты и времени в режиме редактирования.

Это вкратце. Небольшие функции классов объявлены, как правило при объявлении класса, заголовочные файлы и библиотеки не используются. Код и так небольшой.

Код программы
#include <EEPROM.h>

#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#define LEFT 0
#define CENTER 1
#define RIGHT 2

#define RelayModesCount 4
#define KeyFirst 2
#define KeyLast 6

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
RTC_DS1307 RTC; // RTC Modul
DHT dht(7, DHT21); // pin, type
volatile boolean Blinker = true;
volatile long BlinkerTime;
volatile byte ButtonPress[8];
const String RelayModeNames[] = {«OFF», «ON», «Once», «Daily»};

int aKey1 = 0;
int aKey2 = 0;

DateTime NowDate;

boolean DoBlink(void)
{
boolean Result = false;
long NBlinkerTime = millis();
if (Blinker)
{
if (NBlinkerTime — BlinkerTime > 200)
{
digitalWrite(8, HIGH);
BlinkerTime = NBlinkerTime;
Blinker = false;
Result = true;
}
}
else
{
if (NBlinkerTime — BlinkerTime > 300 )
{
digitalWrite(8, LOW);
BlinkerTime = NBlinkerTime;
Blinker = true;
}

}
return Result;
}
String BlinkString(String string, byte Cur, byte ItemsCount)
{
String result = string;
byte len = string.length();
if (!Blinker && Cur == ItemsCount)
{
for (byte i = 0; i < len; i++) result.setCharAt(i, ' ');
}
return result;
}

/********************************************************************************************************/
/********************************** Объявление классов *********************************************/
/********************************************************************************************************/

class TMenu
{
public:
byte _ItemsCount;
TMenu *Parent;
String *MenuName;
boolean ItemIsValue;
byte CurrentItem;

TMenu **Items;
String *ItemsName;
// byte ItemsCount(void);
byte ItemsCount(void) {
return _ItemsCount;
};
bool AddItem(TMenu *NewItem);

virtual void Print(void);
void OnKey(byte KeyNum);
void ChangeItem(byte value);

virtual void Increment(void);
virtual void Decrement(void);

virtual void OnSet(void);
DateTime CheckDateTime(DateTime OldDate, int Increment, byte DatePart);
};

class TNoMenu: public TMenu
{
public:
void Print(void);
TNoMenu(TMenu *ParentMenu) {
MenuName = 0;
CurrentItem = 0;
_ItemsCount = 0;
Parent = ParentMenu;
Items = 0;
ItemsName = 0;
ItemIsValue = false;
};
void Increment(void) {};
void Decrement(void) {};
void OnSet(void) {};

};

class TSelectMenu: public TMenu
{
public:
void Print(void);
TSelectMenu(TMenu *ParentMenu, String NewName) {
MenuName = new String(NewName);
CurrentItem = 0;
_ItemsCount = 0;
Parent = ParentMenu;
Items = 0;
ItemsName = 0;
ItemIsValue = false;
};
void Increment(void) {};
void Decrement(void) {};
void OnSet(void) {};
};

class TTimeMenu: public TMenu
{
public:
void Print(void);
DateTime *SetDateTime;
long OldDateTime;
TTimeMenu(TMenu *ParentMenu, String NewName, DateTime *ParamDate) {
MenuName = new String(NewName);
CurrentItem = 0; _ItemsCount = 6; Parent = ParentMenu; Items = 0; ItemsName = 0;
ItemIsValue = true; OldDateTime = millis();
SetDateTime = ParamDate;
};
void Increment(void) {
*SetDateTime = CheckDateTime (*SetDateTime, 1, CurrentItem);
};
void Decrement(void) {
*SetDateTime = CheckDateTime (*SetDateTime, -1, CurrentItem);
};
void OnSet(void) {
RTC.adjust(*SetDateTime);
};
void SecondTimer(void) {
long TmpDateTime = millis(); if (TmpDateTime — OldDateTime > 1000) {
OldDateTime = TmpDateTime;
*SetDateTime = *SetDateTime + 1;
};
};
};
class TRelayMenu: public TMenu
{
public:
byte RelayNumber;
byte RelayMode;
// byte Shedule=0;
boolean OnceBit;
DateTime RelayOn;
DateTime RelayOff;
TRelayMenu(TMenu *ParentMenu, byte NewNumber, String NewName) {
MenuName = new String(NewName);
CurrentItem = 0; _ItemsCount = 11; Parent = ParentMenu; Items = 0; ItemsName = 0; ItemIsValue = true, OnceBit = false;
RelayNumber = NewNumber;
RelayMode = 0;
RelayOn = DateTime(2015, 1, 1, 23, 00, 00);
RelayOff = DateTime(2015, 1, 1, 07, 00, 00);
};
void Print(void);
void Increment(void) {
if (!CurrentItem) {
RelayMode++;
if ( RelayMode >= RelayModesCount) RelayMode = 0;
}
else if (CurrentItem < 6) RelayOn = CheckDateTime (RelayOn, 1, CurrentItem — 1);
else RelayOff = CheckDateTime (RelayOff, 1, CurrentItem — 6);
};
void Decrement(void) {
if (!CurrentItem) {
RelayMode--;
if ( RelayMode > 127) RelayMode = RelayModesCount — 1;
}
else if (CurrentItem < 6) RelayOn = CheckDateTime (RelayOn, -1, CurrentItem — 1);
else RelayOff = CheckDateTime (RelayOff, -1, CurrentItem — 6);
};

boolean CheckDaily(void);

void OnSet(void) {
///// здесь надо записать реле в память

byte p_address = RelayNumber * 16;
EEPROM.write(p_address, RelayMode);

EEPROM.write(p_address + 1, byte(RelayOn.year() — 2000));
EEPROM.write(p_address + 2, byte(RelayOn.month() ));
EEPROM.write(p_address + 3, byte(RelayOn.day() ));
EEPROM.write(p_address + 4, byte(RelayOn.hour() ));
EEPROM.write(p_address + 5, byte(RelayOn.minute() ));

EEPROM.write(p_address + 6, byte(RelayOff.year() — 2000));
EEPROM.write(p_address + 7, byte(RelayOff.month() ));
EEPROM.write(p_address + 8, byte(RelayOff.day() ));
EEPROM.write(p_address + 9, byte(RelayOff.hour() ));
EEPROM.write(p_address + 10, byte(RelayOff.minute() ));
};
};

/********************************************************************************************************/
/******************************** Конец объявления классов ******************************************/
/********************************************************************************************************/

TMenu *CurrentMenu = 0;
TNoMenu *NoMenu = 0;
TSelectMenu *SelectMenu;
TTimeMenu *TimeMenu;

TRelayMenu *RelayMenu[4];

/********************************************************************************************************************************************/
/********************************************************************************************************************************************/
/********************************************************************************************************************************************/
void setup()
{
NoMenu = new TNoMenu(0);
SelectMenu = new TSelectMenu (NoMenu, «NoMenu»);
TimeMenu = new TTimeMenu(SelectMenu, «Time Setup», &NowDate);

SelectMenu->AddItem(TimeMenu);

byte p_address;
DateTime DTFlesh;
for (int i = 0; i < 4; i++)
{
// здесь надо добавить загрузку параметров из флеша
RelayMenu[i] = new TRelayMenu (SelectMenu, i, «Relay » + String(i + 1));
SelectMenu->AddItem(RelayMenu[i]);

p_address = i * 16;

RelayMenu[i]->RelayMode = EEPROM.read(p_address);

DTFlesh = DateTime(int(EEPROM.read(p_address + 1) + 2000), EEPROM.read(p_address + 2), EEPROM.read(p_address + 3), EEPROM.read(p_address + 4), EEPROM.read(p_address + 5), 0 );
RelayMenu[i]->RelayOn = RelayMenu[i]->CheckDateTime(DTFlesh, 0, 0);

DTFlesh = DateTime(int(EEPROM.read(p_address + 6) + 2000), EEPROM.read(p_address + 7), EEPROM.read(p_address + 8), EEPROM.read(p_address + 9), EEPROM.read(p_address + 10), 0 );
RelayMenu[i]->RelayOff = RelayMenu[i]->CheckDateTime(DTFlesh, 0, 0);
}

for (byte i = KeyFirst; i < KeyLast; i++)
{
pinMode(i, INPUT); //Keypad 2-«menu» 3-"-" 4-"+" 5-«SET»
digitalWrite(i, HIGH); //setup Resistor input2Vcc
ButtonPress[i] = true;
}
pinMode(8, OUTPUT); //LED
pinMode(9, OUTPUT);
for (byte i = 10; i < 14; i++)
{
pinMode(i, OUTPUT); // relay i
digitalWrite(i, HIGH);
}

pinMode (A2, INPUT_PULLUP);
pinMode (A3, INPUT_PULLUP);

Serial.begin(9600); // Used to type in characters
digitalWrite(8, LOW);
digitalWrite(9, HIGH);

lcd.begin(20, 4); // initialize the lcd for 20 chars 4 lines and turn on backlight
RTC.begin();

lcd.noBacklight();
delay(150);
lcd.backlight();

NowDate = RTC.now();
//проверка времени
if ( NowDate.year() > 2000 && NowDate.year() < 2114 &&
NowDate.month() > 0 && NowDate.month() < 13 &&
NowDate.day() > 0 && NowDate.day() < 32 &&
NowDate.hour() >= 0 && NowDate.hour() < 24 &&
NowDate.minute() >= 0 && NowDate.minute() < 60 &&
NowDate.second() >= 0 && NowDate.second() < 60 )
{
CurrentMenu = NoMenu;
}
else
{
lcd.setCursor(2, 1);
lcd.print(«Clock Failure!»);
delay(700);
RTC.adjust(DateTime(2015, 1, 1, 00, 00, 00));
CurrentMenu = TimeMenu;
}

}

void loop()
{
/********* KEYPAD BUNCLE, 5 keys, from 2 to 6 *********/
byte NButtonPress[8] = {0, 0, 0, 0, 0, 0, 0, 0};
byte NButtonRelease[8] = {0, 0, 0, 0, 0, 0, 0, 0};
const byte ButtonTry = 3;

aKey1 = analogRead (2);
aKey2 = analogRead (3);
byte aKeyNum=0;

/**************** check for key pressed or released ****************/
for (byte i = 0; i < 3; i++)
{
delay(15);
if (aKey1< 64) aKeyNum=2;//AnalogKey 1 = Dig2
else if (aKey1<128) aKeyNum=6;//Analog key 3 = D4
else if (aKey1<256) aKeyNum=4;//key 5=d6
else if (aKey2< 64) aKeyNum=1;//key 6 = menu
else if (aKey2<128) aKeyNum=3;//analogkey 2 = D3
else if (aKey2<256) aKeyNum=5;//key 4 =d5
else aKeyNum=0; // no key

for (byte j = KeyFirst; j < KeyLast; j++) // Read ports 2...6
{
if (digitalRead(j) == LOW || aKeyNum==j)
{
NButtonPress[j]++;
NButtonRelease[j] = 0;
}
else
{
NButtonPress[j] = 0;
NButtonRelease[j]++;
delay(5);
}
}

}
/*************** Do key process ******************/
// byte m;

for (byte j = KeyFirst; j < KeyLast; j++)
{
if (NButtonPress[j] >= ButtonTry && ButtonPress[j] == false)
{
ButtonPress[j] = true;
CurrentMenu->OnKey(j);
}
else
{
if (NButtonRelease[j] >= ButtonTry && ButtonPress[j] == true)
{
ButtonPress[j] = false;
}
}
}
/***************** Relay Check *********************/

CurrentMenu->Print();
DoBlink();
}

void LcdPrint(byte string, String str, byte Align)
{
byte StrTrim1;
byte StrTrim2;
lcd.setCursor(0, string); //Start at character 0 on line 0
switch (Align)
{
case RIGHT:

break;

case CENTER:
StrTrim1 = byte((20 — str.length()) / 2);
StrTrim2 = 20 — str.length() — StrTrim1;
for (byte k = 0; k < StrTrim1; k++) lcd.print(" ");
lcd.print(str);
for (byte k = 0; k < StrTrim2; k++) lcd.print(" ");
break;

default:
lcd.print(str);
StrTrim1 = 20 — str.length();
for (byte k = 0; k < StrTrim1; k++) lcd.print(" ");
}
}

void TNoMenu::Print(void)
{
NowDate = RTC.now();
String Ddate;
Ddate = " R1-" + RelayModeNames[RelayMenu[0]->RelayMode] + " R2-" + RelayModeNames[RelayMenu[1]->RelayMode];
LcdPrint(0, Ddate, CENTER);
Ddate = " R3-" + RelayModeNames[RelayMenu[2]->RelayMode] + " R4-" + RelayModeNames[RelayMenu[3]->RelayMode];
LcdPrint(1, Ddate, CENTER);
Ddate = String (NowDate.year()) + "/" + String(NowDate.month()) + "/" + String(NowDate.day()) + " " + String (NowDate.hour()) + ":" + String(NowDate.minute()) + ":" + String(NowDate.second());
LcdPrint(2, Ddate, CENTER);
Ddate = «Temp » + String (int(dht.readTemperature())) + «C, Hum » + String(int(dht.readHumidity())) + "%";
LcdPrint(3, Ddate, CENTER);

RelayCheck();
}

void TTimeMenu::Print(void)
{
SecondTimer();
String Ddate = BlinkString(String((*SetDateTime).year()), CurrentItem, 0) + "/" +
BlinkString(String( (*SetDateTime).month()), CurrentItem, 1) + "/" +
BlinkString(String((*SetDateTime).day()), CurrentItem, 2) + " ";
LcdPrint(1, Ddate, CENTER);
Ddate = BlinkString(String ((*SetDateTime).hour()), CurrentItem, 3) + ":" +
BlinkString(String((*SetDateTime).minute()), CurrentItem, 4) + ":" +
BlinkString(String((*SetDateTime).second()), CurrentItem, 5);
LcdPrint(2, Ddate, CENTER);

LcdPrint(3, " ", CENTER);
RelayCheck();
}

void TMenu::OnKey(byte KeyNum)
{
switch (KeyNum)
{
case 3: // — if (ItemIsValue) Decrement();
else ChangeItem(-1);
break;
case 4: // +
if (ItemIsValue) Increment();
else ChangeItem(1);
break;
case 5: // SET
if (ItemIsValue)
{
OnSet();
ChangeItem(+1);
}
else // вход в подменю
{
if (Items && ItemsCount())
{
if (CurrentMenu->ItemsCount())
{
CurrentMenu = CurrentMenu->Items[CurrentMenu->CurrentItem];
CurrentMenu->CurrentItem = 0;
}
}
}
break;
default: // 2 -menu
if (Parent) CurrentMenu = CurrentMenu->Parent; //(TMenu *) &NoMenu;
else
{
CurrentMenu = SelectMenu;
CurrentMenu->CurrentItem = 0;
}
}
}

void TMenu::ChangeItem(byte value)
{
CurrentItem += value;
if (CurrentItem > 128) CurrentItem = ItemsCount() — 1;
else if (CurrentItem > ItemsCount() — 1) CurrentItem = 0;
}

boolean TMenu::AddItem(TMenu *NewItem)
{
if (!Items) Items = new TMenu *[_ItemsCount = 1];
else Items = (TMenu **)realloc((void *)Items, (_ItemsCount = _ItemsCount + 1) * sizeof(void *));
Items[_ItemsCount — 1] = NewItem;
}

DateTime TMenu::CheckDateTime(DateTime OldDate, int Increment, byte DatePart)
{
int DTmin[6] = {2000, 1, 1, 0, 0, 0};
int DTmax[6] = {2199, 12, 31, 23, 59, 59};

int DT[6];
int diff;

DT[0] = OldDate.year();
DT[1] = OldDate.month();
DT[2] = OldDate.day();
DT[3] = OldDate.hour();
DT[4] = OldDate.minute();
DT[5] = OldDate.second();
DT[DatePart] = DT[DatePart] + Increment;

if (DT[1] == 1 || DT[1] == 3 || DT[1] == 5 || DT[1] == 7 || DT[1] == 8 || DT[1] == 10 || DT[1] == 12) DTmax[2] = 31;
else if (DT[1] == 2)
{
if ((DT[0] % 4 == 0 && DT[0] % 100 != 0) || (DT[0] % 400 == 0)) DTmax[2] = 29;
else DTmax[2] = 28;
}
else DTmax[2] = 30;

for (byte i = 0; i < 6; i++)
{
if (DT[i] > DTmax[i]) DT[i] = DTmin[i];
else if (DT[i] < DTmin[i]) DT[i] = DTmax[i];
}

return DateTime(DT[0], DT[1], DT[2], DT[3], DT[4], DT[5]);

}

void TSelectMenu::Print(void)
{
NowDate = RTC.now();
byte shift = 0;
if (CurrentItem > 3) shift = CurrentItem — 3;
for (byte i = 0; i < 4; i++)
{
if ((CurrentItem — shift) == i) //&&Blinker)
{
LcdPrint(i, ">> " + * (Items[i + shift]->MenuName) + " <<", CENTER);
}
else LcdPrint(i, *(Items[i + shift]->MenuName), CENTER);
}
RelayCheck();
}

void TRelayMenu::Print(void)
{

String DData;
NowDate = RTC.now();
LcdPrint(0, (*MenuName) + "[" + BlinkString(RelayModeNames[RelayMode], CurrentItem, 0) + "]", CENTER);
DData = «On:»;
switch (RelayMode)
{
case 3: //Daily
// DData = DData + " ";
if (CurrentItem > 0 && CurrentItem < 4) CurrentItem = 4;
break;
default:
DData = DData + BlinkString(String(RelayOn.year(), DEC), CurrentItem, 1) + "/" + BlinkString(String( RelayOn.month(), DEC), CurrentItem, 2) +
"/" + BlinkString(String( RelayOn.day(), DEC), CurrentItem, 3);
}
DData = DData + " " + BlinkString(String (RelayOn.hour(), DEC), CurrentItem, 4) + ":" + BlinkString(String(RelayOn.minute(), DEC), CurrentItem, 5);
LcdPrint(1, DData, CENTER);
DData = «Off:»;
switch (RelayMode)
{
case 3: //Daily
// DData = DData + " ";
if (CurrentItem > 5 && CurrentItem < 9) CurrentItem = 9;
break;
default:
DData = DData + BlinkString(String(RelayOff.year(), DEC), CurrentItem, 6) + "/" + BlinkString(String( RelayOff.month(), DEC), CurrentItem, 7) +
"/" + BlinkString(String( RelayOff.day(), DEC), CurrentItem, 8);
}
DData = DData + " " + BlinkString(String (RelayOff.hour(), DEC), CurrentItem, 9) + ":" + BlinkString(String(RelayOff.minute(), DEC), CurrentItem, 10);
LcdPrint(2, DData, CENTER);
LcdPrint(3, " ", CENTER);
}

boolean TRelayMenu::CheckDaily(void)
{
int TimeOn = 60 * int(RelayOn.hour()) + int(RelayOn.minute());
int TimeOff = 60 * int(RelayOff.hour()) + int(RelayOff.minute());
int NowTime = 60 * int(NowDate.hour()) + int(NowDate.minute());
boolean result; // true = время включения больше времени выключения
if ( TimeOn > TimeOff )
{
if (NowTime <= TimeOff || NowTime >= TimeOn ) result = true;
else result = false;
}
else
{
if (NowTime <= TimeOff && NowTime >= TimeOn ) result = true;
else result = false;
};
return result;

}

void RelayCheck (void)
{
boolean OnceBitCheck;
for (byte i = 0; i < 4; i++)
{
switch (RelayMenu[i]->RelayMode)
{
case 1: //relay 0n
digitalWrite(i + 10, LOW);

break;
case 2: //Once;
OnceBitCheck = (NowDate.unixtime() > RelayMenu[i]->RelayOn.unixtime() && NowDate.unixtime()<RelayMenu[i]->RelayOff.unixtime());

if (OnceBitCheck) RelayMenu[i]->OnceBit = true;
else if (RelayMenu[i]->OnceBit)
{
RelayMenu[i]->RelayMode = 0;
byte p_address = RelayMenu[i]->RelayNumber * 16;
EEPROM.write(p_address, RelayMenu[i]->RelayMode);
}
digitalWrite(i + 10, !OnceBitCheck);
break;
case 3: //Daily
digitalWrite(i + 10, !(RelayMenu[i]->CheckDaily()));
break;
default: //relay 0ff
digitalWrite(i + 10, HIGH);
}
}
}


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

Были опасения в точности хода часов, но пока существенного отклонения не заметил.

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

Ну. И что?
Реклама
Комментарии 72
  • +2
    Вот если коробочку не открывать то прям промышленный образец. Редко такие увидишь с ардуино…
    • 0
      Так то обычный электрический щиток с прозрачной дверцей.
      • +2
        Ну у большинства всё остаётся в лапше торчащей из стены :)
      • НЛО прилетело и опубликовало эту надпись здесь
      • 0
        можно сделать красивее, но задача была быстро сделать максимально дешевый колхоз. В большинстве случаев логика работы забита в код.
        • 0
          Разве это колхоз? Тут даже платы с монтажными стойками! :-)
          • 0
            Колхоз, однако, разным бывает…
        • 0
          В доме установлен двухтарифный счетчик, поэтому бойлер нагревает воду с 23 до 7 утра. Аналогично отопление: два из трех тэнов, по моей задумке будут включаться ночью.

          Вы отапливаете дом электричеством??? Да Вы батенька богатей! Последний раз (лет 7-8 назад), когда я видел дом, который топят электричеством — это была загородная дача какого то футболиста Российской сборной у которого куры денег не клюют. Счета там были космические. Ну или вы электричество воруете. У вас нормальная дровяная печь то там есть? В случае если газа в доме нет и невозможно провести, обычно применяют или автоматический твердотопливный котел. Котельную во дворе устраивают. В бункер закидывается топливо на 2-7 дней (в зависимости от температуры воздуха). Или делается скважина и для отопления и горячей воды применяется тепловой насос. Однажды беседовал с обладателями такого девайса. Подтапливаться дровами приходится только в лютые морозы (-25 и ниже), в остальное время он справлялся. Но в том случае, я думаю что, проблема скорее всего была в недостаточной мощности устройства или в его неправильном монтаже.
          • 0
            Все это актуально для отдельного дома, в квартире многое из этих вещей не применишь а центральное отопление не всегда вовремя работает.
            • 0
              Для квартиры тоже есть несколько решений. Ставится кондиционер с функцией отопление и в межсезонье(пока нет отопления) он работает как тепловой насос. Ну а от электрического бойлера никуда в квартире не убежишь. Разве что только газовый котел можно поставить.
              • 0
                Газовый водогрей в квартире, где он не предусмотрен, ставить тупо запрещено.
            • +1
              Скажите, пожалуйста, а Вы графики выхода на точку равнозатратности рисовать не пытались?
              Я вот это делал. Электричество далеко не самый плохой вариант, ТН сильно хуже (если, конечно, его не из списанных деталей от кондеев делать).
              А рекомендуемый Вами пеллетник в моем юскейсе начинает быть дешевле солярочного только на девятый год эксплуатации, но тут вот ведь какая ерунда: пеллетники служат где-то лет 15, после чего их нужно менять (горелки и шнековые приводы так и еще чаще), Дизельные котлы тоже столько же служат, но экономия на смене котлов и расходки получается весьма существенной. И это я еще не моделировал ситуацию, когда планируемые к затратам на пеллетник деньги (точнее их дельта между ТТ и ДТ) кладется в банк и проценты учитываются в смете общих расходов.

              Что же до пеллет, то кВтч электричества в ночном тарифе стоит дешевле кВтч пеллетного, поэтому экономные граждане на ночь глушат ТТ котел и запускают электрический.

              Да, и про котельную в отдельном строении: Вы, я уверен, считали расходы на возведение строения, регистрацию его в БТИ, налоги со строения и теплопотери в подземной теплотрассе в смете общих расходов на хотя бы 10-летнем отрезке. А, да, ну и, разумеется, не забыть бы еще обустройство прохода теплотрассы в основное здание из-под земли. Эта задача, прямо скажем, не из дешевых, особенно, если нет цокольного этажа.
              • 0
                Я не пытался. Я не занимаюсь отоплением. Я рассказал только о том, что видел сам. Тепловой насос лучше, если только его устанавливали не Равшан и Джамшут из соседней стройки. Люди с которыми я разговаривал сначала тоже себе все электричеством сделали, но т.к. они жили в доме постоянно, то зимой счета за электричество доходили до 80-90 т.руб. в месяц(это 6 лет назад). Извините но невозможно зимой при температуре -12 — 25 не топить днем дом. После установки теплового насоса они стали платить максимально по 15-20 т.р. в зимние месяцы. По итогам имеем 2-3 года срок окупаемости по сравнению с электроотоплением. Вы мне про расчеты, а я вам про практику. Я к этим людям вообще по другому вопросу приезжал, просто увидел девайс и спросил. По поводу остальных вопросов. Я хочу сказать следующее. Если дом возможно отопить 15 кВт электрической мощности, то в принципе все ОК. Можно не заморачиваться и топить (хотя суммы счетов будут космическими). Но чаще всего если задействуется электроотопление, то мощность ввода приходится увеличивать. В Подмосковье, например, легко и почти бесплатно получается стандартное подключение 15 кВт на участок. Однако мощность свыше этого лимита стоит таких денег, которых хватит Вам и на постройку котельной, и на котел, и на топливо для этого котла на ближайшие 2-3 года.
            • 0
              Скажите пожалуйста, что будет, если реле, которым Вы включаете насос, залипнет?
              • 0
                не знаком с этой релейной сборкой, но разве там электромагнитные реле?
                • 0
                  Да это и не важно. Что будет с водопроводом, если реле залипнет в замкнутом состоянии?
                  • 0
                    Да ничего не будет. Реле давления отключит насос, вот и всё.
                    • 0
                      А где Вы увидели реле давления в схеме? И зачем оно нужно, если есть ардуино? И зачем нужно ардуино, если есть реле давления?
                      • 0
                        Верну Вам Ваш вопрос: где Вы увидели обработку датчика давления ардуиной?
                        Ардуина может оказаться полезной, если есть желание глушить насос по датчику протечки, например.
                        • +1
                          Я не увидел вообще никакой безопасности в этом проекте. Совсем и полностью. И, наблюдая за похожими проектами, я крайне редко вижу элементы «правильного» подхода к автоматизации, когда надо закладываться на отказ любого элемента системы.
                          «Умный дом в массы» — очень правильный тренд, но забывание о надежности и отказоустойчивости (в классическом понимании) обернется минимум — финансовыми потерями, максимум — трагедиями.
                          • +1
                            … ни одного предохранителя на весь умный дом :)
                            • 0
                              Реле давления к безопасности отношение конечно имеет, но не самое прямое. Оно ставится всегда — просто без него водопровод в автономной системе водоснабщения работать не будет.
                              Кстати вот думаю, не замутить ли обзор реле давления для насосов — я уже четыре разных типа у себя затестдрайвил, но там ни ардуины ни кода — ни фига нет…
                              • 0
                                У моих соседей на чердаке стоит 2 кубовые емкости и выключатель по уровню, прекрасно обходятся без реле давления.
                                Обзор РД — хорошая идея. Я тоже оттестил, в общей сложности, одно безымянное РД и два «контроллера». Причем один из них натурально самозатопился (потекла прокладка мембранной полости), и продолжал при этом функционировать.
                                • 0
                                  Я бы не рискнул ставить на чердак две тонны воды.
                                  Обрушившееся перекрытие с последующим разливом двух тонн воды в помещении это всегда очень дорого.
                                  Особенно зимой.
                                  • 0
                                    Очень странный аргумент. 2 тонны воды Вас на чердаке смущает, а пять тонн кровли — нет?
                                    • 0
                                      Не смущает, разумеется. Снег на кровле это распределенная нагрузка, бак с водой распределен куда меньше.
                                      Кроме того, межэтажные перекрытия редко строят с учетом такой нагрузки, как две тонны воды локально (не, если у Ваших знакомых монолит, тогда все ок).
                    • +1
                      Да, там простейшие электромагнитные реле, которые могут залипнуть.
                      • 0
                        я б не рискнул такое в ответственные цепи ставить
                        • 0
                          Да можно ставить, только какую-то обратную связь надо сделать, с отрубанием всего нафиг в случае залипания.
                          • +1
                            А отрубать будете еще одним реле? :)
                            Твердотельное реле рулит, у него залипания вообще не предусмотрено конструкцией. И обойдется дешевле, чем контроль залипания ЭМ-реле.
                            • 0
                              Ну, контроль — штука вообще хорошая. Поставить датчик тока, и заодно будем знать потребление нагрузки.
                              А отрубать — да, еще одним реле. Или, скорее, пускателем на общем входе всего этого дела. Но в целом — да, симисторы тут будут предпочтительнее.
                              • +1
                                Я бы задавался вопросом «а что, если?» при проектировании системы безопасности.
                                Предположим, релюха включает свет в туалете. Залипла — ну и фиг с ней, не критично. Увижу, что свет горит, когда не должен, разберусь — небо на землю не упадет.
                                А вот поверхностный насос, принудительно запитанный в отсутствие тока воды, может доставить неприятностей, ему так работать неполезно. Довольно скоро он закипятит в себе воду и начнет разрушаться, ибо кавитация, ибо прокладка корпуса и сальник не предназначены для кипятка и т.д.
                                • 0
                                  Эх, не смогу показать, потерял ссылку на форуме, я там разбирал убийственный глубинный насос «российского» производителя. Там тоже не очень думали о безопасности.
                                  В итоге, из-за неправильно выбранного материала нижнего подшипника вала, на нем образовалась каверна. Каверна забилась — насос заклинило, заклинило насос — пусковой ток перегрел и взорвал пусковой конденсатор в корпусе насоса. Во время взрыва и расплавления, фазный провод прилёг на корпус насоса.
                                  Когда я вынул это чудо на поверхность и стал разбираться, то из трусости решил УЗО поставить прямо рядом, на случай «если что». Не поставил — не писал бы сейчас тут.
                                  • 0
                                    Аналогичная история приключилась, когда я крутил потроха китайскому поверхностному насосу. Не удивлен, если честно.
                                    Сосед решил, что он самый умный в мире Карлсон, купил что-то экстремально дешевое, типа насосная станция в сборе за 2500 рублей. Все как положено, ГА с пластиковым фланцем, насос с пластиковым корпусом, дребезжащая автоматика и т.п.
                                    Ну и прекратила она у него работать почти сразу: подшипники там были из смеси соломы с пластилином, ротор был почти заклинен, он меня позвал посмотреть, и тут, видимо ротор заклинило совсем. Ну а через это дело точно так же подорвался конденсатор, а дальше КЗ, оплавление проводов — вместо автомата у дедушки был нормальный жучок. Тут я уж не стал дожидаться финала и обесточил это все нахрен, мой дом рядом стоит, его жалко.

                                • +2
                                  Симисторы имеют плохую особенность, они в любой момент могут самопроизвольно открыться, например от помехи по питанию.
                                  Неоднократно сталкивался с тем что симистор открывается при внезапном КЗ в нагрузке, когда он должен быть закрыт.
                                  Во время грозы, если не предпринять специальных мер они будут периодически открываться на пол периода а то и на целый.
                                  • 0
                                    Да, об этом не стоит забывать. Т.е., тоже не панацея.
                                • +1
                                  Да, именно так. Причем на отдельное, аварийное реле, должен быть свой контроллер, считывающий непосредственно информацию с датчиков (возможно, не со всех), и самостоятельно принимающий решение о том, что «все пропало».
                                  Твердотельное реле, конечно, не залипает. Зато оно может пробиться.
                                  Не бывает безотказных деталей.
                                  • 0
                                    А как Вы планируете контролировать исправность аварийного реле?
                                    Мне действительно это интересно.
                                    • 0
                                      Мне пока в голову приходит только периодическая «учебная тревога».
                                      • 0
                                        Вы же, наверное, сами понимаете, что Вам это надоест за полгода максимум.
                                        Вот скажите, как часто Вы нажимаете кнопку «Тест» на УЗО у себя дома? Раз в полгода, как того требует любая инструкция?
                                        • 0
                                          Я раз в год жму. И, что удивительно, они-таки да, выходят из строя. За 14 лет эксплуатации половина УЗО поменяно.
                                          • 0
                                            Я жму два раза в год, кроме того, купил тестер для УЗО, кроме самотеста еще и им гоняю, чтоб знать, на каком токе утечки он срубается.
                                            Штуковина выглядит вот так: image
                                            Покупал тут: http://www.castorama.fr/store/Testeur-VT35-Terre-et-diff-Multimetrix-prod4620001.html
                                            Одно УЗО из трех установленных таки выбраковал.
                                            • НЛО прилетело и опубликовало эту надпись здесь
                                              • 0
                                                Тогда уж взять проволочный переменник (ППБ) и отградуировать.
                                                • 0
                                                  Ок, ок, ок. Вы себе сделали такую штуку? Я так понимаю, что вряд ли.
                                                  А у меня вот есть и я ей пользуюсь.
                                                  Не все проблемы надо решать паяльником. Некоторые можно и баблом — тупо быстрее.

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

                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                    • 0
                                                      У меня весь щит на ИЭКах собран, разве что УЗО и вводной автомат — АВВ.
                                                      Все живет, все работает. Слухи про ненадежность ИЭК изрядно преувеличены. Могу Вам показать фотку выгоревшего дотла легранда, если желаете, сгорел вообще в номинальном режиме работы. Это не автомат, конечно, был, а устройство защиты от молний, но таки вот.
                                                      Тестер УЗО — ну я просто жил долгое время в той стране, мне до магазина было ближе, чем до метро. В РФ аналогичные приборы тоже продаются, кстати.
                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                            • 0
                                              Отсюда вывод — еще один девайс, который дергает защиту по расписанию :)
                                          • 0
                                            Никак не планирую. Эшелонированность защиты должна иметь предел.
                                            Если совсем все правильно делать, то я бы выбил из производителей деталей исследования по надежности, прикинул бы потребную мне, посчитал бы общую надежность, и сделал бы столько эшелонов, сколько нужно, чтобы выйти на необходимый показатель.
                                            По практике — коммутация одной цепи двумя реле + аварийное реле на весь модуль с контролем выходных цепей (после «исполнительных» реле). Стоит — копейки.
                                        • +1
                                          Есть там залипания, называется пробой.
                                          Второе реле, которое будет всё это обесточивать к моменту залипания основного реле будет иметь гораздо меньшую наработку на количество переключений контактов, а значит его залипание в нужный момент времени маловероятно.
                                  • 0
                                    Судя по фото — да, электромагнитные
                                • +3
                                  Сделано добротно, однако электромагнитные реле лучше заменить на тиристорные, контакты которых не залипают даже в условиях сильных нагрузок. Типа такого:

                                  image
                                  • 0
                                    Симисторные, но не суть. Или собрать самому, из оптосимистора MOC3063 и какого-нибудь мощного выходного.
                                    • 0
                                      Собственно, этот модуль и есть…
                                      • 0
                                        Ну, да, только с радиатором и клеммами. В принципе, они есть недорогие, иногда проще купить, чем городить.
                                      • 0
                                        Симисторные, но не суть.

                                        Симистop (симметричный триодный тиристор) — полупроводниковый прибор, являющийся разновидностью тиристоров… (Wiki)
                                        • 0
                                          Строго говоря — да. И динистор туда же. Но они достаточно отличаются, чтобы иметь собственное название.
                                      • 0
                                        На них надо ставить соответствующее охлаждение.
                                        А поводу незалипания это не совсем так. Там могут быть пробои как и в любой электронике.
                                        • 0
                                          На них надо ставить соответствующее охлаждение.

                                          Смотря какая нагрузка. У нас теплый пол работает через такую сборку + ATTiny, включается каждые 5 секунд на определенное время. Тиристорная сборка как на фото, написано 40 А, но при более слабой нагрузке радиатор добавлять не пришлось.

                                          Недавно чел. описывал опыт включения обогревателя через реле. Реле сининькие (как в статье) работают несколько недель а то и меньше. Выход был найден — именно тиристорные сборки.
                                          • 0
                                            Ну, на этих реле написано 10 или 16А, при этом они долго и хорошо работают на 1-2А, не больше. Проверено.
                                            • 0
                                              Потому что они не силовые. Если внимательно посмотреть в даташит, то эти 10-16А это максимальный рабочий ток который реле может выдержать, но ток коммутации гораздо меньше.
                                              Есть такие же реле но которые нормально коммутируют токи до 5...7А, надо внимательно смотреть на указанную модель реле, силовые они еще и стоят дороже.
                                              • 0
                                                Логично. Вообще, кстати, когда габариты позволяют, я стараюсь использовать реле в колодках на рейку (недавно узнал, что их кличут «промежуточными»).
                                              • 0
                                                Реле, как на картинке выше, часто подделывают и поэтому они могут не тянуть нагрузку, которая указана на наклейке.
                                                Можно найти много интересных картинок и историй по ключевым словам типа «fotek fake ssr». Поэтому надо быть аккуратнее в этом плане.
                                              • 0
                                                Какое потребление такого пола? У меня нагрузки, видимо, больше.
                                          • +2
                                            Раз уж поднята тема безопасности. Для того, чтобы система была хоть немного более правильной, не хватает следующего:
                                            1) предохранителей;
                                            2) темопредохранителей;
                                            3) обратной связи по всему — по выходам, по напряжению питания.
                                            То есть, сделано все аккуратно, но о некоторых вещах просто не подумали.
                                            • НЛО прилетело и опубликовало эту надпись здесь
                                              • +2
                                                Отвечаю по порядку.
                                                1. Отопление комбинированное. Я не хоккеист с зарплатой 1 млн рублей, у меня дом 50 кв. м. За глаза хватает 6 кВт, из них 4 кВт включается на ночь. 4 кВт*8часов*30 дней* 0,69 руб/кВтч = 662 рубля в месяц. Я дурак? Альтернатива — дрова.
                                                2. Это реле времени, а не умный дом. В его задачи входит только переключать режимы. Предохранители установлены в щитке, т.е. на входе. Термопредохранители и прочие защитные элементы установлены в штатной заводской системе управления бойлером и котлом т.е. на выходе. Реле включается в разрыв электроцепи. В случае залипания реле нагреватели будут работать в штатном режиме — будут греть воду или отапливать дом по установленным на штатной панели настройкам. В случае несрабатывания — из крана будут течь холодная вода, а дома будет немного прохладно.
                                                3. Да реле те самые, залипающие. Тиристорные ставить не буду. Буду перемонтировать котел — поставлю контакторы в щиток.
                                                4. Вода в бойлере закончится не может, изучите устройство бойлера, прежде чем давать советы. Забор идет из верхней точки. Выкипеть вода не может, так как выход бойлера — это кран, который в нормальном состоянии закрыт.
                                                5. Обратную связь по всему делать не буду, я критерии устойчивости по Найквисту считать не умею.
                                                • 0
                                                  прошу прощения, по диагонали прочитал, если закончится горячая вода — будет холодная. В штатном режиме хватает на две помывки тела и одну-две помывки посуды. По тех заданию такой расклад устраивает.
                                                  • 0
                                                    Я полностью согласен со всеми Вашими тезисами, кроме одного: вода в бойлере таки может доставить неприятностей.
                                                    Но для этого надо, чтобы заклинило не только Ваше реле, надо, чтоб еще заклинил терморегулятор, а группа безопасности также заклинила или вовсе не была установлена.
                                                    Впечатляющий видос от разрушителей:
                                                    • 0
                                                      По 2-му пункту об этом стоило бы сказать в статье. У меня (и не только) сложилось впечатление, что на самоделку возлагается больше, чем на самом деле.
                                                    • 0
                                                      Небольшой апдейт.
                                                      1) Сделан шлейф с нормальными разъемами.
                                                      2) Добавлено 4 твердотельных реле, все тэны подключены на них
                                                      3) Ввиду того, что заняты все пины, датчик температуры-влажности выпилен
                                                      4) Добавлен ультразвуковой датчик уровня воды в расходную емкость, на него добавлен новый класс реле на скважину и насосную станцию. Минус датчика — при минус 20 и ниже не работает, к минус 10 оживает. Надо утеплять и греть. Сама вода не замерзает, но датчик и электроника как бы не в воде.
                                                      5) Скважина включается отдельной кнопкой (из запаса клавиатуры) и отключается по достижению определенного уровня. По снижению уровня отключается насосная станция.
                                                      6) Библиотеки тестировались на ардуино-мини, поэтому они все добавлены в проект и порезаны. Удалось реально снизить вполовину.
                                                      Исходник тут. https://yadi.sk/d/JBdYvFQB3BE2sF

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

                                                      Самое читаемое