Timer Steuerung (Zeitschaltuhr) mit RTC Modul

Rhetorische Frage:
Welche Code-Version wird von Anfängern wohl bevorzugt?
Die Code-Version die am leichtesten verständlich ist.
Hier die Funktion Lüfter ein Zeitintervall A eingeschaltet, ein anderes Zeitintervall B ausgeschaltet


#define EIN LOW   // Relais is low-aktiv umgekehrte Logik: LOW entspricht eingeschaltet
#define AUS HIGH  // HIGH entspricht ausgeschaltet

const byte PIN_RELAY2_LUFT = 10;

void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) );
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) );
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

unsigned long MyLuefterTimer = 0;  // Timer-variables MUST be of type unsigned long
const byte    OnBoard_LED    = 2;

// für die korrekte Berechnung von Variablen vom Typ unsigned long "UL" an die Zahl anhängen
//                                          2 Minuten * 60 Sekunden * 1000 Millisekunden
const unsigned long LuefterLaueftIntervall =   2      * 60          * 1000UL;
const unsigned long LuefterAusIntervall    =   1      * 60          * 1000UL;
unsigned long SchaltIntervall;
boolean LuefterLaueft;


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}


void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();
  pinMode(PIN_RELAY2_LUFT, OUTPUT);

  // Initialisierung mit Lüfter wird eingeschaltet
  LuefterLaueft = true;
  SchaltIntervall = LuefterLaueftIntervall;
  digitalWrite(PIN_RELAY2_LUFT, EIN);

  MyLuefterTimer = millis(); // In der Timer-Variable den akteuellen Zeitstempel speichern
}


void LuefterIntervallSchaltung() {
    if ( TimePeriodIsOver(MyLuefterTimer, SchaltIntervall) ) {
    // Luefter wird geschaltet.
    // Der Zeitstempel des Schaltens wird in Variable MyLuefterTimer gespeichert
    // und automatisch bei jedem "Ablauf" des Timers von der 
    // function TimePeriodIsOver  aktualisiert = neu gespeichert
    // wenn die Anzahl Millisekunden vergangen ist die in der Variable
    // SchaltIntervall steht dann wird der Code in der If.Bedingung ausgeführt

    if (LuefterLaueft) {
      digitalWrite(PIN_RELAY2_LUFT, AUS);
      LuefterLaueft = false;
      SchaltIntervall = LuefterAusIntervall;
    }
    else {
      digitalWrite(PIN_RELAY2_LUFT, EIN);
      LuefterLaueft = true;
      SchaltIntervall = LuefterLaueftIntervall;      
    }    
  }
}

void loop() {
  BlinkHeartBeatLED(OnBoard_LED, 250);
  LuefterIntervallSchaltung();
}

Und hier die Version mit zusätzlichem komfortablen debug-output

// MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * 

https://forum.arduino.cc/t/comfortable-serial-debug-output-short-to-write-fixed-text-name-and-content-of-any-variable-code-example/888298
#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a 
// Serial.print is executed
// end of macros dbg and dbgi
// MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * 


#define EIN LOW   // Relais is low-aktiv umgekehrte Logik: LOW entspricht eingeschaltet
#define AUS HIGH  // HIGH entspricht ausgeschaltet

const byte PIN_RELAY2_LUFT = 10;

void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) );
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) );
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

unsigned long MyLuefterTimer =  0;  // Timer-variables MUST be of type unsigned long
const byte    OnBoard_LED    = 13;

// für die korrekte Berechnung von Variablen vom Typ unsigned long "UL" an die Zahl anhängen
//                                          2 Minuten * 60 Sekunden * 1000 Millisekunden
const unsigned long LuefterLaueftIntervall =   2      * 60          * 1000UL;
const unsigned long LuefterAusIntervall    =   1      * 60          * 1000UL;
unsigned long SchaltIntervall;
boolean LuefterLaueft;


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}


void setup() {
  Serial.begin(115200);
  Serial.println( F("Setup-Start") );
  PrintFileNameDateTime();
  pinMode(PIN_RELAY2_LUFT, OUTPUT);

  // Initialisierung mit Lüfter wird eingeschaltet
  LuefterLaueft = true;
  SchaltIntervall = LuefterLaueftIntervall;
  digitalWrite(PIN_RELAY2_LUFT, EIN);

  MyLuefterTimer = millis(); // In der Timer-Variable den akteuellen Zeitstempel speichern
}


void LuefterIntervallSchaltung() {

    // dbgi: "printe" nur EINMAL alle 1000 Millisekunden 
    dbgi("1:",LuefterLaueft,1000);
    dbgi("2:",SchaltIntervall,1000);
    dbgi("Laufzeit",millis() - MyLuefterTimer,1000);
    dbgi("",0,1000);
    
    if ( TimePeriodIsOver(MyLuefterTimer, SchaltIntervall) ) {
    // Luefter wird geschaltet.
    // Der Zeitstempel des Schaltens wird in Variable MyLuefterTimer gespeichert
    // und automatisch bei jedem "Ablauf" des Timers von der 
    // function TimePeriodIsOver  aktualisiert = neu gespeichert
    // wenn die Anzahl Millisekunden vergangen ist die in der Variable
    // SchaltIntervall steht dann wird der Code in der If.Bedingung ausgeführt

    if (LuefterLaueft) {
      digitalWrite(PIN_RELAY2_LUFT, AUS);
      LuefterLaueft = false;
      SchaltIntervall = LuefterAusIntervall;
      dbg("Lüfter",LuefterLaueft);    // gib bei JEDEM Aufruf auf die serielle Schnittstelle aus
      dbg("Lüfter",SchaltIntervall);
    }
    else {
      digitalWrite(PIN_RELAY2_LUFT, EIN);
      LuefterLaueft = true;
      SchaltIntervall = LuefterLaueftIntervall;      
      dbg("Lüfter",LuefterLaueft);
      dbg("Lüfter",SchaltIntervall);
    }    
  }
}

void loop() {
  BlinkHeartBeatLED(OnBoard_LED, 250);
  LuefterIntervallSchaltung();
}

Die Zahlenwerte der function millis() springen nach 49,7 Tagen vom Maxwert auf 0.

aber

Wenn man die Variablen die man für das Timing benutzt als Variablen-Typ
unsigned long deklariert
dann funktioniert die If-Bedingung

if ( millis() - StartZeit >= Intervall)

immer

Die Berechnung millis() - StartZeit führt immer zu einem positiven Ergebnis.
Das ist eine Eigenheit der unsigned Integermathematik.
unsigned heißt ohne Vorzeichen = nur positive Zahlen.
Deswegen kann das Programm Jahrzehntelang laufen und es funktioniert.

vgs