Ideas and suggestions working with relays

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

Welcome to the forums. Take a moment and read this: How to get the best out of this forum

It will help others help you. Then, post your code here, not a link to your code. Many folks don't randomly follow links

So only close the valve once the water level sensor reports that there's water.

Keep in mind that this is probably a disaster waiting to happen if your water sensor fails and the whole thing overflows, but I assume you're handling that somehow.

What have you tried in your code to make this happen and where do you get stuck?

Can you make a minimal version of your sketch that just handles the filling of the tank (without delays)? See if you can get that to work, then implement that into your full sketch.

there is a flow meter on the drain and 1 on the fill side plus the level sensor. The draining is the problem, it shuts off after 10 seconds with the delay. For some reason It doesn't acknowledge the flow meter.

same issue with a minimal sketch with just the relays.

Could you please post that version, and also a schematic of your project and a photo of how things are wired together?

Have you tested the sensors in separate sketches to verify you can reliably read them?

Yes I tested each sensor and button first and added 1 component at a time and made sure it was working before adding the next. Give me a min to get the pictures.


Apologies for the hand drawn diagram, it would take me way to long to figure out how to use the online tools and make a good looking diagram. As i have said, all the V and G are on rails and the pins in the code coraspond to the sensor correctly.

No worries about the hand-drawn diagram; I can make sens of it, I think. What kind of output do your sensors give?

In a system with only the relevant sensors connected (flow, level) and a single button, and the code using only those items and the relay that switches the fill pump, what is the behavior you see? And can you share the code for that minimal setup, please?

I'm trying to get you to a point where we have a minimal setup that demonstrates the problem and that conveys the problem to the rest of us.

the flow meters give a total oz and oz per min based on the pulse math. water level is a high and low.

The fill pump is not the issue, it fills 5 gallons perfectly and stops, that works with or without the water level sensor.

the drain partis the problem, it is turning on and off very quickly which is what I am trying to figure out how to fix without using delays.

I understand your method and point.

#include <Relay.h>

Relay DRAIN_VALVE(2, false);
//#define DRAIN_PUMP_RELAY 3

Relay FILL_VALVE(3, false);
//#define WATER_FILL_PUMP_RELAY 5

#define WATER_LEVEL_SENSOR 6

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;

void setup() {
  Serial.begin(9600);
  DRAIN_VALVE.begin();
  FILL_VALVE.begin();

  void loop() {
    startDrainCycle();
    flowMeter();
  }

  void pulseCounter() {
    pulseCount++;
  }

  void flowCalculations() {
    int currentPulseCount = pulseCount;
    pulseCount = 0;
    totalOunces = 0;
    flowRateLMin = (float)currentPulseCount / calibrationFactor;
    flowRateOz = flowRateLMin * 33.814;
    totalOunces += flowRateOz;
  }

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

  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.");
    }
  }

That code goes in setup() to be run once, not every time this function is called. Also, that code does not compile so it makes it difficult to check/help

Thank you, i am using arduino ide 2.3 i think.

You carefully

volatile int pulseCount;

make pulseClunt a volatile variable, but that's only half what you need to do.

Any access to that variable must be done with interrupts off. For getting the value, ppl will usually make a new variable and grab a copy

  int myPulseCount;

  noInterrupts();
  myPulseCount =pulseCount;
  interrupts();

then use myPulseCount. If you zero pulseCount, do it similarly within the safe section when interrupts are off.

It may not be your problem, and it may not have been doing anything, but the flaw waits silently to make trouble for you which, trust me, can be very hard to realize the cause of which.

a7

Thank you, every little bit helps.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.