📜 ⬆️ ⬇️

Arduino programmable relay

The idea is trivial, I needed a controller to manage the load in the house:
1. Boiler heating.
2. Accumulative boiler for water supply.
3. Pump in the well.

I read a lot of fascinating articles on the topic of XX on Arduino, reading which clearly stated in my head the thought “I want Arduino”. Having estimated the cost of components and ready-made solutions, I considered a clear benefit from the implementation of Arduino.

image
')

So, the minimum program:


1. 4 relays, clock (RTC), LCD screen;
2. Modes of operation of each relay: on, off, daily timer, one-time on;
3. Control buttons for setting the time and relay modes;

The house has a two-tariff counter, so the boiler heats the water from 23 to 7 in the morning. Heating is similar: two out of three heating elements, according to my idea, will be switched on at night. Temperature control is still native to the standard remote. A one-time switch-on as a reserve will go to the pump, we program the switch-on, for example, to set the tank or pump the well, after which the relay goes off. The main feature: made a complete device, controlled by buttons, and does not require connection to a PC.

Of course, I wanted to hang everything in the future on the controller, since for heating it is advisable to make 3 operating modes: day from 7 to 23 in order to save, night, warming up for the morning off from 5..6 to 7. But for now the minimum program is implemented.

Hardware:


In the manufacture there was a task to get the product as cheap as possible, therefore the collective farm was present as much as possible. Ali ordered a starter kit for arduino Uno R3, 4 relay module, I2C 20 * 4 LCD screen, RTC DS1307 I2C clock, Dht21 digital temperature and humidity sensor.

Since all this saw the first time had to learn. General concepts learned using Google from:
http://habrahabr.ru/company/masterkit/blog/257747/
http://arduino.ru/Reference

I can not make a beautiful connection scheme, nothing. In Fritzing for example, from components only the microcontroller.

Connecting relays and buttons did not cause problems, only turned on pull-up resistors. This is in the manual. The link https://arduino-info.wikispaces.com/LCD-Blue-I2C#v3 helped in connecting the LCD screen. It took a trimmer resistor adjustment, the screen “out of the box” did not burn at all, which caused me a little confusion.

The clock required only a battery, connected according to the standard scheme http://zelectro.cc/RTC_DS1307_arduino

Clock synchronization with a computer did not. When starting, it checks if the date is less than 2000 or more than 2100, the clock setting menu is displayed.

Connection of several buttons to the analog input is described at http://arduino.net.ua/Arduino_articles/Arduino_proekty/Podkljuchenie%20knopok%20k%20odnomu%20analogovomu%20vhodu/ link, in the same place it is described how to enable pull-up resistors “pinMode (A2, INPUT_PULLUP); "

The controls are classic, “monitor”: buttons “menu”, “+”, “-”, “set”.

Assembly process (with photo)
I took the mounting plate on 6 machines:

image

Put the mounting racks under the modules:

image

Screw relay, clock, controller:

image

From the printer took a couple of rollers and some kind of sleeve. The sleeve is glued on double-sided tape. They will be mounted another board, more on that below.

The power supply unit took from some kind of router Dlink, 5V 2A, did not begin to wrestle with the USB cable directly to it:

image

Cut out plastic panel for mounting the screen:

image

Installed the screen. Secured with mounting racks, under the keyboard - screw. Racks are selected in height with the expectation that they will rest against the cover of the shield, giving the rigidity of the structure. A sleeve on the relay block prevents the board from being pushed down when the buttons are pressed.

image

The buttons were originally planned to be connected to digital inputs, but suddenly I found a keyboard module from the monitor, which came up as a native one (a diagram of the buttons from the monitor ).

The keyboard is glued to a double-sided tape through a gasket to lift the board over the bottom right screw. The buttons press a match through the holes. Ideally, drill holes and insert normal pushers. Maybe I will do it in a cold winter evening, and now it was necessary to urgently implement a relay for heating water.

Photos of the finished device:
image
image

A flashing LED is also present.


At the moment, the relay is hanging "on snot", controls the boiler, the final installation will be made after the installation of wiring and contactors for the heating boiler. Phase from the pads, too, must be removed, of course, when installing the wiring. Now there is no time, it is necessary to do outdoor chores. The cost of parts amounted to about 2 thousand rubles.

Software part:


The software part was not easy: it took 90% of the time to write the menu, the usable code was managed only with the third version of the firmware.

The first approach grew out of test samples for parts verification. A classic example of procedural programming, development has not received. I had to remember the principles of writing a good, valid code that would be amenable to subsequent reading and editing.

The second approach was to translate the code into OOP principles. The basis was a certain class TMenu, from which the menu elements were inherited directly.

In short. The CurrentMenu pointer is assigned the address of the current menu item. The main class items are the ItemIsValue bit, which determines whether the current item is a submenu or a changeable value and the OnKey (), Increment (), Decrement () and Print () functions. Also the menu class contains a pointer to the parent menu and an array of pointers. In general, the use of inheritance made it possible to make an arbitrary multi-level menu, in principle, we can say that this is a dynamic menu, only in this implementation it is formed once during initialization. In any case, the code is easily edited, menu items are added. Cruel reality put me in my place. In UNO R3 for all this luxury is not enough memory.

The third approach is the second pruning. The main difference is that a specific menu class object contains either nested menus or variables — editable values, the type of which is specified by the class.

So, the class is defined:
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); }; 


The class contains:
- the number of menu items (submenu or variable), a pointer to the parent menu (if the pointer is 0, then reached the top);
- MenuName the name of the menu;
- ItemIsValue is described above.
- the position number of the cursor in the menu ( CurrentItem );
- pointer to array of pointers Items . Addresses submenu. If the menu contains editable items, this value is 0;
- the Print () function is called from the loop loop on behalf of the current menu “CurrentMenu-> Print ();” thus the screen with the necessary text is drawn.
- the OnKey function (byte KeyNum) is also called from the loop in the contact bounce block, also known as the keyboard decoder from the monitor.
- functions ChangeItem (byte value) , virtual void Increment (void) , virtual void Decrement (void) are called from OnKey () and process the buttons "+" and "-". ChangeItem () is a batch of menu items, Increment () and Decrement () are polymorphic, a batch of values ​​for the current variable.
- the CheckDateTime function (DateTime OldDate, int Increment, byte DatePart) checks the entered date and time. The viscose year and the number of days in the month of 28/29, 30, 31 are recognized. Based on the logic, the current date, +1 or -1, and the index of the date / time part (0 - year, 5 - second) are passed to the function

Menu navigation is implemented by assigning the address of an object to the CurrentMenu pointer:
- CurrentMenu = CurrentMenu-> Items [CurrentMenu-> CurrentItem]; enter selected menu
- CurrentMenu = CurrentMenu-> Parent; go to previous menu

Work Logic:

The loop loop continuously polls the keyboard, checks the relay settings and flashes the LED.

The keyboard is polled as a rudiment and digital inputs 2-6 (menu, -, +, set), the values ​​of the analog ports are converted to these codes.
- when you press the "menu" button outside the menu, the menu is called up; otherwise, the menu will go up;
- pressing "+" or "-" causes a cyclic reassembly of menu items or a cyclic change of the current parameter. When you press the "'set" button, enter the selected menu or save the variable value to flash with simultaneous selection of the next value.

The bounce is suppressed programmatically, each button is assigned a push and release counter, which increases if pressed or released. The survey is carried out 3 times with an interval of 15 ms. The push or release count is incremented by 1 or reset. Thus, the chattering of both pressing and releasing is recognized. The release state is fixed for one-time operation when the button is held.

In the relay settings , the operation mode is checked, in the “Daily” mode, only time is entered and verified, to the nearest minute. The on time is correctly recognized as longer than the off time, for example, turning on at 23 and turning off at 7. In the “On” mode, the date and time are set. For convenience of setting, I plan to connect the fifth button and set the function for setting the current date and time in edit mode to it.

This is in brief. Small class functions are declared, usually when class is declared, header files and libraries are not used. The code is so small.

Program code
#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 declaration ************* ********************************* /
/ ************************************************* *************************************= ***** /

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) {
///// here you need to write the relay in memory

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 ()));
};
};

/ ************************************************* ************************************************** ***** /
/ ******************************** End of class declaration ************** ***************************** /
/ ************************************************* ************************************************** ***** /

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 ++)
{
// here you need to add loading parameters from the flash
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 ();
// check time
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 // enter the submenu
{
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 = on time is longer than off time
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);
}
}
}


That turned out to be an odd job that saved me from having to get up at 7 am and go to bed at 23, while not forgetting to click the toggle switches. I do not pretend to industrial standards, I did not encapsulate the data in the code, I also left global variables.

There were concerns about the accuracy of the clock, but so far no significant deviations have been noticed.

Thank you for attention.

Source: https://habr.com/ru/post/357880/


All Articles