/*
Chicken guard
- Opens and closes the chicken door in the morning and evening. A status LED (green/red) indicates the door status.
- Monitors the drinkable water level, with a status LED (green/red) indicating the water level.
- Logs information and sends commands via the serial monitor, USB, and optionally the HC-05 bluetooth module.
- Connects to the internet to log information and send commands via MQTT, which can be set up in Home Assistant.
- Allows the addition of a clock module DS3231 to prevent the door from opening before a certain time. The clock adjusts for summer/winter time and periodically syncs with internet time if available.
- Even without a clock module, setting date/time is possible, especially when connected to the internet, ensuring synchronization at each startup and regular intervals.
A light sensor determines when the door opens and closes, but it won't open before a specified time, particularly during summertime when predators may be active early in the morning.
Even without a clock module, setting date/time is possible, especially when connected to the internet, ensuring synchronization at each startup and regular intervals.
A light sensor determines when the door opens and closes, but it won't open before a specified time, particularly during summertime when predators may be active early in the morning.
Even without the clock module, the system can still operate based on time using the Arduino timer, especially when connected to the internet.
In the absence of an internet connection, the current time must be provided each time the Arduino resets.
The system uses a light sensor, and a 3-way switch at the light detector allows for different modes, including immediate door closure or opening based on the LDR connection.
Two LEDs indicate whether the door is (about to) open or close and also show error status.
A blinking pattern of the red LED signifies an error, which can be reset by switching the switch between immediate close and open within 5 seconds.
Upon powering on, the system waits for commands for the first minute, and commands can be entered anytime thereafter.
There are manual commands to open and close the door, and the system determines whether to keep it closed or open based on the light conditions.
The trap door's design, similar to a medieval castle door, employs an elastic on the outside to assist in opening the door initially.
Make sure the door is not closed and there is enough light during the initial setup to allow for proper system configuration.
In normal operation, commands can still be given, and logging can be adjusted using the 'L' command.
The 'H' command provides a help screen with all available commands.
Note that by default, the Arduino resets when the monitor is started (Ctrl Shift M), causing a loss of variables.
This can be addressed by using a capacitor between the reset pin and ground.
A second reason why a capacitor is used is to ensure that a reset is performed when power is applied.
This is necessary for the Ethernet shield I have. Newer versions do not require this reset, but I have an older version that needs a reset at every start.
Ensure the capacitor is disconnected when uploading a new sketch.
Otherwise you get an error that it could not upload. I use a small switch to do that.
In addition to controlling the door, the system monitors the water level with two water level switches indicating if the water is close to empty or completely empty.
When the LED is green, the water level is okay. When it blinks green/red, the water is almost empty, and when it blinks red, the water level is completely empty.
When SERIAL1 is defined, communication can be done via Serial1, enabling connection with a Bluetooth HC-05 module.
If EEPROMModule is defined, some data is written to EEPROM to prevent data loss upon restart.
If EthernetModule is defined and the Arduino is connected to Ethernet, two extra modules become available: MQTTModule for sending MQTT messages to/from the Arduino and NTPModule for synchronizing date/time with internet time.
When MQTT is enabled, the system can be monitored via Home Assistant, adding a convenient touch to this chicken guard.
*/
#define WOKWI // simulator https://wokwi.com/
#if defined ESP32
# define ESP32_DEVKIT_V1
#endif
#include <Arduino.h>
/*
Content of secret.h:
#define WIFISSID "<your wifi ssid>"
#define WIFIPASSWORD "<your wifi password>"
#define MQTTUSER "<your mqtt user>"
#define MQTTPASSWORD "<your mqtt password>"
or when no password for MQTT
#undef MQTTUSER
#undef MQTTPASSWORD
#define UPLOADUSER "<your upload user>"
#define UPLOADPASSWORD "<your upload password>"
*/
#include "secret.h"
#if defined ESP32_DEVKIT_V1
#define WIFI
#endif
#if defined ESP32_DEVKIT_V1 && !defined WOKWI
#define OTA
#define SERIALBT
#endif // ESP32_DEVKIT_V1
#if defined SERIALBT
#include "BluetoothSerial.h"
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif
BluetoothSerial SerialBT;
#endif // SERIALBT
#define ClockModule // If defined then compile with the clock module code
#if defined ESP32_DEVKIT_V1 || !defined WOKWI
# define EthernetModule // If defined then connect to ethernet
#endif
#if !defined WOKWI
# define MQTTModule // If defined then compile with MQTT support - can be used via Home Assistant
#endif
#define NTPModule // If defined then get internet time
#define EEPROMModule // If defined then store data in EEPROM
#if !defined ESP32_DEVKIT_V1
# define SERIAL1 // If defined then also communicate via Serial1 (Bluetooth in my case)
#endif
//#define CONTROLBUILTIN // If set then set the BUILTIN LED
#if !defined EthernetModule
# undef MQTTModule // MQTT can't work without ethernet
# undef NTPModule // NTP can't work without ethernet
#endif
#define postfix "2"
#define myName "ChickenGuard" postfix
#if defined MQTTModule
#define MQTTid "ChickenGuard" postfix
#define MQTTprefix "Chickenguard" postfix
/*
Following must be added to configuration.yaml of HA:
If not, too much events are logged in the logbook
logbook:
exclude:
entities:
- sensor.chickenguard_time_now
- sensor.chickenguard_ldr
- sensor.chickenguard_ldr_average
- sensor.chickenguard_monitor
*/
#endif // MQTTModule
#if defined ESP32_DEVKIT_V1
# define MOTORCLOSEPIN 25
# define MOTOROPENPIN 26
# define MOTORPWMPIN 0 //27 // does not work yet for ESP32. Extra code must be added
# define LEDCLOSEDPIN 32
# define LEDOPENEDPIN 33
# define MAGNETPIN 39
# define LDRPIN 34
# define LEDNOTEMPTYPIN 14
# define LEDEMPTYPIN 13
# define ALMOSTEMPTYPIN 35
# define EMPTYPIN 36
#else
# define MOTORCLOSEPIN 4
# define MOTOROPENPIN 5
# define MOTORPWMPIN 44 // 490 Hz (4 and 13 are 980 Hz but 490 seems to work alot better) - See https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/
# define LEDCLOSEDPIN 6
# define LEDOPENEDPIN 7
# define MAGNETPIN 3
# define LDRPIN A2
# define LEDNOTEMPTYPIN 9
# define LEDEMPTYPIN 8
# define ALMOSTEMPTYPIN 11
# define EMPTYPIN 12
#endif
const int motorClosePin = MOTORCLOSEPIN; // motor turns one way to close the door
const int motorOpenPin = MOTOROPENPIN; // motor turns other way to open the door
const int motorPWMPin = MOTORPWMPIN; // motor PWM pin
const int ledClosedPinInit = LEDCLOSEDPIN;
int ledClosedPin = ledClosedPinInit; // green LED - door closed
int ledOpenedPin = LEDOPENEDPIN; // red LED - door open
const int magnetPin = MAGNETPIN; // magnet switch
const int ldrPin = LDRPIN; // LDR (light sensor)
int ledNotEmptyPin = LEDNOTEMPTYPIN; // green LED - enough water
int ledEmptyPin = LEDEMPTYPIN; // red LED - (almost) empty water
const int almostEmptyPin = ALMOSTEMPTYPIN; // almost empty switch
const int emptyPin = EMPTYPIN; // empty switch
/* Up time */
unsigned int uptimeDays = 0;
unsigned char uptimeHours = 0;
unsigned char uptimeMinutes = 0;
unsigned char uptimeSeconds = 0;
const int ldrOpenNow = 1020; // light value for door open now
const int ldrCloseNow = 0; // light value for door close now
const int nMeasures = 5; // number of measures to do on which an average is made to determine if door is opened (avg >= ldrMorning) or closed (avg <= ldrEvening)
const int measureEverySeconds = 60; // number of seconds between light measurements and descision if door should be closed or opened
// ... for this much of milliseconds and then continue closing the door
const unsigned long waitForInputMaxMs = 900000; // maximum wait time for input (900000 ms = 15 min)
int motorPWM; // motor PWM value - See https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/
int ldrMorning; // light value for door to open
int ldrEvening; // light value for door to close
int openMilliseconds; // number of milliseconds to open door
int closeMilliseconds; // maximal number of milliseconds to close door
int closeWaitTime1; // after this much milliseconds, stop closing door ...
int closeWaitTime2;
int closeWaitTime3;
int status = 0; // status. 0 = all ok
int toggle = 0; // led blinking toggle
bool logit = false; // log to monitor
bool isClosedByMotor; // is door closed by motor
bool keepOpen = false; // keep door forced open
bool keepClosed = false; // keep door forced closed
uint16_t lightMeasures[nMeasures]; // array with light measurements (nMeasures measures taken every measureEverySeconds seconds). Contains the last nMeasures light measures
int measureIndex = 0; // position in lightMeasures for next measurement.
uint16_t ldrMinimum = 9999; // minimum ldr value
uint16_t ldrMaximum = 0; // maximum ldr value
const byte startHourOpen = 7; // minimum hour that door may open
const byte startMinuteOpen = 0; // minimum minute that door may open
bool hasClockModule = false; // Is the clock module detected
#if defined ClockModule
int hourOpened = 0; // hour of last door open
int minuteOpened = 0; // minute of last door open
int secondOpened = 0; // second of last door open
int hourClosed = 0; // hour of last door close
int minuteClosed = 0; // hour of last door close
int secondClosed = 0; // hour of last door close
#else
unsigned long msTime = 0; // millis() value of set time
int dayTime = 0; // set day at msTime
int monthTime = 0; // set month at msTime
int yearTime = 0; // set year at msTime
int hourTime = 0; // set hour at msTime
int minuteTime = 0; // set minute at msTime
int secondsTime = 0; // set seconds at msTime
unsigned long msOpened = 0; // millis() value of last door open
unsigned long msClosed = 0; // millis() value of last door close
#endif
unsigned long timePrevReset = 0; // time when a previous open now or close now was done
bool openPrevReset;
const byte dstDay = 1; // day on which DST is changed. 1 = sunday
const byte dstMinutes = 60; // number of minutes on DST change
const byte summerMonth = 3; // month that summertime begins
const int summerDstDayWeek = -1; // week in summerMonth that summertime begins. Positive means starting from the beginning from the month, negative from the end of the month (-1 = last week)
const byte summerHour = 2; // hour that summertime begins
const byte winterMonth = 10; // month that wintertime begins
const int winterDstDayWeek = -1; // week in winterMonth that summertime begins. Positive means starting from the beginning from the month, negative from the end of the month (-1 = last week)
const byte winterHour = 3; // hour that wintertime begins
bool dstAdjust = true; // Is there still a possible adjust today?
unsigned long PrevTime; // Time to execute every second chicken loop
int measureEverySecond;
bool blinkEmpty; // blink for (almost) empty
unsigned long PrevSyncTime; // previous time a sync was done
#define SyncTime 30 * (24 * 60 * 60 * 1000) // sync every x days
struct
{
char *name;
int *variable;
int initialValue;
} changeableData[] =
{
{ "ldrMorning", &ldrMorning, 600 },
{ "ldrEvening", &ldrEvening, 400 },
{ "motorPWM", &motorPWM, 255 }, // 255 = full speed
{ "openMilliseconds", &openMilliseconds, 1400 },
{ "closeMilliseconds", &closeMilliseconds, 4000 },
{ "closeWaitTime1", &closeWaitTime1, 10000 },
{ "closeWaitTime2", &closeWaitTime2, 4000 },
{ "closeWaitTime3", &closeWaitTime3, 40 },
#if defined ClockModule
{ "hourOpened", &hourOpened, 0 },
{ "minuteOpened", &minuteOpened, 0 },
{ "secondOpened", &secondOpened, 0 },
{ "hourClosed", &hourClosed, 0 },
{ "minuteClosed", &minuteClosed, 0 },
{ "secondClosed", &secondClosed, 0 },
#endif
};
void(* resetFunc) (void) = 0; //declare reset function at address 0
#if defined resetPin
void SetupReset()
{
digitalWrite(resetPin, HIGH); // Set digital pin to HIGH
delay(200);
pinMode(resetPin, OUTPUT); // Make sure it gets the HIGH
delay(200);
pinMode(resetPin, INPUT_PULLUP); // Change this to a pullup resistor such that arduino can still reset itself (for example upload sketch)
}
void DoReset()
{
pinMode(resetPin, OUTPUT); // Change to an output
digitalWrite(resetPin, LOW); // Reset
// Should not get here but if it does undo reset
delay(500);
SetupReset();
resetFunc(); // try a soft reset
}
#else
#define SetupReset()
#if defined ESP32
#define DoReset() ESP.restart()
#else
#define DoReset() resetFunc()
#endif
#endif
void printSerial(char *data)
{
int l = strlen(data);
while (l > 0 && (data[l - 1] == '\r' || data[l - 1] == '\n'))
data[--l] = 0;
Serial.print(data);
# if defined SERIAL1
Serial1.print(data);
# endif
# if defined SERIALBT
SerialBT.print(data);
# endif
}
void printSerialInt(int a)
{
Serial.print(a);
# if defined SERIAL1
Serial1.print(a);
# endif
# if defined SERIALBT
SerialBT.print(a);
# endif
}
void printSerialln(char *data)
{
if (data != NULL)
printSerial(data);
Serial.println();
# if defined SERIAL1
Serial1.println();
# endif
# if defined SERIALBT
SerialBT.println();
# endif
}
void printSerialln()
{
printSerialln(NULL);
}
void showChangeableData(char *name)
{
char buf[50];
bool found = false;
for (int i = 0; i < sizeof(changeableData) / sizeof(*changeableData); i++)
{
if (name == NULL || *name == 0 || strcasecmp(name, changeableData[i].name) == 0)
{
sprintf(buf, "%s = %d", changeableData[i].name, *(changeableData[i].variable));
if (name != NULL && *name)
setMQTTMonitor(buf);
printSerialln(buf);
found = true;
}
}
if (!found)
{
printSerialln("Variable not found in changeable data");
setMQTTMonitor("Variable not found in changeable data");
}
}
void showChangeableData()
{
showChangeableData(NULL);
}
void setChangeableData()
{
for (int i = 0; i < sizeof(changeableData) / sizeof(*changeableData); i++)
*(changeableData[i].variable) = changeableData[i].initialValue;
# if defined EEPROMModule
readChangeableData();
# endif
showChangeableData();
}
// arduino function called when it starts or a reset is done
void setup(void)
{
SetupReset();
# if defined ESP32_DEVKIT_V1
Serial.begin(115200);
# else
Serial.begin(9600);
# endif
Serial.setTimeout(60000);
# if defined SERIAL1
Serial1.begin(9600);
Serial1.setTimeout(60000);
# endif
#if defined SERIALBT
SerialBT.begin(myName); //Bluetooth device name
SerialBT.setTimeout(60000);
#endif
printSerialln("Chicken hatch 15/06/2024. Copyright peno");
setChangeableData();
#if defined ClockModule
hasClockModule = InitClock();
if (hasClockModule)
printSerialln("Clock module found");
else
printSerialln("Clock module not found");
#else
printSerialln("Clock module not included");
#endif
pinMode(motorClosePin, OUTPUT);
pinMode(motorOpenPin, OUTPUT);
if (motorPWMPin != 0)
pinMode(motorPWMPin, OUTPUT);
pinMode(ldrPin, INPUT);
pinMode(magnetPin, INPUT_PULLUP);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
pinMode(ledOpenedPin, OUTPUT);
pinMode(ledClosedPin, OUTPUT);
pinMode(ledNotEmptyPin, OUTPUT);
digitalWrite(ledNotEmptyPin, LOW);
pinMode(ledEmptyPin, OUTPUT);
digitalWrite(ledEmptyPin, LOW);
pinMode(almostEmptyPin, INPUT_PULLUP);
pinMode(emptyPin, INPUT_PULLUP);
// red
digitalWrite(ledOpenedPin, HIGH);
digitalWrite(ledClosedPin, LOW);
# if defined EthernetModule
setupEthernet();
# endif
# if defined WOKWI
delay(5000);
# endif
// yellow
digitalWrite(ledOpenedPin, HIGH);
digitalWrite(ledClosedPin, HIGH);
# if defined OTA
setupOTA();
# endif
# if defined MQTTModule
printSerialln("MQTT enabled");
setupMQTT();
loopEthernet();
loopMQTT(true);
setMQTTDoorStatus("Setup");
setMQTTWaterStatus("Setup");
setMQTTMonitor("Setup");
loopEthernet();
loopMQTT(true);
# else
printSerialln("MQTT not enabled");
# endif
#if defined NTPModule
printSerialln("NTP module enabled");
InitUdp();
# if !defined ClockModule
SyncDateTime();
# endif
#else
printSerialln("NTP module not enabled");
#endif
# if defined WOKWI
delay(5000);
# endif
// green
digitalWrite(ledOpenedPin, LOW);
digitalWrite(ledClosedPin, HIGH);
SetStatusLed(false);
logit = true;
isClosedByMotor = IsClosed();
LightMeasurement(true); // fill the whole light measurement array with the current light value
bool flip = false;
// First 60 seconds chance to enter commands
for (int i = 59; i >= 0; i--)
{
ProcessWater();
flip = !flip;
digitalWrite(ledOpenedPin, flip ? LOW : HIGH);
digitalWrite(ledClosedPin, flip ? HIGH : LOW);
loopEthernet();
loopMQTT(false);
info(i, logit);
setMQTTTime();
switch (Command(true))
{
case 1:
i = 60; // When a command was given then wait again 60 seconds
break;
case 2:
i = 0;
break;
}
delay(1000);
}
setMQTTMonitor("");
logit = false;
printSerialln("Starting");
SetLEDOpenClosed();
Close(false); // Via the magnet switch we know for sure that the door is closed hereafter
LightMeasurement(true); // fill the whole light measurement array with the current light value
ProcessDoor(true, false); // opens the door if light and lets it closed if dark
measureEverySecond = measureEverySeconds;
PrevTime = PrevSyncTime = millis();
uptimeDays = uptimeHours = uptimeMinutes = uptimeSeconds = 0;
}
void SetStatusLed(bool on)
{
#if defined CONTROLBUILTIN
if (on)
digitalWrite(LED_BUILTIN, HIGH);
else
digitalWrite(LED_BUILTIN, LOW);
#endif
}
// Is the door closed according to the magnetic switch
bool IsClosed()
{
return digitalRead(magnetPin) == 0;
}
// stop the motor
void MotorOff(void)
{
digitalWrite(motorClosePin, LOW);
digitalWrite(motorOpenPin, LOW);
}
// run the motor in opening direction
void MotorOpen(bool log)
{
if (motorPWMPin != 0)
analogWrite(motorPWMPin, motorPWM); // See https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/
digitalWrite(motorClosePin, LOW);
digitalWrite(motorOpenPin, HIGH);
if (log)
{
#if defined ClockModule
if (hasClockModule)
{
readDS3231time(&secondOpened, &minuteOpened, &hourOpened, NULL, NULL, NULL, NULL);
# if defined EEPROMModule
writeChangeableData();
# endif
}
#else
msOpened = millis();
#endif
}
}
// run the motor in closing direction
void MotorClose(bool log)
{
if (motorPWMPin != 0)
analogWrite(motorPWMPin, motorPWM);
digitalWrite(motorClosePin, HIGH);
digitalWrite(motorOpenPin, LOW);
if (log)
{
#if defined ClockModule
if (hasClockModule)
{
readDS3231time(&secondClosed, &minuteClosed, &hourClosed, NULL, NULL, NULL, NULL);
# if defined EEPROMModule
writeChangeableData();
# endif
}
#else
msClosed = millis();
#endif
}
}
// close the door
void Close(bool log)
{
printSerialln("Closing door");
if (!IsClosed())
{
MotorClose(log);
// run the motor until magnetic switch detects closed or maximum closeMilliseconds milliseconds (for safety if something goes wrong)
// after closeWaitTime1 the motor stops for closeWaitTime2 milliseconds such that the door is again in rest because the elastic lets it vibrate
int ElapsedTime = 0;
unsigned long StartTime = millis();
bool waited = false;
printSerialln("Closing door 1");
while (!IsClosed() && ElapsedTime < closeMilliseconds)
{
unsigned long CurrentTime = millis();
ElapsedTime = CurrentTime - StartTime; // note that an overflow of millis() is not a problem. ElapsedTime will still be correct
if (!waited && ElapsedTime >= closeWaitTime1)
{
printSerialln("Closing door 2");
MotorOff();
delay(closeWaitTime2);
waited = true;
StartTime += closeWaitTime2;
MotorClose(false);
printSerialln("Closing door 3");
}
}
printSerial("Closing door 4 (ElapsedTime = ");
printSerialInt(ElapsedTime);
printSerialln(")");
MotorOff();
if (ElapsedTime >= closeMilliseconds)
status = 2; // oops, magnetic switch did not detect a door close within closeMilliseconds milliseconds
else
{
status = 3; // door not closed
delay(500); // wait a half of a second
// close the door again to have a good close. This can be done because an elastic is used
// sometimes this must be repeated multiple times because the motor rolls back because of the tension of the elastic. Try max 10 times
for (int i = 0; i < 10 && status != 0; i++)
{
printSerial("Closing door 5 (");
printSerialInt(i);
printSerialln(")");
MotorClose(false);
delay(closeWaitTime3); // close only for 30 milliseconds
MotorOff();
delay(500); // wait a half of a second
// check if door is closed
if (IsClosed())
status = 0; // yes, all ok
else
printSerialln("Door not closed, try again");
}
}
printSerialln("Closing door 6");
SetLEDOff();
if (status == 0)
{
printSerialln("Door closed");
SetLEDOpenClosed();
isClosedByMotor = true;
}
else
printSerialln("Oops, door *not* closed");
}
}
// Open the door
void Open(bool log)
{
printSerialln("Opening door");
if (isClosedByMotor) // If not closed by motor then do nothing
{
if (IsClosed())
{
MotorOpen(log);
// There is no detection on the door when it is open so the motor runs for openMilliseconds milliseconds to open the door
delay(openMilliseconds);
MotorOff();
}
// check if door is now really open (ie not closed)
status = 1; // door not open
// check max 10 times
for (int i = 0; i < 10 && status != 0; i++)
{
delay(500);
if (!IsClosed())
status = 0; // all ok, door is open
else
printSerialln("Door not open, check again");
}
SetLEDOff();
if (status == 0)
{
printSerialln("Door open");
SetLEDOpenClosed();
isClosedByMotor = false;
}
else
printSerialln("Oops, door *not* open");
}
}
uint16_t ReadLDR()
{
uint16_t ldr = analogRead(ldrPin);
# if defined ESP32
ldr >>= 2;
# endif
return ldr;
}
// Do a light measurement
void LightMeasurement(bool init)
{
int counter;
uint16_t ldr = ReadLDR();
if (keepClosed)
ldr = ldrCloseNow;
else if (keepOpen)
ldr = ldrOpenNow;
if (init)
counter = nMeasures;
else
{
counter = 1;
if (ldr > ldrCloseNow && ldr < ldrOpenNow)
{
int i;
for (i = 1; i < nMeasures && lightMeasures[i] == lightMeasures[0]; i++);
if (i >= nMeasures && (lightMeasures[0] <= ldrCloseNow || lightMeasures[0] >= ldrOpenNow))
counter = nMeasures;
}
}
for (; counter >= 1; counter--)
{
//do a light measure
lightMeasures[measureIndex] = ldr;
if (lightMeasures[measureIndex] > ldrMaximum)
ldrMaximum = lightMeasures[measureIndex];
if (lightMeasures[measureIndex] < ldrMinimum)
ldrMinimum = lightMeasures[measureIndex];
measureIndex++;
if (measureIndex >= nMeasures)
measureIndex = 0;
}
}
// Calculation on light measurements
void LightCalculation(uint16_t &average, uint16_t &minimum, uint16_t &maximum, bool ahead = false)
{
uint16_t ldr;
// calculate the average of the array and the minimal and maximum value
average = 0;
minimum = ~0;
maximum = 0;
for (int counter = nMeasures - 1; counter >= 0; counter--)
{
if (!ahead || counter != measureIndex)
ldr = lightMeasures[counter];
else
ldr = ReadLDR();
average += ldr;
if (ldr > maximum)
maximum = ldr;
if (ldr < minimum)
minimum = ldr;
}
average /= nMeasures;
}
void checkReset(bool open)
{
unsigned long timeNow = millis();
if (timeNow == 0)
timeNow++;
if (timePrevReset != 0 &&
open != openPrevReset &&
timeNow - timePrevReset < 5000)
{
isClosedByMotor = IsClosed();
status = 0;
}
if (open != openPrevReset || timePrevReset == 0)
{
openPrevReset = open;
timePrevReset = timeNow;
}
}
// check if door must be opened or closed and do it if needed
int ProcessDoor(bool mayOpen, bool log)
{
uint16_t average, minimum, maximum;
int ret = 0;
LightCalculation(average, minimum, maximum);
setMQTTLDRavg((int)average);
setMQTTTemperature();
int ldr = ReadLDR();
setMQTTLDR(ldr);
if (ldr <= ldrCloseNow || keepClosed)
{
checkReset(false);
average = 0;
LightMeasurement(true); // fill the whole light measurement array with the current light value
}
else if (ldr >= ldrOpenNow || keepOpen)
{
checkReset(true);
average = ldrOpenNow;
LightMeasurement(true); // fill the whole light measurement array with the current light value
}
else
timePrevReset = 0;
bool isClosed = IsClosed();
#if defined ClockModule
static int hourOpened2 = 0; // hour of last door open
static int minuteOpened2 = 0; // minute of last door open
static int secondOpened2 = 0; // second of last door open
if (isClosed)
hourOpened2 = minuteOpened2 = secondOpened2 = 0;
else if (hasClockModule && hourOpened2 == 0 && minuteOpened2 == 0 && secondOpened2 == 0)
readDS3231time(&secondOpened2, &minuteOpened2, &hourOpened2, NULL, NULL, NULL, NULL);
#endif
char *ptr = (char *)(status == 0 ? isClosed ? "Door closed" : "Door open" : status == 1 ? "Door should be open but is still closed" : status == 2 ? "Door not closed after timeout" : "Door not closed after 10 tries to tighten");
if (status != 0)
{
char data[100];
sprintf(data, "Something is not ok (%d - %s); idle", status, ptr);
printSerialln(data);
#if defined ClockModule
printSerial(" Time open: ");
ShowTime(&hourOpened, &minuteOpened, &secondOpened);
printSerialln();
printSerial(" Time actually open: ");
ShowTime(&hourOpened2, &minuteOpened2, &secondOpened2);
printSerialln();
#endif
}
else if (isClosed && !isClosedByMotor)
// door is manually closed instead of automatically with the motor. Do nothing until manually opened again
;
// If the average is less than ldrEvening and the door is not closed then close it
else if (average <= ldrEvening && !isClosed)
{
Close(log);
ret = motorClosePin;
}
// If the average is greater than ldrMorning and the door may open and it is closed and it may open by time then open it
else if (average >= ldrMorning && isClosed && (average == ldrOpenNow || (mayOpen && MayOpen(0))))
{
Open(log);
ret = motorOpenPin;
}
// Else if the minimum ldr value is smaller than ldrEvening, but the average isn't (yet) and the door is not closed (open) then it is about time to close it
else if (minimum <= ldrEvening && !isClosed)
ret = ledClosedPin;
// Else if the maximum ldr value is greater than ldrMorning, but the average isn't (yet) and the door is closed and the time is later than a couple of minutes before may open then it is about time to open it
else if (maximum >= ldrMorning && isClosed && MayOpen(-nMeasures / 2))
ret = ledOpenedPin;
if (keepOpen || keepClosed)
ret = 0;
else if (ret == motorOpenPin)
ptr = "Door opening";
else if (ret == motorClosePin)
ptr = "Door closing";
else if (ret == ledOpenedPin)
ptr = "Door closed, about time to open it";
else if (ret == ledClosedPin)
ptr = "Door open, about time to close it";
setMQTTDoorStatus(ptr);
setMQTTTime();
return ret;
}
// Check if the current time is later than the may open time - deltaMinutes
bool MayOpen(int deltaMinutes)
{
int hour, minute, second;
GetTime(hour, minute, second);
int startHourOpen1 = startHourOpen;
int startMinuteOpen1 = startMinuteOpen + deltaMinutes;
if (startMinuteOpen1 < 0)
{
startHourOpen1--;
startMinuteOpen1 += 60;
}
return (hour > startHourOpen1 || (hour == startHourOpen1 && minute >= startMinuteOpen1));
}
void loop(void)
{
loopEthernet();
loopMQTT(false);
loopOTA();
unsigned long CurrentTime = millis();
if (CurrentTime - PrevTime >= 1000) // every second
{
PrevTime = CurrentTime;
if (++uptimeSeconds == 60)
{
uptimeSeconds = 0;
if (++uptimeMinutes == 60)
{
uptimeMinutes = 0;
if (++uptimeHours == 24)
{
uptimeHours = 0;
uptimeDays++;
}
}
}
setUpTime();
ProcessWater();
measureEverySecond--;
int ret = ProcessDoor(false, true);
if (status != 0)
{
// Oops something is wrong. Blink the red led as many times as the error code and then 2 seconds off and then start again
if (++toggle > status * 2)
toggle = 0;
SetStatusLed(toggle % 2 != 0);
digitalWrite(ledClosedPin, toggle == status * 2 ? HIGH : LOW);
digitalWrite(ledOpenedPin, toggle % 2 == 0 ? LOW : HIGH);
}
else if (ret == ledClosedPin || ret == ledOpenedPin)
{
// The door is about to close or open
// Blink the corresponding led every second
if (++toggle > 1)
toggle = 0;
digitalWrite(ret, toggle == 0 ? LOW : HIGH);
digitalWrite(ret == ledClosedPin ? ledOpenedPin : ledClosedPin, LOW);
}
else
SetLEDOpenClosed();
info(measureEverySecond, logit);
Command(false);
if (measureEverySecond == 0)
{
// every minute
DSTCorrection();
LightMeasurement(false);
ProcessDoor(isClosedByMotor == IsClosed(), true); // If the door is manually closed then don't try to open
measureEverySecond = measureEverySeconds;
}
}
#if defined NTPModule
if (CurrentTime - PrevSyncTime >= SyncTime)
{
PrevSyncTime = CurrentTime;
//SyncDateTime();
}
#endif
}
void ProcessWater()
{
if (digitalRead(emptyPin)) // empty open
{
// blink red
blinkEmpty = !blinkEmpty;
digitalWrite(ledEmptyPin, blinkEmpty ? HIGH : LOW);
digitalWrite(ledNotEmptyPin, LOW);
setMQTTWaterStatus("Empty");
}
else if (digitalRead(almostEmptyPin)) // almost empty open
{
// blink red/green
blinkEmpty = !blinkEmpty;
digitalWrite(ledEmptyPin, blinkEmpty ? HIGH : LOW);
digitalWrite(ledNotEmptyPin, blinkEmpty ? LOW : HIGH);
setMQTTWaterStatus("Low");
}
else
{
// green
digitalWrite(ledNotEmptyPin, HIGH);
digitalWrite(ledEmptyPin, LOW);
setMQTTWaterStatus("Ok");
}
#if 0
printSerial("Almost empty: ");
printSerialln(digitalRead(almostEmptyPin));
printSerial("Empty: ");
printSerialln(digitalRead(emptyPin));
#endif
}
void SetLEDOff()
{
digitalWrite(ledOpenedPin, LOW);
digitalWrite(ledClosedPin, LOW);
}
void SetLEDOpenClosed()
{
if (IsClosed())
{
digitalWrite(ledOpenedPin, LOW);
digitalWrite(ledClosedPin, HIGH);
}
else
{
digitalWrite(ledClosedPin, LOW);
digitalWrite(ledOpenedPin, HIGH);
}
}
void info(int measureEverySecond, bool withDayOfWeek, char *buf)
{
if (measureEverySecond >= 0)
sprintf(buf + strlen(buf), "%d: ", measureEverySecond);
uint16_t average, minimum, maximum;
LightCalculation(average, minimum, maximum);
sprintf(buf + strlen(buf), "LDR: %d, ~: %d", ReadLDR(), average);
LightCalculation(average, minimum, maximum, true);
sprintf(buf + strlen(buf), ", ~~: %d, Door is %s (%s), Open LDR: %d, Close LDR: %d, Status: %d", average, IsClosed() ? "closed" : "open", isClosedByMotor ? "closed" : "open", ldrMorning, ldrEvening, status);
if (hasClockModule)
{
#if defined ClockModule
int second, minute, hour;
byte dayOfWeek;
// retrieve data from DS3231
readDS3231time(&second, &minute, &hour, &dayOfWeek, NULL, NULL, NULL);
strcat(buf, ", Time now: ");
ShowTime(&hour, &minute, &second, withDayOfWeek ? &dayOfWeek : NULL, buf + strlen(buf));
strcat(buf, ", Time open: ");
ShowTime(&hourOpened, &minuteOpened, &secondOpened, buf + strlen(buf));
strcat(buf, ", Time closed: ");
ShowTime(&hourClosed, &minuteClosed, &secondClosed, buf + strlen(buf));
#endif
}
#if !defined ClockModule
else
{
unsigned long timeNow = millis();
strcat(buf, ", Time now: ");
ShowTime(timeNow, timeNow, buf + strlen(buf));
strcat(buf, ", Time open: ");
ShowTime(msOpened, timeNow, buf + strlen(buf));
strcat(buf, ", Time closed: ");
ShowTime(msClosed, timeNow, buf + strlen(buf));
}
#endif
}
void info(int measureEverySecond, bool dolog)
{
if (dolog)
{
char buf[255] = { 0 };
info(measureEverySecond, false, (char *) buf);
printSerialln(buf);
setMQTTMonitor(buf);
}
}
#if defined ClockModule
void ShowTime(int *hour, int *minute, int *second, byte *dayOfWeek, char *buf)
{
char *buffer[15];
if (buf == NULL)
buf = (char *)buffer;
sprintf(buf, "%d:%02d", *hour, *minute);
if (second != NULL)
sprintf(buf + strlen(buf), ":%02d", *second);
if (dayOfWeek != NULL)
sprintf(buf + strlen(buf), " (%d)", *dayOfWeek);
if (buf == (char *)buffer)
printSerial(buf);
}
void ShowTime(int *hour, int *minute, int *second, char *buf)
{
ShowTime(hour, minute, second, NULL, buf);
}
void ShowTime(int *hour, int *minute, int *second)
{
ShowTime(hour, minute, second, NULL);
}
#endif
void GetTime(int &hour, int &minute, int &second)
{
minute = 0;
hour = 24;
if (hasClockModule)
{
#if defined ClockModule
// retrieve data from DS3231
readDS3231time(&second, &minute, &hour, NULL, NULL, NULL, NULL);
#endif
}
#if !defined ClockModule
else if (msTime != 0)
{
unsigned long timeNow = millis();
GetTime(timeNow, timeNow, hour, minute, second);
}
#endif
}
#if !defined ClockModule
void GetTime(unsigned long time, unsigned long timeNow, int &hour, int &minute, int &second)
{
byte year, month, day;
GetTime(time, timeNow, year, month, day, hour, minute, second);
}
void GetTime(unsigned long time, unsigned long timeNow, byte &year, byte &month, byte &day, int &hour, int &minute, int &second)
{
if (msTime != 0 && time >= msTime)
{
unsigned long ms = time - msTime;
unsigned long days = ms / 1000 / 60 / 60 / 24;
ms -= days * 24 * 60 * 60 * 1000;
unsigned long hours = ms / 1000 / 60 / 60;
ms -= hours * 60 * 60 * 1000;
unsigned long minutes = ms / 1000 / 60;
ms -= minutes * 60 * 1000;
unsigned long seconds = ms / 1000;
unsigned long D1 = dayTime + days;
unsigned long M1 = monthTime;
unsigned long Y1 = yearTime;
unsigned long h1 = hourTime + hours;
unsigned long m1 = minuteTime + minutes;
unsigned long s1 = secondsTime + seconds;
while (s1 >= 60)
{
m1++;
s1 -= 60;
}
while (m1 >= 60)
{
h1++;
m1 -= 60;
}
while (h1 >= 24)
{
h1 -= 24;
D1++;
}
while (D1 > (unsigned long)daysInMonth(Y1, M1))
{
D1 -= daysInMonth(Y1, M1);
M1++;
if (M1 > 12)
{
Y1++;
M1 = 1;
}
}
year = (byte)Y1;
month = (byte)M1;
day = (byte)D1;
hour = (byte)h1;
minute = (byte)m1;
second = (byte)s1;
}
else
year = month = day = hour = minute = second = 0;
}
void ShowTime(unsigned long time, unsigned long timeNow, char *buf)
{
char data[100];
if (buf == NULL)
buf = data;
if (msTime == 0)
{
sprintf(buf, "%lu", timeNow);
if (timeNow > time)
{
unsigned long ms = timeNow - time;
unsigned long days = ms / 1000 / 60 / 60 / 24;
ms -= days * 24 * 60 * 60 * 1000;
unsigned long hours = ms / 1000 / 60 / 60;
ms -= hours * 60 * 60 * 1000;
unsigned long minutes = ms / 1000 / 60;
ms -= minutes * 60 * 1000;
unsigned long seconds = ms / 1000;
ms -= seconds * 1000;
strcat(buf, " (");
sprintf(buf + strlen(buf), "%dd%d:%02d:%02d:%03d", (int)days, (int)hours, (int)minutes, (int)seconds, (int)ms);
strcat(buf, ")");
}
}
else
{
*buf = 0;
if (time >= msTime)
{
int hour, minute, second;
GetTime(time, timeNow, hour, minute, second);
sprintf(buf + strlen(buf), "%02d:%02d:%02d", (int)hour, (int)minute, (int)second);
}
else
strcat(buf, "-");
}
if (buf == data)
printSerial(buf);
}
void ShowTime(unsigned long time, unsigned long timeNow)
{
ShowTime(time, timeNow, NULL);
}
#endif
String WaitForInput(char *question)
{
printSerialln(question);
unsigned long StartTime = millis();
unsigned long ElapsedTime = 0;
while (!Serial.available()
# if defined SERIAL1
&& !Serial1.available()
# endif
# if defined SERIALBT
&& !SerialBT.available()
# endif
&& ElapsedTime < waitForInputMaxMs)
{
// wait for input
unsigned long CurrentTime = millis();
ElapsedTime = CurrentTime - StartTime; // note that an overflow of millis() is not a problem. ElapsedTime will still be correct
}
return ElapsedTime >= waitForInputMaxMs ? "" :
Serial.available() ? Serial.readStringUntil(10) :
# if defined SERIAL1
Serial1.available() ? Serial1.readStringUntil(10) :
# endif
# if defined SERIALBT
SerialBT.available() ? SerialBT.readStringUntil(10) :
# endif
"";
}
int Command(bool start)
{
if (logit)
printSerialln("Waiting for command ");
if (Serial.available()
# if defined SERIAL1
|| Serial1.available()
# endif
# if defined SERIALBT
|| SerialBT.available()
# endif
)
{
String answer;
answer = WaitForInput("");
answer.toUpperCase();
if (answer.substring(0, 5) == "START")
return 2;
Command(answer, true, start);
return 1;
}
return 0;
}
void setChangeableValue(char *name, char *svalue)
{
int value;
bool found = false;
printSerial("Setting value for variable ");
printSerial(name);
printSerial(" to ");
printSerialln(svalue);
for (int i = 0; i < sizeof(changeableData) / sizeof(*changeableData); i++)
{
if (strcasecmp(name, changeableData[i].name) == 0)
{
value = atoi(svalue);
*(changeableData[i].variable) = value;
#if defined EEPROMModule
writeChangeableData();
#endif
found = true;
break;
}
}
if (!found)
{
printSerialln("Variable not found in changeable data");
setMQTTMonitor("Variable not found in changeable data");
}
}
void swap(int &i1, int &i2)
{
int i = i1;
i1 = i2;
i2 = i;
}
void Command(String answer, bool wait, bool start)
{
answer.toUpperCase();
printSerial("Received: ");
printSerialln((char *)answer.c_str());
if (answer.substring(0, 2) == "AT") // current date/time arduino: dd/mm/yy hh:mm:ss
{
#if !defined ClockModule
if (answer[2])
{
int day = answer.substring(2, 4).toInt();
int month = answer.substring(5, 7).toInt();
int year = answer.substring(8, 10).toInt();
int hour = answer.substring(11, 13).toInt();
int minute = answer.substring(14, 16).toInt();
int sec = answer.substring(17, 19).toInt();
if (day != 0 && month != 0 && year != 0)
{
msTime = millis();
dayTime = day;
monthTime = month;
yearTime = year;
hourTime = hour;
minuteTime = minute;
secondsTime = sec;
}
}
byte dayOfMonth, month, year;
int hour, minute, second;
char data[30];
unsigned long timeNow = millis();
GetTime(timeNow, timeNow, year, month, dayOfMonth, hour, minute, second);
sprintf(data, "%02d/%02d/%02d %02d:%02d:%02d", (int)dayOfMonth, (int)month, (int)year, (int)hour, (int)minute, (int)second);
printSerialln(data);
setMQTTMonitor(data);
if (logit && wait)
WaitForInput("Press enter to continue");
#endif
}
else if (answer.substring(0, 2) == "CT" && hasClockModule) // current date/time clock module: dd/mm/yy hh:mm:ss
{
#if defined ClockModule
if (answer[2])
{
int day = answer.substring(2, 4).toInt();
int month = answer.substring(5, 7).toInt();
int year = answer.substring(8, 10).toInt();
int hour = answer.substring(11, 13).toInt();
int minute = answer.substring(14, 16).toInt();
int sec = answer.substring(17, 19).toInt();
if (day != 0 && month != 0 && year != 0)
setDS3231time(sec, minute, hour, 0, day, month, year);
}
printDS3231time();
if (logit && wait)
WaitForInput("Press enter to continue");
#endif
}
else if (answer.substring(0, 1) == "O") // open
{
Open(false);
keepOpen = true;
keepClosed = false;
LightMeasurement(true);
if (logit && wait)
WaitForInput("Press enter to continue");
}
else if (answer.substring(0, 1) == "C") // close
{
Close(false);
keepClosed = true;
keepOpen = false;
LightMeasurement(true);
if (logit && wait)
WaitForInput("Press enter to continue");
}
else if (answer.substring(0, 1) == "A") // auto
{
keepOpen = keepClosed = false;
LightMeasurement(true); // fill the whole light measurement array with the current light value
ProcessDoor(true, false); // opens the door if light and lets it closed if dark
if (logit && wait)
WaitForInput("Press enter to continue");
}
else if (answer.substring(0, 2) == "SL")
{
int ledClosedPinValue = digitalRead(ledClosedPin);
int ledOpenedPinValue = digitalRead(ledOpenedPin);
int ledNotEmptyPinValue = digitalRead(ledNotEmptyPin);
int ledEmptyPinValue = digitalRead(ledEmptyPin);
swap(ledClosedPin, ledNotEmptyPin);
swap(ledOpenedPin, ledEmptyPin);
digitalWrite(ledClosedPin, ledClosedPinValue);
digitalWrite(ledOpenedPin, ledOpenedPinValue);
digitalWrite(ledNotEmptyPin, ledNotEmptyPinValue);
digitalWrite(ledEmptyPin, ledEmptyPinValue);
if (ledClosedPin == ledClosedPinInit)
{
printSerialln("LEDs normal");
setMQTTMonitor("LEDs normal");
}
else
{
printSerialln("LEDs switched");
setMQTTMonitor("LEDs switched");
}
if (logit && wait)
WaitForInput("Press enter to continue");
}
#if defined NTPModule
else if (answer.substring(0, 4) == "SYNC")
{
SyncDateTime();
if (logit && wait)
WaitForInput("Press enter to continue");
}
#endif
else if (answer.substring(0, 4) == "LET ")
{
char *ptr, *value, *variable = (char *)answer.c_str() + 4;
while (*variable == ' ')
variable++;
for (ptr = variable; *ptr && *ptr != '='; ptr++);
if (*ptr)
{
*ptr = 0;
value = ptr + 1;
while (--ptr >= variable && *ptr == ' ')
*ptr = 0;
while (*value == ' ')
value++;
ptr = value + strlen(value);
while (--ptr >= value && *ptr == ' ')
*ptr = 0;
setChangeableValue(variable, value);
showChangeableData(variable);
}
if (logit && wait)
WaitForInput("Press enter to continue");
}
else if (answer.substring(0, 3) == "GET")
{
char *ptr, *value, *variable;
variable = (char *) (answer.length() > 3 ? answer.c_str() + 4 : NULL);
if (variable != NULL)
{
while (*variable == ' ')
variable++;
ptr = variable + strlen(variable);
while (--ptr >= variable && *ptr == ' ')
*ptr = 0;
}
showChangeableData(variable);
if (logit && wait)
WaitForInput("Press enter to continue");
}
else if (answer.substring(0, 1) == "L") // log toggle
{
logit = !logit;
if (!logit)
setMQTTMonitor("");
}
else if (answer.substring(0, 1) == "S") // reset status
{
status = answer.substring(1).toInt();
}
else if (answer.substring(0, 5) == "RESET") // reset
{
DoReset(); //call reset
}
else if (answer.substring(0, 1) == "R") // repeat
{
int x = answer.substring(1).toInt();
bool isClosedByMotor = IsClosed();
for (int i = 0; i < x; i++)
{
Close(false);
delay(5000);
Open(false);
delay(5000);
}
if (isClosedByMotor)
Close(false);
if (logit && wait)
WaitForInput("Press enter to continue");
}
else if (answer.substring(0, 1) == "T") // temperature
{
#if defined ClockModule
char buf[50];
strcpy(buf, "Temperature: ");
dtostrf(readTemperature(), 5, 2, buf + strlen(buf));
strcat(buf, " °C");
printSerialln(buf);
setMQTTMonitor(buf);
if (logit && wait)
WaitForInput("Press enter to continue");
#endif
}
#if defined EthernetModule
else if (answer.substring(0, 2) == "IP")
{
printLocalIP();
if (logit && wait)
WaitForInput("Press enter to continue");
}
else if (answer.substring(0, 2) == "IS")
{
printEthernetStatus();
if (logit && wait)
WaitForInput("Press enter to continue");
}
#endif
else if (answer.substring(0, 1) == "I") // info
{
char buf[512] = { 0 };
info(-1, true, (char *) buf);
strcat(buf, "\r\nMeasurements:");
int measureIndex0 = measureIndex;
for (int counter = nMeasures - 1; counter >= 0; counter--)
{
measureIndex0 = (measureIndex0 > 0 ? measureIndex0 : nMeasures) - 1;
sprintf(buf + strlen(buf), " %d", lightMeasures[measureIndex0]);
}
strcat(buf, "\r\n");
uint16_t average, minimum, maximum;
LightCalculation(average, minimum, maximum);
sprintf(buf + strlen(buf), "Avg: %d, Min: %d, Max: %d", average, minimum, maximum);
strcat(buf, "\r\n");
sprintf(buf + strlen(buf), "@ Min: %d, Max: %d", ldrMinimum, ldrMaximum);
printSerialln(buf);
setMQTTMonitor(buf);
if (logit && wait)
WaitForInput("Press enter to continue");
}
else if (answer.substring(0, 1) == "H") // help
{
printSerialln("O: Open door");
printSerialln("C: Close door");
printSerialln("A: Auto door");
printSerialln("S(x): Reset status to x (default 0)");
printSerialln("R<times>: Repeat opening and closing door");
printSerialln("I: Info");
printSerialln("L: Log toggle");
printSerialln("SL: Switch LEDs");
#if !defined ClockModule
printSerialln("AT<dd/mm/yy hh:mm:ss>: set arduino timer date/time");
#endif
#if defined ClockModule
printSerialln("CT<dd/mm/yy hh:mm:ss>: Set clockmodule date/time");
#endif
#if defined NTPModule
printSerialln("SYNC: Synchronize date/time with NTP server");
#endif
#if defined ClockModule
printSerialln("T: Temperature");
#endif
#if defined EthernetModule
printSerialln("IP: Print IP address");
printSerialln("IS: Show Ethernet status");
#endif
printSerialln("RESET: Reset Arduino");
printSerialln("LET name=value: Set variable value");
printSerialln("GET {name}: Get variable value");
if (start)
printSerialln("START: Start loop");
if (logit && wait)
WaitForInput("Press enter to continue");
}
}
bool isLeapYear(int year)
{
return (((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0);
}
int dayofweek(int year, int month, int day)
{
static byte t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
year -= month < 3;
return (year + year / 4 - year / 100 + year / 400 + t[month - 1] + day) % 7;
}
int daysInMonth(int year, int month)
{
static byte days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
return days[month - 1] + (month == 2 && isLeapYear(year) ? 1 : 0);
}
void DSTCorrection()
{
int second, minute, hour;
byte dayOfWeek, dayOfMonth, month, year;
#if defined ClockModule
if (hasClockModule)
readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
else
#endif
{
#if !defined ClockModule
unsigned long timeNow = millis();
GetTime(timeNow, timeNow, year, month, dayOfMonth, hour, minute, second);
dayOfWeek = dayofweek(2000 + year, month, dayOfMonth) + 1;
#endif
}
if (dayOfWeek != dstDay)
dstAdjust = true;
else if (dstAdjust)
{
int dstDayWeek = 0;
int adjustMinutes = 0;
if (month == summerMonth && hour == summerHour)
{
dstDayWeek = summerDstDayWeek;
adjustMinutes = +dstMinutes;
}
else if (month == winterMonth && hour == winterHour)
{
dstDayWeek = winterDstDayWeek;
adjustMinutes = -dstMinutes;
}
if (adjustMinutes != 0)
{
int weekDays = 7;
int lastDay = daysInMonth(year, month);
int firstDay = summerDstDayWeek > 0 ? 1 : lastDay - weekDays + 1;
lastDay = summerDstDayWeek > 0 ? weekDays : lastDay;
if (dstDayWeek < 0)
{
dstDayWeek = -dstDayWeek;
weekDays = -weekDays;
}
int day = ((int)dayOfMonth) - (dstDayWeek - 1) * weekDays;
if (day >= firstDay && day <= lastDay)
{
#if defined ClockModule
if (hasClockModule)
{
readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
minute += adjustMinutes % 60;
while (minute >= 60)
{
hour++;
minute -= 60;
}
while (minute < 0)
{
hour--;
minute += 60;
}
hour += adjustMinutes / 60;
setDS3231time(second, minute, hour, dayOfWeek, dayOfMonth, month, year);
}
else
#endif
{
#if !defined ClockModule
minuteTime += adjustMinutes % 60;
while (minuteTime >= 60)
{
hourTime++;
minuteTime -= 60;
}
hourTime += adjustMinutes / 60;
#endif
}
dstAdjust = false;
}
}
}
}
#if defined ClockModule
#include <Wire.h>
#define DS3231_I2C_ADDRESS 0x68
byte decToBcd(byte val)
{ // Convert normal decimal numbers to binary coded decimal
return ((val / 10 * 16) + (val % 10));
}
byte bcdToDec(byte val)
{ // Convert binary coded decimal to normal decimal numbers
return ((val / 16 * 10) + (val % 16));
}
bool InitClock()
{
Wire.begin();
int sec, minute, hour;
readDS3231time(&sec, &minute, &hour, NULL, NULL, NULL, NULL);
return sec < 60 && minute < 60 && hour < 24;
}
void setDS3231time(int second, int minute, int hour, byte dayOfWeek, byte dayOfMonth, byte month, byte year)
{
//if (dayOfWeek == 0)
dayOfWeek = dayofweek(2000 + year, month, dayOfMonth) + 1;
// sets time and date data to DS3231
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0); // set next input to start at the seconds register
Wire.write(decToBcd(second)); // set seconds
Wire.write(decToBcd(minute)); // set minutes
Wire.write(decToBcd(hour)); // set hours
Wire.write(decToBcd(dayOfWeek)); // set day of week (1=Sunday, 7=Saturday)
Wire.write(decToBcd(dayOfMonth)); // set date (1 to 31)
Wire.write(decToBcd(month)); // set month
Wire.write(decToBcd(year)); // set year (0 to 99)
Wire.endTransmission();
}
void readDS3231time(int *second,
int *minute,
int *hour,
byte *dayOfWeek,
byte *dayOfMonth,
byte *month,
byte *year)
{
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0); // set DS3231 register pointer to 00h
Wire.endTransmission();
Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
// request seven bytes of data from DS3231 starting from register 00h
byte b, d, m, y;
b = bcdToDec(Wire.read() & 0x7f);
if (second != NULL)
*second = b;
b = bcdToDec(Wire.read());
if (minute != NULL)
*minute = b;
b = bcdToDec(Wire.read() & 0x3f);
if (hour != NULL)
*hour = b;
b = bcdToDec(Wire.read());
if (dayOfWeek != NULL)
{
*dayOfWeek = b;
if (dayOfMonth == NULL)
dayOfMonth = &d;
if (month == NULL)
month = &m;
if (year == NULL)
year = &y;
}
b = bcdToDec(Wire.read());
if (dayOfMonth != NULL)
*dayOfMonth = b;
b = bcdToDec(Wire.read());
if (month != NULL)
*month = b;
b = bcdToDec(Wire.read());
if (year != NULL)
*year = b;
if (dayOfWeek != NULL)
*dayOfWeek = dayofweek(2000 + *year, *month, *dayOfMonth) + 1;
}
void printDS3231time()
{
int second, minute, hour;
byte dayOfWeek, dayOfMonth, month, year;
char data[30];
readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
sprintf(data, "%02d/%02d/%02d %02d:%02d:%02d", (int)dayOfMonth, (int)month, (int)year, (int)hour, (int)minute, (int)second);
printSerialln(data);
}
float readTemperature()
{
float temperature;
if (hasClockModule)
{
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0x11); // register address for the temperature
Wire.endTransmission();
Wire.requestFrom(DS3231_I2C_ADDRESS, 2); // get 2 bytes
int MSB = Wire.read(); // 2's complement int portion
int LSB = Wire.read(); // fraction portion
temperature = MSB & 0x7F; // do 2's moth on MSB
temperature = temperature + (LSB >> 6) * 0.25; // only care about bits 7 and 8
}
else
temperature = 0.0;
return temperature;
}
#endif /* ClockModule */
#if defined EthernetModule
const unsigned long waitReconnectEthernet = 5L * 60L * 1000L; /* 5 minutes */
const int maxDurationEthernetConnect = 5000; /* 5 seconds */
bool hasEthernet = false;
unsigned long prevEthernetCheck = 0;
#if defined WIFI
#include <WiFi.h>
#include <WiFiClient.h>
#if defined OTA
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>
#endif // OTA
#elif defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__)
// Nano
#include <EthernetENC.h> // uses a bit less memory
//#include <UIPEthernet.h> // uses a bit more memory
#else
// Mega
#include <Ethernet.h> // does not work with an Arduino nano and its internet shield because it uses ENC28J60 which is a different instruction set
#endif
#if defined WIFI
WiFiClient client;
byte mac[] = { 0xa8, 0x42, 0xe3, 0x9e, 0xb0, 0x00 };
#else
byte mac[] = { 0x54, 0x34, 0x41, 0x30, 0x30, 0x32 };
EthernetClient client;
#endif
#endif /* EthernetModule */
void setupEthernet()
{
#if defined WIFI
delay(10);
printSerialln();
printSerial("Connecting to ");
printSerialln((char*)WIFISSID);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFISSID, WIFIPASSWORD);
unsigned long t1 = millis();
while (WiFi.status() != WL_CONNECTED) {
unsigned long t2 = millis();
if (t2 - t1 > 1 * 60 * 1000) // try 10 minutes
DoReset();
delay(500);
printSerial(".");
}
hasEthernet = true;
printSerialln();
printSerialln("WiFi connected");
IPAddress ip = WiFi.localIP();
char buf[50];
sprintf(buf, "IP address: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
printSerialln(buf);
WiFi.macAddress(mac);
sprintf(buf, "MAC address: %02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
printSerialln(buf);
sprintf(buf, "RRSI: %d", (int)WiFi.RSSI());
printSerialln(buf);
#elif defined EthernetModule
int ret;
printSerialln("Start Ethernet");
ret = Ethernet.begin(mac);
hasEthernet = (ret == 1);
if (!hasEthernet)
prevEthernetCheck = millis();
else
prevEthernetCheck = 0;
printSerial("Done Ethernet: ");
printSerial(hasEthernet ? "OK" : "NOK: ");
if (!hasEthernet)
printSerial(ret);
printSerialln();
printSerial("IP: ");
printLocalIP();
#endif
}
void loopEthernet()
{
#if defined WIFI
#elif defined EthernetModule
unsigned long CurrentTime1 = millis();
if (prevEthernetCheck == 0 /* If previous connection was ok */ ||
CurrentTime1 - prevEthernetCheck > waitReconnectEthernet /* If waitReconnectEthernet time is passed, try again to connect */)
{
//printSerialln("Ethernet.maintain start");
Ethernet.maintain();
//printSerialln("Ethernet.maintain done");
unsigned long CurrentTime2 = millis();
if (CurrentTime2 - CurrentTime1 > maxDurationEthernetConnect) /* If connecting to ethernet takes more than maxDurationEthernetConnect time then there is probably no ethernet. In this case we will wait 5 minutes before trying again or else everything is slowed down too much */
{
hasEthernet = false;
prevEthernetCheck = CurrentTime2;
printSerialln("No Ethernet...");
}
else if (prevEthernetCheck != 0)
{
hasEthernet = true;
prevEthernetCheck = 0;
printSerialln("Ethernet restored");
}
else
hasEthernet = true;
}
#endif
}
void printLocalIP()
{
#if defined EthernetModule
# if defined WIFI
IPAddress ip = WiFi.localIP();
#else
IPAddress ip = Ethernet.localIP();
#endif
char sip[16];
sprintf(sip, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
printSerialln(sip);
setMQTTMonitor(sip);
#endif
}
void printEthernetStatus()
{
#if defined EthernetModule
if (hasEthernet)
printSerialln("Ethernet ok");
else
{
printSerialln("Ethernet nok");
unsigned long CurrentTime1 = millis();
unsigned long nextCheck;
if (prevEthernetCheck == 0 /* If previous connection was ok */ ||
CurrentTime1 - prevEthernetCheck > waitReconnectEthernet /* If waitReconnectEthernet time is passed, try again to connect */)
nextCheck = 0;
else
nextCheck = waitReconnectEthernet - (CurrentTime1 - prevEthernetCheck);
char buf[50];
sprintf(buf, "Next check in %d seconds", (int) (nextCheck / 1000));
printSerialln(buf);
}
#else
printSerialln("No Ethernet connection");
#endif
}
#if defined OTA
#define UPDATEPAGE "/jsdlmkfjcnsdjkqcfjdlkckslcndsjfsdqfjksd" // a name that nobody can figure out
const char *uploadContent =
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
"<input type='file' name='update'>"
"<br>"
"<br>"
"<input type='submit' value='Update'>"
"</form>"
"<div id='prg'>progress: 0%</div>"
"<script>"
"$('form').submit(function(e){"
"e.preventDefault();"
"var form = $('#upload_form')[0];"
"var data = new FormData(form);"
" $.ajax({"
"url: '" UPDATEPAGE "',"
"type: 'POST',"
"data: data,"
"contentType: false,"
"processData:false,"
"xhr: function() {"
"var xhr = new window.XMLHttpRequest();"
"xhr.upload.addEventListener('progress', function(evt) {"
"if (evt.lengthComputable) {"
"var per = evt.loaded / evt.total;"
"$('#prg').html('progress: ' + Math.round(per*100) + '%');"
"}"
"}, false);"
"return xhr;"
"},"
"success:function(d, s) {"
"console.log('success!')"
"},"
"error: function (a, b, c) {"
"}"
"});"
"});"
"</script>";
WebServer server(80);
const char *host = myName;
void setupOTA()
{
/*use mdns for host name resolution*/
if (!MDNS.begin(host)) { //http://esp32.local
Serial.println("Error setting up MDNS responder!");
while (1) {
delay(1000);
}
}
Serial.println("mDNS responder started");
/*return upload page which is stored in uploadContent */
server.on("/", HTTP_GET, []() {
// See https://github.com/espressif/arduino-esp32/blob/master/libraries/WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino
if (!server.authenticate(UPLOADUSER, UPLOADPASSWORD)) {
return server.requestAuthentication();
}
server.sendHeader("Connection", "close");
server.send(200, "text/html", uploadContent);
});
/*handling uploading firmware file */
server.on(UPDATEPAGE, HTTP_POST, []() {
// See https://github.com/espressif/arduino-esp32/blob/master/libraries/WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino
if (!server.authenticate(UPLOADUSER, UPLOADPASSWORD)) {
return server.requestAuthentication();
}
server.sendHeader("Connection", "close");
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
ESP.restart();
}, []() {
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
Serial.printf("Update: %s\n", upload.filename.c_str());
if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
/* flashing firmware to ESP*/
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_END) {
if (Update.end(true)) { //true to set the size to the current progress
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
} else {
Update.printError(Serial);
}
}
});
server.begin();
}
#endif
void loopOTA()
{
#if defined OTA
server.handleClient();
#endif
}
#if defined MQTTModule
// See https://github.com/dawidchyrzynski/arduino-home-assistant/
// See https://dawidchyrzynski.github.io/arduino-home-assistant/documents/getting-started/index.html
#include <ArduinoHA.h>
const unsigned long waitReconnectMQTT = 5L * 60L * 1000L; /* 5 minutes */
const int maxDurationMQTT = 900; /* 0.9 seconds */
unsigned long prevMQTTCheck = 0;
int cntMQTTCheck = 0;
#define BROKER_ADDR IPAddress(192,168,1,121)
HADevice device(mac, sizeof(mac));
HAMqtt mqtt(client, device, 15);
HASensor chickenguardDoorStatus(MQTTprefix "DoorStatus");
HASensorNumber chickenguardLDR(MQTTprefix "LDR");
HASensorNumber chickenguardLDRavg(MQTTprefix "LDRAverage");
HASensorNumber chickenguardTemperature(MQTTprefix "Temperature", HASensorNumber::PrecisionP1);
HASensor chickenguardUpTime(MQTTprefix "UpTime");
HASensor chickenguardTimeNow(MQTTprefix "TimeNow");
HASensor chickenguardTimeOpened(MQTTprefix "TimeOpened");
HASensor chickenguardTimeClosed(MQTTprefix "TimeClosed");
HASensor chickenguardMonitor(MQTTprefix "Monitor");
HASensor chickenguardWaterStatus(MQTTprefix "WaterStatus");
void setupMQTT()
{
device.setName(MQTTid);
device.setSoftwareVersion("1.0.0");
device.enableSharedAvailability();
device.enableLastWill();
// for icons,
// see https://pictogrammers.com/library/mdi/
chickenguardDoorStatus.setIcon("mdi:door");
chickenguardDoorStatus.setName("Door Status");
chickenguardLDR.setIcon("mdi:flare");
chickenguardLDR.setName("LDR");
chickenguardLDRavg.setIcon("mdi:flare");
chickenguardLDRavg.setName("LDR Average");
chickenguardTemperature.setIcon("mdi:thermometer");
chickenguardTemperature.setName("Temperature");
chickenguardTemperature.setUnitOfMeasurement("°C"); // Also results in no logging in logbook HA
chickenguardUpTime.setIcon("mdi:timer-edit-outline");
chickenguardUpTime.setName("Uptime");
chickenguardTimeNow.setIcon("mdi:clock-outline");
chickenguardTimeNow.setName("Time Now");
chickenguardTimeOpened.setIcon("mdi:clock-out");
chickenguardTimeOpened.setName("Time Opened");
chickenguardTimeClosed.setIcon("mdi:clock-in");
chickenguardTimeClosed.setName("Time Closed");
chickenguardMonitor.setIcon("mdi:monitor");
chickenguardMonitor.setName("Monitor");
chickenguardWaterStatus.setIcon("mdi:water-outline");
chickenguardWaterStatus.setName("Water Status");
printSerialln("Start MQTT");
mqtt.onMessage(onMqttMessage);
mqtt.onConnected(onMqttConnected);
# if defined MQTTUSER && defined MQTTPASSWORD
mqtt.begin(BROKER_ADDR, MQTTUSER, MQTTPASSWORD);
# else
mqtt.begin(BROKER_ADDR);
# endif
prevMQTTCheck = 0;
cntMQTTCheck = 0;
printSerialln("Done MQTT");
}
void onMqttMessage(const char* topic, const uint8_t* payload, uint16_t length)
{
if (strcmp(topic, MQTTid "/cmd") == 0)
{
String answer = "";
for (int i = 0; i < length; i++)
answer = answer + (char)payload[i];
Command(answer, false, false);
}
}
void onMqttConnected()
{
printSerialln("Connected to the broker!");
printSerial("MQTTid: ");
printSerialln(MQTTid);
mqtt.subscribe(MQTTid "/#");
}
#endif /* MQTTModule */
void loopMQTT(bool force)
{
#if defined MQTTModule
if (hasEthernet)
{
unsigned long CurrentTime1 = millis();
if (force ||
prevMQTTCheck == 0 /* If previous connection was ok */ ||
CurrentTime1 - prevMQTTCheck > waitReconnectMQTT /* If waitReconnectMQTT time is passed, try again to connect */)
cntMQTTCheck = 0;
else if (++cntMQTTCheck >= 5) /* wait until there are 5 times delays */
cntMQTTCheck = 0;
if (cntMQTTCheck == 0)
{
//printSerialln("mqtt.loop start");
mqtt.loop();
//printSerialln("mqtt.loop done");
unsigned long CurrentTime2 = millis();
if (force)
cntMQTTCheck = 0;
else if (CurrentTime2 - CurrentTime1 > maxDurationMQTT) /* if mqtt.loop() took more than 0.9 sec then we assume that no MQTT connection could be established (1 second seems to be the timeout) */
{
prevMQTTCheck = CurrentTime2;
printSerialln("No MQTT...");
}
else if (prevMQTTCheck != 0)
{
prevMQTTCheck = 0;
cntMQTTCheck = 0;
printSerialln("MQTT restored");
}
}
}
#endif
}
void setMQTTDoorStatus(char *msg)
{
#if defined MQTTModule
chickenguardDoorStatus.setValue(msg);
#endif
#if defined MQTTDebug
printSerial(">>>MQTT DoorStatus: ");
printSerialln(msg);
printSerialln("<<<");
#endif
}
void setMQTTLDR(int ldr)
{
#if defined MQTTModule
chickenguardLDR.setValue((int16_t)ldr);
#endif
#if defined MQTTDebug
printSerial(">>>MQTT LDR: ");
printSerialInt(ldr);
printSerialln();
printSerialln("<<<");
#endif
}
void setMQTTLDRavg(int average)
{
#if defined MQTTModule
chickenguardLDRavg.setValue((int16_t)average);
#endif
#if defined MQTTDebug
printSerial(">>>MQTT LDRAvg: ");
printSerialInt(average);
printSerialln();
printSerialln("<<<");
#endif
}
void setMQTTTemperature()
{
#if defined MQTTModule && defined ClockModule
chickenguardTemperature.setValue((int16_t)readTemperature());
#endif
#if defined MQTTDebug
printSerial(">>>MQTT Temperature: ");
printSerialInt(readTemperature());
printSerialln();
printSerialln("<<<");
#endif
}
void setMQTTMonitor(char *msg)
{
#if defined MQTTModule
if (strlen(msg) > 255) // it looks like that a message may not be longer than 255 characters
msg[255] = 0;
chickenguardMonitor.setValue(msg);
#endif
#if defined MQTTDebug
printSerial(">>>MQTT Monitor: ");
printSerialln(msg);
printSerialln("<<<");
#endif
}
void setMQTTWaterStatus(char *msg)
{
#if defined MQTTModule
chickenguardWaterStatus.setValue(msg);
#endif
#if defined MQTTDebug
printSerial(">>>MQTT WaterStatus: ");
printSerialln(msg);
printSerialln("<<<");
#endif
}
void setUpTime()
{
#if defined MQTTModule || defined MQTTDebug
char buf[50];
sprintf(buf, "%u:%02d:%02d:%02d", uptimeDays, (int)uptimeHours, (int)uptimeMinutes, (int)uptimeSeconds);
#endif
#if defined MQTTModule
chickenguardUpTime.setValue(buf);
#endif
#if defined MQTTDebug
printSerial(">>>MQTT UpTime: ");
printSerialln(buf);
printSerialln("<<<");
#endif
}
void setMQTTTime()
{
#if defined MQTTModule
char buf[10];
int second, minute, hour;
#if defined ClockModule
// retrieve data from DS3231
if (hasClockModule)
readDS3231time(&second, &minute, &hour, NULL, NULL, NULL, NULL);
else
second = minute = hour = -1;
if (hour != -1)
ShowTime(&hour, &minute, &second, buf);
else
strcpy(buf, "Unknown");
#else
unsigned long timeNow = millis();
ShowTime(timeNow, timeNow, buf);
#endif
chickenguardTimeNow.setValue(buf);
#if defined ClockModule
if (hourOpened != 0 || minuteOpened != 0 || secondOpened != 0)
ShowTime(&hourOpened, &minuteOpened, &secondOpened, buf);
else
strcpy(buf, "Unknown");
#else
ShowTime(msOpened, timeNow, buf);
#endif
chickenguardTimeOpened.setValue(buf);
#if defined ClockModule
if (hourClosed != 0 || minuteClosed != 0 || secondClosed != 0)
ShowTime(&hourClosed, &minuteClosed, &secondClosed, buf);
else
strcpy(buf, "Unknown");
#else
ShowTime(msClosed, timeNow, buf);
#endif
chickenguardTimeClosed.setValue(buf);
#endif
}
#if defined NTPModule
#include <time.h>
const char timeServer[] = "time.nist.gov"; // time.nist.gov NTP server
#if defined WIFI
// see https://randomnerdtutorials.com/esp32-date-time-ntp-client-server-arduino/
void InitUdp()
{
}
struct tm *GetNTP()
{
static struct tm time_info;
configTime(1 * 60 * 60 /* gmt + 1 */, 60 * 60 /* 1 hour difference between summer and winter */, timeServer);
if (getLocalTime(&time_info))
return &time_info;
return NULL;
}
#else
// See https://docs.arduino.cc/tutorials/ethernet-shield-rev2/udp-ntp-client /* NTP */
// See https://interface.fh-potsdam.de/prototyping-machines//hardware-prototypes/Boxnet/hardware_code/Write_boxes/DigitalClock_paul/ /* NTP */
// See https://forum.arduino.cc/t/what-is-the-best-library-for-a-mega-for-local-and-gmt-time/693976/4 /* set_zone */
// See https://forum.arduino.cc/t/time-isnt-converted-correct-from-unix-time/1060600 /* UNIX_OFFSET */
//#include <SPI.h>
#include <EthernetUdp.h>
#if !defined UNIX_OFFSET
# define UNIX_OFFSET 946684800UL
#endif
unsigned int localPort = 8888; // local port to listen for UDP packets
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
// A UDP instance to let us send and receive packets over UDP
EthernetUDP Udp;
void InitUdp()
{
Udp.begin(localPort);
}
// send an NTP request to the time server at the given address
void sendNTPpacket(const char * address)
{
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011; // LI, Version, Mode
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
Udp.beginPacket(address, 123); // NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
struct tm *GetNTP()
{
int size;
while ((size = Udp.parsePacket()) > 0)
{
while (size > 0)
{
Udp.read(packetBuffer, min(size, NTP_PACKET_SIZE)); // discard any previously received packets
size -= min(size, NTP_PACKET_SIZE);
}
}
sendNTPpacket(timeServer); // send an NTP packet to a time server
uint32_t beginWait = millis();
while (millis() - beginWait < 1500)
{
size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE)
{
// We've received a packet, read the data from it
Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
// the timestamp starts at byte 40 of the received packet and is four bytes,
// or two words, long. First, extract the two words:
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
// combine the four bytes (two words) into a long integer
// this is NTP time (seconds since Jan 1 1900):
unsigned long secsSince1900 = highWord << 16 | lowWord;
// Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
const unsigned long seventyYears = 2208988800UL;
// subtract seventy years:
unsigned long epoch = secsSince1900 - seventyYears;
# if defined ESP32
time_t unixTime;
# else
unsigned long unixTime;
# endif
unixTime = epoch - UNIX_OFFSET;
unixTime += 1L * 60L * 60L; // GMT+1
struct tm *time_info;
time_info = gmtime(&unixTime);
int dayOfWeek = dayofweek(1900 + time_info->tm_year, 1 + time_info->tm_mon, time_info->tm_mday) + 1;
if (isDstEurope(time_info->tm_mday, 1 + time_info->tm_mon, dayOfWeek, time_info->tm_hour))
{
unixTime += 1L * 60L * 60L; // summer time
time_info = gmtime(&unixTime);
}
return time_info;
}
}
return NULL;
}
// See https://arduinoforum.nl/viewtopic.php?f=9&t=3476#p25098
// ---------------------------
// isDstEurope
// ---------------------------
// Uses the normal local time (winter time) to determine
// if the daylight saving is active.
//
// In Europe, the summer time (+1 hour) starts at
// the last Sunday of March. At 02:00 (am) the clock
// jumps to 03:00 (am).
// The end if the summer time is at the last Sunday
// of October. At 03:00 (am) the clock jumps back
// to 02:00 (am).
// When the winter time is the base, it means that the DST
// becomes active at 02:00 (winter time) but also inactive at 02:00 (winter time).
//
// The parameters are the winter time, don't use the summertime for them.
// day : 1...31
// month : 1...12
// dow : 1...7 1 = Sunday
// hour : 0...23
//
// Returns:
// boolean that indicates that an hour should be added.
// Note that this is not an increment of the hour, but
// everything advances an hour into the future.
//
boolean isDstEurope( int day, int month, int dow, int hour)
{
boolean dst = false;
// day 1...31, dow 1...7
// calculate the current or previous Sunday of this month.
// the result could be negative, which is no problem.
int previousSunday = day - (dow - 1);
// The range for a switch-case statement is a non-standard 'c' usage !
switch( month)
{
case 1 ... 2:
dst = false;
break;
case 3:
// The lowest day for sunday is 25, the highest is 31
if( previousSunday >= 25)
{
if( dow == 1) // is it sunday right now ?
{
if( hour >= 2)
{
dst = true; // dst starts at two in the night
}
else
{
dst = false; // it is the right date, but not yet time
}
}
else
{
dst = true; // it is past the last sunday
}
}
else
{
dst = false; // the date is before the last sunday
}
break;
case 4 ... 9:
dst = true;
break;
case 10:
if( previousSunday >= 25)
{
if( dow == 1) // is it sunday right now ?
{
if( hour < 2)
{
dst = true; // dst stops at two in the night
}
else
{
dst = false; // it is the right date, but beyond the DST
}
}
else
{
dst = false; // it is past the last sunday
}
}
else
{
dst = true; // the date is before the last sunday
}
break;
case 11 ... 12:
dst = false;
break;
default:
// wrong parameter for the month
dst = false;
}
return dst;
}
#endif
void printNTP(struct tm *time_info)
{
if (time_info != NULL)
{
char buf[20];
sprintf(buf, "Date: %02d/%02d/%04d", time_info->tm_mday, 1 + time_info->tm_mon, 1900 + time_info->tm_year);
printSerialln(buf);
sprintf(buf, "Time: %02d:%02d:%02d", time_info->tm_hour, time_info->tm_min, time_info->tm_sec);
printSerialln(buf);
}
else
printSerialln("No NTP packet");
}
void SyncDateTime()
{
for (int i = 0; i < 60; i++)
{
struct tm *time_info = GetNTP();
printNTP(time_info);
if (time_info != NULL)
{
#if defined ClockModule
if (hasClockModule)
{
setDS3231time(time_info->tm_sec, time_info->tm_min, time_info->tm_hour, 0, time_info->tm_mday, 1 + time_info->tm_mon, 1900 + time_info->tm_year - 2000);
printDS3231time();
}
#else
msTime = millis();
dayTime = time_info->tm_mday;
monthTime = 1 + time_info->tm_mon;
yearTime = 1900 + time_info->tm_year - 2000;
hourTime = time_info->tm_hour;
minuteTime = time_info->tm_min;
secondsTime = time_info->tm_sec;
ShowTime(msTime, msTime, NULL);
printSerialln();
#endif
break;
}
}
}
#endif /* NTPModule */
#if defined EEPROMModule
#include <EEPROM.h>
#define EEPROMMagic 25687
#define EEPROM_SIZE (1 + sizeof(changeableData) / sizeof(*changeableData) * sizeof(int))
void readChangeableData()
{
int address = 0;
int value;
#if defined ESP32
EEPROM.begin(EEPROM_SIZE);
#endif
EEPROM.get(address, value);
if (value == EEPROMMagic)
{
printSerialln("Reading changeable data from EEPROM");
for (int i = 0; i < sizeof(changeableData) / sizeof(*changeableData); i++)
{
address += sizeof(value);
EEPROM.get(address, value);
*(changeableData[i].variable) = value;
}
}
else
printSerialln("No valid changeable data stored in EEPROM");
}
void writeChangeableData()
{
int address = 0;
int value;
printSerialln("Writing changeable data to EEPROM");
value = EEPROMMagic;
EEPROM.put(address, value);
for (int i = 0; i < sizeof(changeableData) / sizeof(*changeableData); i++)
{
address += sizeof(value);
value = *(changeableData[i].variable);
EEPROM.put(address, value);
}
#if defined ESP32
EEPROM.commit();
#endif
}
#endif /* EEPROMModule */Loading
esp32-s2-devkitm-1
esp32-s2-devkitm-1