Here's my attempt:
const byte RelayCount = 4;
bool ARelayIsOn = false;
const unsigned long SECONDS = 1000UL;
const unsigned long MINUTES = 60 * SECONDS;
struct Relay
{
const byte RelayPin;
unsigned long OnInterval;
unsigned long OffInterval;
bool readyToTurnOn;
bool isOn;
unsigned long StartTime;
} Relays[RelayCount] =
{
{4, 3 * SECONDS, 15 * MINUTES, true, false, 0},
{5, 3 * SECONDS, 15 * MINUTES, true, false, 0},
{6, 3 * SECONDS, 15 * MINUTES, true, false, 0},
{7, 3 * SECONDS, 15 * MINUTES, true, false, 0}
};
void TurnRelayOn(int r)
{
ARelayIsOn = true;
Relays[r].readyToTurnOn = false;
Relays[r].isOn = true;
Relays[r].StartTime = millis(); // Start of OnInterval
digitalWrite(Relays[r].RelayPin, LOW); // LOW for ON
}
void TurnRelayOff(int r)
{
ARelayIsOn = false; // Only one is on at a time
Relays[r].readyToTurnOn = false;
Relays[r].isOn = false;
Relays[r].StartTime = millis(); // Start of OffInterval
digitalWrite(Relays[r].RelayPin, HIGH); // HIGH for OFF
}
void setup()
{
for (int r = 0; r < RelayCount; r++)
{
digitalWrite(Relays[r].RelayPin, HIGH); // HIGH for OFF
pinMode(Relays[r].RelayPin, OUTPUT);
}
}
void loop()
{
unsigned long currentMillis = millis();
if (ARelayIsOn)
{
// A relay is on so find the on one and see if it is
// time to turn off
for (int r = 0; r < RelayCount; r++)
{
if (Relays[r].isOn)
{
// See if it is time to
if (currentMillis - Relays[r].StartTime >= Relays[r].OnInterval)
{
// Time to turn off
TurnRelayOff(r);
}
}
}
}
if (!ARelayIsOn)
{
// No relay is on so look for one that is ready to turn on
for (int r = 0; r < RelayCount; r++)
{
if (Relays[r].readyToTurnOn)
{
TurnRelayOn(r);
break; // Don't look for more to turn on!
}
}
}
// Look for any relay that is off and see if it is ready to turn on
for (int r = 0; r < RelayCount; r++)
{
if (!Relays[r].isOn && !Relays[r].readyToTurnOn)
{
if (currentMillis - Relays[r].StartTime >= Relays[r].OffInterval)
Relays[r].readyToTurnOn = true;
}
}
}