#include <Wire.h>
#include <RTClib.h>
#include <EEPROM.h>
constexpr int NUM_RELAYS = 8; // 8 relays (1-6 for logic, 7-8 for always-on until 1-6 turn off)
constexpr unsigned long STATUS_UPDATE_INTERVAL = 5000;
constexpr int SERIAL_BAUD_RATE = 9600;
constexpr int RELAY_ACTIVE_STATE = LOW; // Relays are active low
constexpr int RELAY_INACTIVE_STATE = HIGH; // Relays are inactive high
struct TimeSettings {
int8_t hour;
int8_t minute;
};
struct EEPROMConfig {
static constexpr int START_HOUR = 0;
static constexpr int START_MINUTE = 1;
static constexpr int END_HOUR = 2;
static constexpr int END_MINUTE = 3;
};
class RelayController {
private:
RTC_DS3231 rtc;
const int relayPins[NUM_RELAYS] = {2, 3, 4, 5, 6, 7, 8, 9}; // Relays 1-6: pins 2-7, Relays 7-8: pins 8-9
const int EVSEPresent = 10; // EVSE present signal
const int CPPresent = 11; // CP present signal
const int overridePin = 12; // Override button pin
TimeSettings startTime = {2, 0};
TimeSettings endTime = {8, 0};
bool evsePresent = false;
bool cpPresent = false;
bool overrideTriggered = false;
unsigned long lastStatusUpdate = 0;
bool relaysAreOn = false;
bool isValidTime(int hour, int minute) {
return hour >= 0 && hour < 24 && minute >= 0 && minute < 60;
}
void loadFromEEPROM() {
EEPROM.get(EEPROMConfig::START_HOUR, startTime);
EEPROM.get(EEPROMConfig::END_HOUR, endTime);
if (!isValidTime(startTime.hour, startTime.minute)) startTime = {2, 0};
if (!isValidTime(endTime.hour, endTime.minute)) endTime = {8, 0};
}
void saveToEEPROM() {
EEPROM.put(EEPROMConfig::START_HOUR, startTime);
EEPROM.put(EEPROMConfig::END_HOUR, endTime);
}
void setRelayStates(bool active) {
if (active) {
// Activate relays 1-6
for (int i = 0; i < 6; i++) {
digitalWrite(relayPins[i], RELAY_ACTIVE_STATE);
delay(500); // Delay between each relay activation
}
relaysAreOn = true;
} else {
// Deactivate relays 1-6
for (int i = 5; i >= 0; i--) {
digitalWrite(relayPins[i], RELAY_INACTIVE_STATE);
delay(500); // Delay between each relay deactivation
}
relaysAreOn = false;
// Turn off relays 7-8 only after relays 1-6 are deactivated
digitalWrite(relayPins[6], RELAY_INACTIVE_STATE); // Relay 7
digitalWrite(relayPins[7], RELAY_INACTIVE_STATE); // Relay 8
}
}
bool isTimeInRange(const DateTime& now) {
int currentMinutes = now.hour() * 60 + now.minute();
int startMinutes = startTime.hour * 60 + startTime.minute;
int endMinutes = endTime.hour * 60 + endTime.minute;
return (startMinutes <= endMinutes) ? (currentMinutes >= startMinutes && currentMinutes < endMinutes)
: (currentMinutes >= startMinutes || currentMinutes < endMinutes);
}
public:
void begin() {
Serial.begin(SERIAL_BAUD_RATE);
// Initialize RTC
if (!rtc.begin()) {
Serial.println(F("RTC initialization failed!"));
while (1) delay(800);
}
if (rtc.lostPower()) {
Serial.println(F("RTC lost power, resetting to compile time."));
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
// Initialize all relays and ensure they start in the correct state
for (int i = 0; i < NUM_RELAYS; i++) {
pinMode(relayPins[i], OUTPUT);
digitalWrite(relayPins[i], RELAY_INACTIVE_STATE); // Start with all relays OFF
}
// Turn on relays 7-8 immediately
digitalWrite(relayPins[6], RELAY_ACTIVE_STATE); // Relay 7
digitalWrite(relayPins[7], RELAY_ACTIVE_STATE); // Relay 8
// Initialize input pins
pinMode(EVSEPresent, INPUT_PULLUP);
pinMode(CPPresent, INPUT_PULLUP);
pinMode(overridePin, INPUT_PULLUP);
// Load time settings from EEPROM
loadFromEEPROM();
}
void update() {
DateTime now = rtc.now();
evsePresent = digitalRead(EVSEPresent) == LOW;
cpPresent = digitalRead(CPPresent) == LOW;
bool overridePressed = digitalRead(overridePin) == LOW;
if (!evsePresent || !cpPresent) {
overrideTriggered = false;
if (relaysAreOn) {
setRelayStates(false); // Turn off relays 1-6 (and 7-8 if relays 1-6 are off)
}
} else {
if (evsePresent && cpPresent && overridePressed) {
overrideTriggered = true;
}
if (overrideTriggered) {
setRelayStates(true); // Turn on relays 1-6
} else {
bool shouldActivate = evsePresent && cpPresent && isTimeInRange(now);
if (shouldActivate && !relaysAreOn) {
setRelayStates(true); // Turn on relays 1-6
} else if (!shouldActivate && relaysAreOn) {
setRelayStates(false); // Turn off relays 1-6 (and 7-8 if relays 1-6 are off)
}
}
}
// Process serial commands
if (Serial.available()) {
processCommand(Serial.readStringUntil('\n'));
}
// Print status periodically
if (millis() - lastStatusUpdate >= STATUS_UPDATE_INTERVAL) {
printStatus(now);
lastStatusUpdate = millis();
}
}
void processCommand(String command) {
command.trim();
if (command.startsWith("start ")) {
parseTime(command.substring(6), startTime);
saveToEEPROM();
Serial.println(F("Start time updated."));
} else if (command.startsWith("end ")) {
parseTime(command.substring(4), endTime);
saveToEEPROM();
Serial.println(F("End time updated."));
} else if (command.startsWith("set ")) {
setRTC(command.substring(4));
Serial.println(F("RTC time updated."));
} else if (command == "status") {
printStatus(rtc.now());
} else if (command == "help") {
printHelp();
} else {
Serial.println(F("Unknown command. Type 'help' for commands."));
}
}
void parseTime(String input, TimeSettings& timeSetting) {
int hour, minute;
if (sscanf(input.c_str(), "%d:%d", &hour, &minute) == 2 && isValidTime(hour, minute)) {
timeSetting.hour = hour;
timeSetting.minute = minute;
} else {
Serial.println(F("Invalid time format. Use HH:MM."));
}
}
void setRTC(String input) {
int year, month, day, hour, minute, second;
if (sscanf(input.c_str(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second) == 6) {
rtc.adjust(DateTime(year, month, day, hour, minute, second));
} else {
Serial.println(F("Invalid format. Use YYYY-MM-DD HH:MM:SS."));
}
}
void printStatus(const DateTime& now) {
Serial.println(F("\n=== System Status ==="));
Serial.print(F("Current: "));
Serial.print(now.timestamp());
Serial.print(F("\nStart: "));
Serial.print(startTime.hour);
Serial.print(F(":"));
Serial.print(startTime.minute);
Serial.print(F("\nEnd: "));
Serial.print(endTime.hour);
Serial.print(F(":"));
Serial.print(endTime.minute);
Serial.print(F("\nEVSE Present? "));
Serial.println(evsePresent ? F("Yes") : F("No"));
Serial.print(F("CP Present? "));
Serial.println(cpPresent ? F("Yes") : F("No"));
Serial.print(F("Override Active? "));
Serial.println(overrideTriggered ? F("Yes") : F("No"));
for (int i = 0; i < NUM_RELAYS; i++) {
Serial.print(F("Relay "));
Serial.print(i + 1);
Serial.print(F(" Status? "));
Serial.println(digitalRead(relayPins[i]) == RELAY_ACTIVE_STATE ? F("ON") : F("OFF"));
}
Serial.print(F("Temperature: "));
Serial.print(rtc.getTemperature());
Serial.println(F("°C"));
}
void printHelp() {
Serial.println(F("\nAvailable Commands:"));
Serial.println(F(" start HH:MM - Set relay start time"));
Serial.println(F(" end HH:MM - Set relay end time"));
Serial.println(F(" set YYYY-MM-DD HH:MM:SS - Set RTC time"));
Serial.println(F(" status - Show current status"));
Serial.println(F(" help - Show help message\n"));
}
};
RelayController controller;
void setup() {
controller.begin();
}
void loop() {
controller.update();
}