Arduino malfunctions occasionally, could this be a programming error?

I've built a timer to control a hydroponic plant setup, but i'm having issues with reliability. Sometimes it will go for days without error, other times it'll fail to turn off the lights at night and back on in the morning. There were times where it would fail to turn the pump off too, and possibly on too but I haven't caught it in the act.

If the lights fail to turn on or off, both fail, not just one. If I physically press the buttons it works as expected. When I noticed the pump failing, the serial monitor reported that it had switched it off as normal, but it didn't. I never caught what the serial monitor said after the lights failed, failures are far more infrequent because they're only triggered twice a day vs 16 times for the pump.
I have tested the buttons responsiveness and they seem to work fine with a press as short as 50ms, I use 2 button presses of 600ms to be safe.
I'm uncertain as to whether the pins fail to be written high to low, or the remote fails to respond to the input.

rtc library used
Outlet control remote

Important/potentially problematic parts of the code:

#include <DS3231.h>   // RTC Library

DS3231  rtc(SDA, SCL);    // Define rtc pins

// Pins
const uint8_t remote[10] = {A1, A0, A3, A2, 2, 3, 6, 5, 8}; //(OFF,ON) (8,9 active HIGH)

class Button {
  public:
    Button(int, String);
    String device;
    bool getState();
    void on();
    void off();
    void cycle(unsigned long interval);               // on for interval, off
  private:
    uint8_t num;                                // button number
    bool state;
};

Button pump(1, "Pump"), light(2, "Light"), light2(3, "Light2"), air(4, "Air");

void setup() {
  Serial.begin(9600);
  Serial.println("BOOT UP");
  rtc.begin();

  for (uint8_t i = 0; i <= 8; i++) {
    pinMode(remote[i], OUTPUT);
    digitalWrite(remote[i], LOW);
    if (i < 4) {                                // press each off button
      digitalWrite(remote[i], HIGH);
      delay(200);
      digitalWrite(remote[i], LOW);
      delay(200);
    }
  }
  //  rtc.setTime(0, 54, 10);                         // set current 24h time (hour, min, sec)
  //  rtc.setTime(5, 59, 55);                    // Test condition
  Serial.print("CURRENT TIME: "); displayTime(rtc.getTime());
  air.on();                                     // air always on
}

void loop() {
  bool dry;
  Time t = rtc.getTime();
  feed(t);
  if (!light.getState() && t.hour >= 6) {       // turn on lights after 6AM
    light.on();
    light2.on();
  } else if (light.getState() && t.hour < 6) {
    light.off();
    light2.off();
  }
  if (pump.getState()) {                        // pump should always be off outside of cycle
    pump.off();                                 // be sure pump is off
  }
}

void feed(Time t) {
  if (t.sec == 0 && t.min == 0) {                 // only check on the hour
    if (t.hour % 3 == 0) {                        // every 3 hours
      digitalWrite(remote[8], HIGH); // cycle drip pump
      pump.cycle(90);                             // cycle pump for 90 sec
    }
    pump.off();                               // turn off the goddamned pump
  }
  if(t.sec == 0 && t.min == 10 && t.hour % 3 == 0) {             // turn off drip pump after 10 min
    digitalWrite(remote[8], LOW);
  }
}

/***************** Button Class *****************/
Button::Button(int buttNum, String devName) {
  num = buttNum - 1;
  device = devName;
  state = false;
}

bool Button::getState() {
  return state;
}

void Button::off() {
  for (uint8_t i = 0; i < 2; i++) {        // do it twice because it fails sometimes *Still fails sometimes?
    digitalWrite(remote[num], HIGH);
    delay(600);
    digitalWrite(remote[num], LOW);
    delay(100);
  }
  state = false;
  String out = device + " OFF: ";
  Serial.print(out); displayTime(rtc.getTime());
}

void Button::on() {
  for (uint8_t i = 0; i < 2; i++) {         // do it twice because it fails sometimes *Still fails sometimes?
    digitalWrite(remote[num + 4], HIGH);
    delay(600);
    digitalWrite(remote[num + 4], LOW);
    delay(100);
  }
  state = true;
  String out = device + " ON: ";
  Serial.print(out); displayTime(rtc.getTime());
}

void Button::cycle(unsigned long interval) {
  Serial.println("Cycle Begin");
  unsigned long curr = millis();
  while (millis() - curr < interval * 1000UL) {
    if (!this->getState()) {
      this->on();
    }
  }
  this->off();
}

Use of Strings causes memory problems and eventual program crashes, so is not recommended on Arduino.

 String out = device + " ON: ";

Also, electrical noise from motors/pumps/servos can cause random resets or malfunctions. Post a wiring diagram.

const uint8_t remote[10] = {A1, A0, A3, A2, 2, 3, 6, 5, 8}; //(OFF,ON) (8,9 active HIGH)

Ten elements, but only nine explicitly initialised.
Why?

What does the comment mean?

Yes, post wiring. Curious!

But it sounds like you are hot wiring a cheapy plug remote power switch. Assuming you only see the BOOT message when you expect, it seems likely the failure is without your code and Arduino.

Whatever else you find, I would recommend going to the trouble of having some way to close the loop - that is to say verify that the cheapy remote has in fact switched properly. Naturally you'll wanna do this without adding additional places where noise and power glitches can interfere making it worse than not doing.

Alternately or additionally, you could issue the commands regularly, even if they would seem redundant. Send "lamp off" every minute during the lamp off period, &c. Then if the problem was something random and temporary the lamp would soon get turnt on. This also would help ride over a brief power outrage.

I would do both. The log text would be interesting!

a7

TolpuddleSartre:

const uint8_t remote[10] = {A1, A0, A3, A2, 2, 3, 6, 5, 8}; //(OFF,ON) (8,9 active HIGH)

Ten elements, but only nine explicitly initialised.
Why?

What does the comment mean?

I made the array one bigger than it needs to be out of habit of using a null terminator. Correct me if I'm wrong, but I'm pretty sure it shouldn't cause any problems unless the empty element isn't null by default. The comment (OFF,ON) simply means the first half are off buttons and the second half ar on buttons. The last int in the array, 8, is different from the rest. I added it later. instead of a remote button, it controls a mosfet which is connected to a low power water pump. (8 active HIGH) means the pump is on while 8 is high. I just put it in the same array to simplify writing the initialization. 9 is for another pump I haven't implemented into the system yet, so it isn't in the array. Sorry for the confusion.

alto777:
Yes, post wiring. Curious!

But it sounds like you are hot wiring a cheapy plug remote power switch. Assuming you only see the BOOT message when you expect, it seems likely the failure is without your code and Arduino.

Whatever else you find, I would recommend going to the trouble of having some way to close the loop - that is to say verify that the cheapy remote has in fact switched properly. Naturally you'll wanna do this without adding additional places where noise and power glitches can interfere making it worse than not doing.

Alternately or additionally, you could issue the commands regularly, even if they would seem redundant. Send "lamp off" every minute during the lamp off period, &c. Then if the problem was something random and temporary the lamp would soon get turnt on. This also would help ride over a brief power outrage.

I would do both. The log text would be interesting!

a7

I do get the boot message only when expected, so you're probably right about it being the remote. There's an indicator LED that turns on when the button is pressed, but the transmitter may not necessarily be active while the light is on if its a crappy product. If I could find the right pin on the right IC in the remote, that would be ideal.

I already have it set to press the buttons multiple times, but it seems that if it's going to fail, every level of redundancy also fails. I'd rather get to the root of the failure rather than try to circumvent it with redundancy anyway.

jremington:
Use of Strings causes memory problems and eventual program crashes, so is not recommended on Arduino.

 String out = device + " ON: ";

Also, electrical noise from motors/pumps/servos can cause random resets or malfunctions. Post a wiring diagram.

The arduino doesn't seem to be crashing, but I will go to the effort of getting rid of the strings and using char arrays where necessary. As far as electrical noise goes, I don't think that's the issue. I'll upload a wiring diagram soon, but for now I can say there are no inductive loads connected directly to the arduino other than one pump connected through a mosfet which was added after the issue was known. Any unintentional resets shouldn't cause any issue as long as the RTC functions properly.

GustavoMcSavy:

I do get the boot message only when expected, so you're probably right about it being the remote. There's an indicator LED that turns on when the button is pressed, but the transmitter may not necessarily be active while the light is on if its a crappy product. If I could find the right pin on the right IC in the remote, that would be ideal.

I already have it set to press the buttons multiple times, but it seems that if it's going to fail, every level of redundancy also fails. I'd rather get to the root of the failure rather than try to circumvent it with redundancy anyway.

The lamp on the remote could flash, even if you find the right pin on the remote it could still be a communication failure, unless by remote you mean the wall socket part, that might could be a place to hot-wire, although Danger Will Robinson as they say. If it can be made reliable, you have a huge benefit in isolating the smarts and the power stuff.

Do you mean to say here that once there has been a failure, nothing works again? So that saying "on" every minute would never eventual get through? If you are having to (or have by coincidence) somehow reset the receiver(s) and/or transmitter after a failure it could mean some kind of lock-up there. I feel the remote/plug combo is your, er, weak link.

I appreciate and approve of wanting to find the root issue here. Having done, there is no shame in adding redundancy and feedback. Where would rock and roll be without feedback?!

a7