Hardware: Mega, i2c display, 4 push buttons, 2 solenoid valves, 2 flow meters, water level sensor (might add a second) 2 relays that control the valves. Wiring: V+ is on 1 rail and G is on another rail, everything else is defined by pins.
The issue is timing, I want the relay to be on while there is water is draining through the flow meter. The relay opens the valve then closes the valve about 10 seconds later, I want the valve to remain open until the tank is drained. The buttons are to activate the drain, fill or auto cycle and there is an auto cycle once a week. I want to avoid using delays because of the buttons.
https://app.arduino.cc/sketches/5416406a-a3a6-4faf-8be5-479e65b6716b?view-mode=preview
```cpp
#include <U8g2lib.h>
#include <U8x8lib.h>
#include <SPI.h>
#include <Wire.h>
#include <RTClib.h>
#include <Relay.h>
#define DEBUG 1
#if DEBUG == 1
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#else
#define debug(x)
#define debugln(x)
#endif
Relay DRAIN_VALVE(2, false);
//#define DRAIN_PUMP_RELAY 3
Relay FILL_VALVE(3, false);
//#define WATER_FILL_PUMP_RELAY 5
#define BUTTON_AUTO 10
#define BUTTON_DRAIN 11
#define BUTTON_FILL 12
#define BUTTON_TOPOFF 13
#define WATER_LEVEL_SENSOR 6
#define I2C_SDA 20
#define I2C_SCL 21
// #define SCK 52
// #define DATA 51
// #define RESET 9
// #define DC 19
// #define CS 53
U8G2_SSD1309_128X64_NONAME2_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, I2C_SCL, I2C_SDA);
// U8G2_SSD1309_128X64_NONAME2_F_4W_HW_SPI u8g2(U8G2_R0, RES, DC,CS);
RTC_DS3231 rtc;
DateTime now, datetime, nextAutoCycle;
int lastButtonStateAuto = HIGH, lastButtonStateDrain = HIGH, lastButtonStateFill = HIGH, lastButtonStateTopOff = HIGH;
static unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50;
const int flowMeterDrain = 4, flowMeterFill = 5;
const float calibrationFactor = 7.5;
volatile int pulseCount;
unsigned long lastFlowRateCheck = 0;
float flowRateLMin, flowRateOz, totalOunces = 0;
bool isCycleActive = false, isTopOffActive = false;
//************************************************************************SETUP
void setup() {
Serial.begin(9600);
u8g2.begin();
Wire.begin();
DRAIN_VALVE.begin();
FILL_VALVE.begin();
if (!rtc.begin()) {
debugln("Couldn't find RTC");
while (1)
;
}
if (rtc.lostPower()) {
debugln("RTC lost power, lets set the time!");
}
rtc.adjust(DateTime(2024, 5, 9, 14, 26, 0));
pinMode(BUTTON_AUTO, INPUT_PULLUP);
pinMode(BUTTON_DRAIN, INPUT_PULLUP);
pinMode(BUTTON_FILL, INPUT_PULLUP);
pinMode(BUTTON_TOPOFF, INPUT_PULLUP);
}
//**********************************************************************LOOP
void loop() {
now = rtc.now();
if (!isCycleActive && !isTopOffActive) {
checkButtons();
}
if (lastButtonStateAuto == LOW && !isCycleActive && !isTopOffActive && now.unixtime() >= nextAutoCycle.unixtime() && digitalRead(WATER_LEVEL_SENSOR) == LOW) {
startAutoCycle();
}
if (digitalRead(WATER_LEVEL_SENSOR) == LOW && !isCycleActive) {
startFillCycle();
}
updateDisplay();
setNextAutoCycle();
// DateTime now = rtc.now();
// debug("Current Date/Time: ");
// debug(now.year(), DEC);
// debug('/');
// debug(now.month(), DEC);
// debug('/');
// debug(now.day(), DEC);
// debug(" ");
// debug(now.hour(), DEC);
// debug(':');
// debug(now.minute(), DEC);
// debug(':');
// debugln(now.second(), DEC);
delay(200);
}
void pulseCounter() {
pulseCount++;
}
//******************************************************Flow Calc
void flowCalculations() {
int currentPulseCount = pulseCount;
pulseCount = 0;
totalOunces = 0;
flowRateLMin = (float)currentPulseCount / calibrationFactor;
flowRateOz = flowRateLMin * 33.814;
totalOunces += flowRateOz;
}
//***********************************************FLOW METER
void flowMeter() {
pinMode(flowMeterDrain, INPUT_PULLUP);
// attachInterrupt(digitalPinToInterrupt(flowMeterDrain), pulseCounter, FALLING);
pinMode(flowMeterFill, INPUT_PULLUP);
// attachInterrupt(digitalPinToInterrupt(flowMeterFill), pulseCounter, FALLING);
if (millis() - lastFlowRateCheck >= 1000) {
noInterrupts();
flowCalculations();
// debug("Flow Drain rate: ");
// debug(flowRateOz);
// debug(" oz/min; Total: ");
// debug(totalOunces);
// debugln(" oz");
interrupts();
lastFlowRateCheck = millis();
}
}
//***************************************************CHECK BUTTONS
void checkButtons() {
debugln("Checking buttons... ");
int currentButtonStateAuto = digitalRead(BUTTON_AUTO);
int currentButtonStateDrain = digitalRead(BUTTON_DRAIN);
int currentButtonStateFill = digitalRead(BUTTON_FILL);
// int currentButtonStateTopOff = digitalRead(BUTTON_TOPOFF);
buttonState(&lastButtonStateAuto, digitalRead(BUTTON_AUTO), startAutoCycle);
buttonState(&lastButtonStateDrain, digitalRead(BUTTON_DRAIN), startDrainCycle);
buttonState(&lastButtonStateFill, digitalRead(BUTTON_FILL), startFillCycle);
// buttonState(&lastButtonStateTopOff, digitalRead(BUTTON_TOPOFF), topOffCycle);
}
void buttonState(int *lastState, int currentState, void (*action)()) {
if (millis() - lastDebounceTime > debounceDelay) {
if (currentState == LOW && *lastState == HIGH) {
action();
*lastState = LOW;
lastDebounceTime = millis();
} else if (currentState == HIGH && *lastState == LOW) {
*lastState = HIGH;
}
}
}
//*******************************************************************AUTO BUTTON
void autoButton() {
int readingAuto = digitalRead(BUTTON_AUTO);
debug("Auto button state: ");
debugln(readingAuto);
if (digitalRead(BUTTON_AUTO) == LOW && !isCycleActive) {
startAutoCycle();
}
if (readingAuto == LOW && lastButtonStateAuto == HIGH && !isCycleActive) {
debugln("Auto button pressed and no active cycle.");
lastButtonStateAuto = LOW;
delay(50);
} else if (readingAuto == HIGH && lastButtonStateAuto == LOW) {
lastButtonStateAuto = HIGH;
}
}
void startAutoCycle() {
if (!isCycleActive) {
debugln("Auto cycle started.");
isCycleActive = true;
startDrainCycle();
if (!isCycleActive) {
startFillCycle();
}
isCycleActive = false;
} else {
debugln("Attempt to start auto cycle failed: another cycle is active.");
}
}
//********************************************************************DRAIN BUTTON
void drainButton() {
int readingDrain = digitalRead(BUTTON_DRAIN);
debug("Drain button state: ");
debugln(readingDrain);
if (digitalRead(BUTTON_DRAIN) == LOW && !isCycleActive) {
startDrainCycle();
}
if (readingDrain == LOW && lastButtonStateDrain == HIGH && !isCycleActive) {
debugln("Drain button pressed and no active cycle.");
lastButtonStateDrain = LOW;
delay(50);
} else if (readingDrain == HIGH && lastButtonStateAuto == LOW) {
lastButtonStateDrain = HIGH;
}
}
//****************************************************************DRAIN
void startDrainCycle() {
if (!isCycleActive) {
isCycleActive = true;
debugln("Drain cycle started.");
do {
DRAIN_VALVE.turnOn();
delay(5000);
} while (digitalRead(flowMeterDrain) > 0);
DRAIN_VALVE.turnOff();
debugln("Drain cycle stopped.");
isCycleActive = false;
} else {
debugln("Attempt to start drain failed: another cycle is active.");
}
}
//************************************************************************FILL BUTTON
void fillButton() {
int readingFill = digitalRead(BUTTON_FILL);
debug("Fill button state: ");
debugln(readingFill);
if (digitalRead(BUTTON_FILL) == LOW) {
startFillCycle();
}
if (readingFill == LOW && lastButtonStateFill == HIGH && !isCycleActive) {
debugln("Fill button pressed and no active cycle.");
lastButtonStateFill = LOW;
delay(50);
} else if (readingFill == HIGH && lastButtonStateFill == LOW) {
lastButtonStateFill = HIGH;
}
}
//***********************************************************************FILL
void startFillCycle() {
unsigned long startMillis = millis();
float totalFilled = 0.0;
if (!isCycleActive) {
isCycleActive = true;
debugln("Fill cycle started.");
FILL_VALVE.turnOn();
while (digitalRead(WATER_LEVEL_SENSOR) == HIGH) {
delay(1000);
flowCalculations();
totalFilled += flowRateOz;
debug("Flow Rate: ");
debug(flowRateLMin);
debug(" L/min, Total Filled: ");
debug(totalFilled);
debugln(" oz");
}
FILL_VALVE.turnOff();
isCycleActive = false;
debugln("Fill cycle completed.");
} else {
debugln("Attempt to start fill failed: another cycle is active.");
}
}
//*****************************************************************TOPOFF BUTTON
// void topOffButton() {
// int currentButtonStateTopOff = digitalRead(BUTTON_TOPOFF);
// int readingtopOff = digitalRead(BUTTON_TOPOFF);
// if ((millis() - lastDebounceTime) > debounceDelay) {
// if (currentButtonStateTopOff != lastButtonStateTopOff) {
// lastDebounceTime = millis();
// if (currentButtonStateTopOff == LOW) {
// isTopOffActive = !isTopOffActive;
// if (isTopOffActive) {
// debugln("Top off cycle started.");
// FILL_VALVE.turnOn();
// } else {
// debugln("Top off cycle stopped.");
// FILL_VALVE.turnOff();
// }
// }
// }
// }
// }
//*************************************************************TOP OFF
// void topOffCycle() {
// if (!isCycleActive) {
// isTopOffActive = !isTopOffActive;
// int waterLevelState = digitalRead(WATER_LEVEL_SENSOR);
// debug("Water Level Sensor State: ");
// debugln(waterLevelState);
// if (waterLevelState == HIGH) {
// isTopOffActive = true;
// FILL_VALVE.turnOn();
// debugln("Top off cycle started.");
// while (digitalRead(WATER_LEVEL_SENSOR) == LOW) {
// delay(1000);
// }
// FILL_VALVE.turnOff();
// isTopOffActive = false;
// debugln("Top off cycle completed");
// }
// }
// }
void processButtonState(int *buttonState, int reading, void (*action)()) {
if (reading != *buttonState) {
*buttonState = reading;
if (*buttonState == LOW) {
action();
}
}
}
//**************************************************************UPDATE DISPLAY
void updateDisplay() {
now = rtc.now();
char dateStr[20], timeStr[20], nextAutoDateStr[20], nextAutoTimeStr[20];
sprintf(dateStr, "%02d/%02d/%02d", now.month(), now.day(), now.year());
sprintf(nextAutoDateStr, "Next: %02d/%02d ", nextAutoCycle.month(), nextAutoCycle.day());
int hour = now.hour();
bool isPM = hour >= 12;
if (hour > 12) hour -= 12;
if (hour == 0) hour = 12; // Handle midnight case
sprintf(timeStr, "%02d:%02d:%02d %s", hour, now.minute(), now.second(), isPM ? "PM" : "AM");
sprintf(nextAutoTimeStr, " %02d:%02d %s", hour, nextAutoCycle.hour(), nextAutoCycle.minute(), isPM ? "PM" : "AM");
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(5, 10, "Current Date/Time:");
u8g2.drawStr(5, 20, dateStr);
u8g2.drawStr(5, 30, timeStr);
u8g2.drawStr(5, 45, nextAutoDateStr);
u8g2.drawStr(69, 45, nextAutoTimeStr);
if (isCycleActive || isTopOffActive) {
drawActiveCycleIndicators();
} else {
drawInactiveCycleIndicators();
}
} while (u8g2.nextPage());
}
void drawActiveCycleIndicators() {
u8g2.setDrawColor(1); // White on black
if (isCycleActive) {
u8g2.drawBox(6, 51, 29, 13);
u8g2.drawBox(49, 51, 34, 13);
u8g2.drawBox(97, 51, 27, 13);
u8g2.setDrawColor(0); // Black text on white
u8g2.drawStr(9, 61, "AUTO");
u8g2.drawStr(51, 61, "DRAIN");
u8g2.drawStr(99, 61, "FILL");
}
// if (isTopOffActive) {
// u8g2.drawFrame(97, 51, 27, 13);
// u8g2.drawStr(99, 61, "FILL");
// }
}
void drawInactiveCycleIndicators() {
u8g2.setDrawColor(1); // White text
u8g2.drawStr(9, 61, "AUTO");
u8g2.drawStr(51, 61, "DRAIN");
u8g2.drawStr(99, 61, "FILL");
}
//******************************************************************NextAutoCycle
void setNextAutoCycle() {
now = rtc.now();
int currentDayOfWeek = now.dayOfTheWeek();
int daysUntilNextWednesday = (10 - currentDayOfWeek + 7) % 7;
if (daysUntilNextWednesday == 0 && (now.hour() > 14 || (now.hour() == 14 && now.minute() > 0))) {
daysUntilNextWednesday = 7;
}
nextAutoCycle = now + TimeSpan(daysUntilNextWednesday * SECONDS_PER_DAY);
nextAutoCycle = DateTime(nextAutoCycle.year(), nextAutoCycle.month(), nextAutoCycle.day(), 14, 0, 0); // Set to 2 PM
}
//KY019 5V relay module
int relay = 6; // relay turns trigger signal - active high;
void setup ()
{
pinMode (relay, OUTPUT); // Define port attribute is output;
}
void loop ()
{
digitalWrite (relay, HIGH); // relay conduction;
delay (1000);
digitalWrite (relay, LOW); // relay switch is turned off;
delay (1000);
}

