Zeitausgabe unterbrechen für Feiertage/Events

Hallo liebe Community,

irgendwie habe ich ein Brett vorm Kopf… Ich habe es zwar hinbekommen, dass er läuft aber es ist nicht schön.

Zur Zeit baue ich ja immer noch an meiner Wordclock. Das Anzeigen der Uhrzeit usw. klappt wunderbar. Aktuell möchte ich, dass an definierten Feiertagen zB. Weihnachten ein Tannenbaum für eine Minute angezeigt wird.

Ich habe euch einen minimalen Sketch erstellt, der mein “Chaos” zeigt und lauffähig ist:

#include <TimeLib.h> // Von Paul Stofferegen

// Variabeln
tmElements_t tm;
bool ZeigeEvent = false; // Status ob Event angezeigt wird -> um erneutes Beschreiben zu verhindern
byte EventAnzeige = 5; // In welchen Minuten Abständen sollen Events angezeigt werden (5er Schritte) - Event wird 1 Minute angezeigt
byte EventTage[][3] = { // Events außer Weihnachten und Silvester
  {14, 2, 0}, // Tag, Monat, Funktion
  {28, 10, 1},
};

time_t xmas; // Heiliger Abend
time_t adv1; // 1. Advent
time_t adv2; // 2. Advent
time_t adv3; // 3. Advent
time_t adv4; // 4. Advent


void setup() {
  Serial.begin(9600);
  setTime(12, 15, 45, 24, 12, 20); // Uhrzeit/Datum zum Testen (12:15:45 / 24.12.2020)
  AdventsTage(); // Die Adventtage aussrechnen und in die Variablen übegeben
}

void loop() {
  Events(); // Prüfen ob es ein "Event" -> Wenn ja 1 Minute anzeigen

  if (!ZeigeEvent) { // Uhrzeit nur Anzeigen wenn gerade _kein_ Event läuft
    Serial.print(hour());
    Serial.print(':');
    Serial.print(minute());
    Serial.print(':');
    Serial.println(second());
    delay(1000); // Delay nur zum Test hier
  }

}

// Funktionen
void AdventsTage() {
  tm.Second = tm.Hour = tm.Minute = 0;                    // Datum erzeugen: 00:00:00 Uhr am...
  tm.Day = 24;                                            // 24.
  tm.Month = 12;                                          // 12.
  tm.Year = year() - 1970;                                 // ...des aktuellen Jahres (minus 1970, weil Timestamp)
  xmas = makeTime(tm);                             // Timestamp aus Weihnachts-Datum erzeugen
  adv1 = xmas - 86400 * weekday(xmas) - 1728000;   // 1. Advent berechnen
  adv2 = adv1 + 604800;                            // 2. Advent = 1. Adv + 7 Tage (=604800 Sek)
  adv3 = adv2 + 604800;                            // 3. Advent
  adv4 = adv3 + 604800;                            // 4. Advent
}

void Events() {
  // Wenn es nur noch eine Minute bis zum Neujahr wird ein Countdown angezeigt
  if (day() == 31 && month() == 12 && hour() == 23 && minute() == 59 && second() > 0) {
    Serial.println("Silvester Countdown");
    // silvester_countdown(); // Muss vom loop() immer wieder aufgerufen werden
    ZeigeEvent = true;
  }
  // Haben wir das Neujahr erreicht wird ein Feuerwerk für 1 Minute angezeigt.
  else if (day() == 1 && month() == 1 && hour() == 0 && minute() == 0) {
    Serial.println("Feuerwerk anzeigen");
    // feuerwerk(); // Muss vom loop() immer wieder aufgerufen werden
    ZeigeEvent = true;
  }

  // Wenn es kein Silverster/Neujahr ist, wird geprüft ob die Minute Restlos teilbar ist
  else if (!(minute() % EventAnzeige)) {

    if ((month() == 11 || month() == 12)) {
      // 1. Advent
      if (now() >= adv1 && now() < adv1 + 86400) {

        if (!ZeigeEvent) {
          // Hier wird normalerweise mit FastLED eine Kerze dargestellt.
          Serial.println("1. Advent");
          ZeigeEvent = true; // Damit der 1. Advent nicht immer neu ausgeführt wird
        }
      }

      // 2. Advent
      else if (now() >= adv2 && now() < adv2 + 86400) {

        if (!ZeigeEvent) {
          Serial.println("2. Advent");
          ZeigeEvent = true;
        }
      }

      // 3. Advent
      else if (now() >= adv3 && now() < adv3 + 86400) {

        if (!ZeigeEvent) {
          Serial.println("3. Advent");
          ZeigeEvent = true;
        }
      }

      // 4. Advent - Und ist nicht gleich Weihnachten?
      else if (now() >= adv4 && now() < adv4 + 86400  && adv4 != xmas) {

        if (!ZeigeEvent) {
          Serial.println("4. Advent");
          ZeigeEvent = true;
        }
      }

      // Weihnachten + 1. und 2. Weihnachtstag
      else if (now() >= xmas && now() < xmas + (86400 * 3)) {

        if (!ZeigeEvent) {
          Serial.println("Weihnachtstage");
          ZeigeEvent = true;
        }
      }
    }

    for (byte i = 0; i < (sizeof(EventTage) / 3); i++) {
      if ((day() == EventTage[i][0]) && (month() == EventTage[i][1])) {

        switch (EventTage[i][2]) {
          case 0: // Valentinstag
            if (!ZeigeEvent) {
              Serial.println("Es ist Valentinstag");
              ZeigeEvent = true;
            }
            break;
          case 1: //Geburtstag
            if (!ZeigeEvent) {
              Serial.println("Char Array mit Lauftext wird gesetzt");
              ZeigeEvent = true;
            }
            Serial.println("Lauftext wird ausgegeben");
            // text_laufschrift();
            break;
        }
      }
    }
  }
  else {
    ZeigeEvent = false;
  }
}

Wie könnte man das Übersichtlicher gestalten? Sollte ich alle Daten in einem Timestamp wie bei den Adventstage umwandeln - und diese in einem Array ablegen?

Schön wäre es auch wenn man über alle “Events” mit einer Schleife laufen kann und dann prüft, ob heute das passende Datum ist und dann gleichzeitig weiß welche Funktion aufgerufen werden soll.

Vorstellen tue ich mir das so:

Events = {
   {TIMESTAMP, weihnachtsbaum()},
   {TIMESTAMP2,  valentinstag()}
};

Ich würde mich über Hilfe und Tipps sehr freuen :-).

Liebe Grüße,
Björn

Vorstellen tue ich mir das so

Statt

byte EventTage[][3]

stelle ich mir vereinheitlicht eher sowas vor:

typedef struct  {
 time_t& start;
 unsigned int dauer; // in Minuten 
 void (*func) (byte); // Funktion mit Parameter
 byte index; //  Parameter für Funktion
} Event;

Event events[] = {
{adv1,1440,advent,1) },
{adv2,1440,advent,2) },
{adv3,1440,advent,3) },
{adv4,1440,advent,4) },
{xmas,3*1440,baum,0) },
{silvester,1,countdown,0) },
{neujahr,1,feuerwerk,0)   },
{valentin,1440,geburtstag,0)},
{geb1,1440,geburtstag,1)    },
{geb2,1440,geburtstag,2)    }
};

Die Zeitstempel müssen mindestens jährlich einmal neu gesetzt werden (in setup) da sie die Jahreszahl enthalten, und dabei muss beachtet werden, ob das nächste Ereignis evtl. erst nächstes Jahr passieren wird (neujahr sicher, aber Vorsicht nicht nur bei valentin) :wink:

In der Struktur stehen Referenzen auf diese Zeitstempel und Funktionszeiger.
Vermutlich erleichtert es die Bearbeitung, wenn die Dauer eines Events auch gespeichert ist
(1 Minute für Countdown und Feuerwerk bis 3 Tage für Weihnachten)
Damit die gleiche Funktion leicht unterschiedlich verwendet werden kann (advent), ist eine Funktion mit Parameter sinnvoll. Das gilt dann natürlich für alle Funktionszeiger.

Die Funktionen müssen so definiert sein

void advent(byte index) {
   // je nach index 1 bis 4 Kerzen anzeigen
}
void countdown(byte) {
  // silvestercountdown, Parameter wird ignoriert
}

Hallo Michael,

vielen vielen Dank für deine Hilfe :grin:!

Ich habe mein Sketch nun so umgebaut, dass er mit deinem Struct funktioniert. Ich habe lediglich die Minuten durch Sekunden im Struct Array (?) abgeändert.

Das setzen der Feiertage hab ich nun in eine eigene Funktion gesetzt, die im Setup aufgerufen wird. Werde dafür noch eine Funktion erstellen, dass die Funktion immer zum neuen Jahr automatisch aufgerufen wird - falls kein Neustart erfolgte.

#include <TimeLib.h> // Von Paul Stofferegen

// Variabeln
tmElements_t tm;
bool ZeigeEvent = false; // Status ob Event angezeigt wird -> um erneutes Beschreiben zu verhindern
byte EventAnzeige = 5; // In welchen Minuten Abständen sollen Events angezeigt werden (5er Schritte) - Event wird 1 Minute angezeigt

time_t xmas; // Heiliger Abend
time_t adv1; // 1. Advent
time_t adv2; // 2. Advent
time_t adv3; // 3. Advent
time_t adv4; // 4. Advent
time_t silvester; // Silvester 23:59:01
time_t neujahr; // Neujahr 00:00:00

typedef struct  {
  time_t& start;
  unsigned int dauer; // in Minuten
  void (*func) (byte); // Funktion mit Parameter
  byte index; //  Parameter für Funktion
} Event;

void advent(byte index) {
  static byte oldMinute = 100;
  byte aktMinute = minute();

  if ( !(aktMinute % EventAnzeige)) { // Minute restlos Teilbar?
    if ( oldMinute != aktMinute ) { // Funktion nur 1x Aufrufen
      oldMinute = aktMinute;
      switch (index) {
        case 1:
          Serial.println("1. Advent");
          break;
        case 2:
          Serial.println("2. Advent");
          break;
        case 3:
          Serial.println("3. Advent");
          break;
        case 4:
          Serial.println("4. Advent");
          break;
      }
    }
  }
  else { // Wenn Minuten nicht ohne Rest teilbar wird die Uhrzeit angezeigt
    ZeigeEvent = false;
  }
}

void countdown(byte) {
  // silvestercountdown, Parameter wird ignoriert
  // Funktion soll bei jedem Aufruf ausgeführt werden
  byte testcounter = 60;
  Serial.print("Countdown : ");  Serial.println(testcounter - second());
}

void feuerwerk(byte) {
  Serial.print("Feuerwerk : ");  Serial.println(second());
}

Event events[] = {
  {adv1, 86400, advent, 1 },
  {adv2, 86400, advent, 2 },
  {adv3, 86400, advent, 3 },
  {adv4, 86400, advent, 4 },
  //{xmas, 3 * 86400, baum, 0) },
  {silvester, 59, countdown, 0 },
  {neujahr, 60, feuerwerk, 0   },
  //{valentin,1440,geburtstag,0)},
  //{geb1,1440,geburtstag,1)    },
  //{geb2,1440,geburtstag,2)    }
};

void setup() {
  Serial.begin(9600);
  setTime(23, 59, 50, 31, 12, 20); // Uhrzeit/Datum zum Testen (12:15:45 / 24.12.2020)
  Feiertage(); // Timestamps der Feiertage generieren
  Serial.println(); // Nur zum Test ;-)
}

void loop() {
  showEvents(); // Prüfen ob es ein "Event" -> Wenn ja 1 Minute anzeigen

  if (!ZeigeEvent) { // Uhrzeit nur Anzeigen wenn gerade _kein_ Event läuft
    Serial.print(hour());
    Serial.print(':');
    Serial.print(minute());
    Serial.print(':');
    Serial.println(second());
  }

  delay(1000); // Delay nur zum Test hier
}

// Timestamps der Feiertage generieren
void Feiertage() {
  tm.Second = tm.Hour = tm.Minute = 0;             // Datum erzeugen: 00:00:00 Uhr am...
  tm.Day = 24;                                     // 24.
  tm.Month = 12;                                   // 12.
  tm.Year = year() - 1970;                         // ...des aktuellen Jahres (minus 1970, weil Timestamp)
  xmas = makeTime(tm);                             // Timestamp aus Weihnachts-Datum erzeugen
  adv1 = xmas - 86400 * weekday(xmas) - 1728000;   // 1. Advent berechnen
  adv2 = adv1 + 604800;                            // 2. Advent = 1. Adv + 7 Tage (=604800 Sek)
  adv3 = adv2 + 604800;                            // 3. Advent
  adv4 = adv3 + 604800;                            // 4. Advent

  tm.Hour = 23;
  tm.Minute = 59;
  tm.Second = 1;
  tm.Day = 31;
  tm.Month = 12;
  tm.Year = year() - 1970;
  silvester = makeTime(tm);

  tm.Hour = tm.Minute = tm.Second = 0;
  tm.Day = 1;
  tm.Month = 1;
  tm.Year = year() - 1970 + 1; // Da Neujahr in der Zukunft liegt
  neujahr = makeTime(tm);
}

void showEvents() {
  byte t = sizeof(events) / sizeof(Event); // Anzahl der Events
  unsigned long aktZeit = now();
  ZeigeEvent = false; // Auf false setzen, falls kein Event gefunden wird

  for (byte i = 0; i < t; i++) { // Durchlaufe jedes Event
    // Prüfe ob das Event größer oder gleich dem aktuellen Timestamp ist und gleichzeitig
    // noch kleiner dem aktuellen Timestamp wenn start+dauer addiert werden.
    if (events[i].start <= aktZeit && (events[i].start + events[i].dauer) > aktZeit ) {
      ZeigeEvent = true;
      events[i].func(events[i].index); // Funktion des Events aufrufen
    }
  }
}

Wenn dir noch was ins Auge springt was nicht passt, gerne frei raus :).

Ich wünsche Dir und der restlichen Community ein schönes Wochenende.

Liebe Grüße,
Björn

Fehlt natürlich noch die Funktion geburtstag (byte) und der switch in der Funktion advent() ist auch sehr holprig

void advent (byte index) {
    Serial.print ((int)index);
    Serial.println (". Advent");
}

Die Umwandlung einer Zahl in einen Text macht zwar print, ist aber von Hand hier auch sehr einfach:

Serial.write ('0'+index);