Programmable Relay on Arduino

The idea is commonplace, I needed a controller to control the load in the house:
1. A heating boiler.
2. Storage boiler for water supply.
3. The pump in the well.

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

image

So, the minimum program:


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

A two-tariff meter is installed in the house, so the boiler heats the water from 23 to 7 in the morning. Similarly, heating: two of the three ten, according to my idea, will turn on at night. Temperature control is still native to the standard remote control. A one-time inclusion as a reserve will go to the pump, we program the inclusion, for example, to set the tank or pump the well, after which the relay goes into off mode. The main feature: a finished device is made, controlled by buttons, and does not require a connection to a PC.

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

Hardware part:


When manufacturing, the task was to get the cheapest product possible, therefore, the collective farm is present as much as possible. Ali ordered a starter kit for arduino Uno R3, 4 relay modules, I2C 20 * 4 lcd screen, RTC DS1307 I2C clock, Dht21 digital temperature and humidity sensor.

Since I saw all this for the first time, I had to master it. I got general concepts using Google from:
http://habrahabr.ru/company/masterkit/blog/257747/
http://arduino.ru/Reference

I can’t make a beautiful connection diagram, there’s nothing. In Fritzing, for example, of the components, only the microcontroller itself.

Connecting the relay and buttons did not cause problems, I only turned on the pull-up resistors. This is in the manual. Link helped to connect the LCD screenhttps://arduino-info.wikispaces.com/LCD-Blue-I2C#v3 . Adjustment with a tuning resistor was required, “out of the box” the screen did not burn at all, which caused me a slight confusion.

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

did not synchronize the clock with the computer. At startup, a check is made if the date is less than 2000 or more than 2100, the clock settings menu is displayed.

The 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/ , in the comments it is described how to turn on the pull-up resistors “pinMode (A2, INPUT_PULLUP); "

The control is classic, "monitor": the buttons "menu", "+", "-", "set".

Assembly process (with photo)
I took a mounting plate for 6 machines:

image

I put the mounting racks under the modules: I

image

screwed the relay, clock, controller: I

image

took a couple of rollers and some kind of sleeve from the printer. The sleeve is glued to double-sided tape. Another board will be attached to them, more on that below.

The power supply was taken from some Dlink router, 5V 2A, I did not

image

start to puzzle over the USB cable directly: I cut a panel for attaching the screen from plastic: I

image

installed the screen. Secured with mounting racks, under the keyboard - a screw. Racks are selected in height with the expectation that the shield cover will abut against them, giving rigidity to the structure. A bushing on the relay block prevents the board from being pushed down when the buttons are pressed.

image

I initially planned to connect the buttons to the digital inputs, but suddenly I found a keyboard module from the monitor, which came up as a native one ( the buttons scheme from the monitor ).

I glued the keyboard to the double-sided tape through the gasket to raise the board above the lower right screw. I press the buttons with a match through the holes. Ideally, you need to drill holes and insert normal pushers there. Maybe I’ll make it on a cold winter evening, but now it was urgent to introduce a relay for heating water.

Photo of the finished device: A
image
image

flashing LED is also present.


At the moment, the relay hangs "on the nozzles", controls the boiler, the final installation will be made after installing the wiring and contactors for the heating boiler. The phase from the block must also be removed, of course, during wiring. Now there is no time, it is necessary to do outdoor housework. The cost of parts amounted to about 2 thousand rubles.

Software part:


The software part was not easy: 90% of the time was spent on writing the menu, the usable code was mastered only with the third version of the firmware.

The first approach grew from test pieces to test parts. The classic example of procedural programming, has not received development. I had to recall the principles of writing good, valid code that would lend itself to subsequent reading and editing.

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

In short. The CurrentMenu pointer is assigned the address of the current menu item. The main elements of the class are the ItemIsValue bit, which determines whether the current item is a submenu or a variable value and the functions OnKey (), Increment (), Decrement () and Print (). The menu class also contains a pointer to the parent menu and an array of pointers. In general, the use of inheritance made it possible to create 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. UNO R3 lacks all this luxury.

The third approach is to cut back on the second. The main difference is one - a specific object of the menu class contains either submenus or variables - editable values, the type of which is set 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 the top is reached);
- MenuName is the name of the menu;
- ItemIsValue described above
- cursor position number in the menu ( CurrentItem );
- a pointer to an array of items pointers . Submenu addresses 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 desired text is drawn.
- OnKey function (byte KeyNum)It is also called from the loop cycle in the contact bounce suppression unit, it is the keyboard decoder from the monitor.
- the functions ChangeItem (byte value) , virtual void Increment (void) , virtual void Decrement (void) are called from OnKey () and process the "+" and "-" buttons. ChangeItem () is a sorting of menu items, Increment () and Decrement () are polymorphic, sorting the values ​​of the current variable.
- function CheckDateTime (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 date / time part index (0 - year, 5 - seconds) are transferred to the function.

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

Logic of work:

The loop cycle continuously polls the keyboard, checks the relay settings and blinks an LED.

The keyboard is polled as a rudiment and by digital inputs 2-6 (menu, -, +, set), the values ​​of the analog ports are recounted to these codes.
- when you click on the “menu” button outside the menu, the menu is called up; otherwise, the menu goes up;
- when you press "+" or "-", the menu items are cycled through or the current parameter is cyclically changed. When you press the "'set" button, enter the selected menu or save the value of the variable to flash while simultaneously selecting the next value.

Chatter is suppressed programmatically, each button is assigned a counter for pressing and releasing, which increases if pressed or released. The survey is carried out 3 times with an interval of 15 ms. The press or release counter is incremented by 1 or reset. In this way, rattling of both pressing and releasing is recognized. The release state is fixed for a single actuation while holding the button.

In the relay settings , the operating mode is checked, in the "Daily" mode only time is entered and checked, accurate to minutes. The turn-on time is correctly recognized longer than the turn-off time, for example, turn-on at 23 and turn-off at 7. In the "On" mode, the date and time are set. For convenience, I plan to connect the fifth button and set the function for setting the current date and time in editing mode on it.

This is briefly. Small class functions are declared, as a rule, when declaring a class, header files and libraries are not used. The code is already small.

Program code
#include

#include
#include
#include
#include
#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 to 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 we need to add the loading of 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 ());

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


Here's a hand-made article that saved me from having to get up at 7 in the morning and go to bed at 23, while not forgetting to click the toggle switches. I do not pretend to industrial standards, I did not do encapsulation of data in the code, I also left global variables.

There were concerns about the accuracy of the watch, but so far I did not notice a significant deviation.

Thank you for attention.

Also popular now: