/* NeoPixel Clock 3
   Auf drei Neopixel-Ringen wird die Uhrzeit angezeigt.
   Stunde = rot, Minute = grün, Sekunde = blau
   Der äußere Ring hat 60 LEDs, der 2. 48, der innere 40.
   Wenn weniger LEDs im Ring sind werden, dann wird mit der
   map-Funktion auf die nächste LED gerundet.
   LED_COUNT ist die Anzahl der verfügbaren LEDs,
   Pixel_Nr = map ( Sekunde, 0, 59, 0, LED_COUNT-1)

   Stellen der Uhrzeit über die serielle Schnittstelle
   Sende ? und der Controller antwortet mit seiner Uhrzeit
   Sende !12:05:50 und der Controller übernimmt die Uhrzeit 12:05:50
   Sende m0 oder m1 oder m2 um die Anzeige-Betriebsart umzutellen
   m0 => nur ein Pixel in den Grundfarben für die Anzeige, die Ausgabe
         der Stunde hat Vorrang vor Minute und Sekunde
   m1 => wenn das selbe Pixel von mehrern Zeiten genutzt ist, dann wird
         eine Mischfarbe ausgegeben
   m2 => wie m1, zusätzlich hat die Sekunde einen "Schweif" von 5 Pixeln
   Sende n (Normal-Modus) oder d (Demo-Modus, Zeit läuft schneller)

       Funktionen und Datenstruktur für NTP-Zugriff siehe
     https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-time.c
     Die Struktur tm findet sich in der time.h (Arduino/C Standardbibliothek)
     struct tm {
        int8_t          tm_sec;   //< seconds after the minute - [ 0 to 59 ]
        int8_t          tm_min;   //< minutes after the hour - [ 0 to 59 ]
        int8_t          tm_hour;  //< hours since midnight - [ 0 to 23 ]
        int8_t          tm_mday;  //< day of the month - [ 1 to 31 ]
        int8_t          tm_wday;  //< days since Sunday - [ 0 to 6 ]
        int8_t          tm_mon;   //< months since January - [ 0 to 11 ]
        int16_t         tm_year;  //< years since 1900
        int16_t         tm_yday;  //< days since January 1 - [ 0 to 365 ]
        int16_t         tm_isdst; //< Daylight Saving Time flag
     };

     Zugriff auf einzelne Komponenten:
     sekunde = timeinfo.tm_sec;
     minute = timeinfo.tm_min;
     stunde = timeinfo.tm_hour;
     tag = timeinfo.tm_mday;
     monat = timeinfo.tm_mon + 1;        // Monat 0 = Januar => wird geändert zu Januar = 1
     jahr = timeinfo.tm_year + 1900;
     wochentag = timeinfo.tm_wday;       // Sonntag = 0, Montag = 1 etc.
*/

// NEOPIXEL BEST PRACTICES for most reliable operation:
// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections.
// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel.
// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR.
// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS
//   connect GROUND (-) first, then +, then data.
// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip,
//   a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED.
// (Skipping these may work OK on your workbench but can fail in the field)
#include <WiFi.h>
#include <Adafruit_NeoPixel.h>


// NTP Einstellungen
#define TIMEZONE 1                            // Zeitzone: UTC = 0, Winterzeit MEZ: UTC+1, Sommerzeit MESZ: UTC+2 
#define DST_OFFSET 3600                       // Offset für Winterzeit 0, für Sommerzeit 3600 (DST: daylight saving time)
#define NTP_SERVER "ptbtime1.ptb.de"         // Zeitserver der physikalisch-technischen Bundesanstalt in Braunschweig
#define UTC_OFFSET     0
#define UTC_OFFSET_DST 0

//#include <credentials.h>
const char* ssid = "Wokwi-GUEST";
const char* password = "";
const byte  channel = 6;    // wifi channel only for Wokwi

// Which pin on the Arduino is connected to the NeoPixels?
#define LED_PIN   17

// How many NeoPixels are attached to the Arduino?
#define LED_RING1 60      // für die Sekunden
#define LED_RING2 48      // jede 4. LED für Stunden
#define LED_RING3 40      // jede 10. LED für 3/6/9/12
#define LED_COUNT LED_RING1+LED_RING2+LED_RING3

// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
// Argument 1 = Number of pixels in NeoPixel strip
// Argument 2 = Arduino pin number (most are valid)
// Argument 3 = Pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
//   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)


// Globale Variablen
byte Stunde, Minute, Sekunde, Mode;
int ColorStunde = 0xFF0000, ColorMinute = 0x00FF00, ColorSekunde = 0x0000FF, msPerSec = 1000;
long NextTime=0, timeRaw;
unsigned long ShowTime, UpdateTime;
// ntp timestamp
struct tm timeinfo;

// setup() function -- runs once at startup --------------------------------
void setup() {
  Serial.begin(115200);
  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();            // Turn OFF all pixels ASAP
  strip.setBrightness(200); // Set BRIGHTNESS to about 1/5 (max = 255)
  Mode = 0;
  // Fill along the length of the strip in various colors...
  colorWipe(strip.Color(255,   0,   0), 20); // Red
  colorWipe(strip.Color(  0, 255,   0), 15); // Green
  colorWipe(strip.Color(  0,   0, 255), 00); // Blue
  WiFiStart();
  configTime(UTC_OFFSET, UTC_OFFSET_DST, NTP_SERVER);
  setTimezone("CET-1CEST,M3.5.0,M10.5.0/3");
  //getLocalTime(&timeinfo);    // start with correct time from NTP server
  //Sekunde = timeinfo.tm_sec;
  //Minute = timeinfo.tm_min;
  //Stunde = timeinfo.tm_hour;
  //AusgabeZeit();
  Stunde = 10;             // start with a given time
  Minute = 0;
  Sekunde = 0;
}


// loop() function -- runs repeatedly as long as board is on ---------------

void loop() {
  // Uhrzeit bestimmen (grob mit millis(), exakt wird es mit Timer)
  if (millis() >= NextTime) {
    NextTime = millis() + msPerSec;
    AusgabeZeit();
    Sekunde ++;
    if ( Sekunde == 60 ) {
      Sekunde = 0;
      Minute ++;
      if ( Minute == 60 ) {
        Minute = 0;
        Stunde ++;
        if ( Stunde == 24 ) Stunde = 0;
      }
    }
  }
  // Stellen der Uhrzeit über die serielle Schnittstelle
  // Sende ? und der Controller antwortet mit seiner Uhrzeit
  // Sende !12:05:50 und der Controller übernimmt die Uhrzeit 12:05:50
  if (Serial.available() != 0) {
    char Zeichen = Serial.read();
    if (Zeichen == '?') {         // Befehl '?' Uhrzeit ausgeben
      Serial.printf("Uhrzeit: %2d:%02d:%02d \n", Stunde, Minute, Sekunde);
    }
    if (Zeichen == '!') {         // Befehl '!' Uhrzeit stellen
      Stunde = Serial.parseInt();
      if (Stunde > 23) Stunde = 0;
      Minute = Serial.parseInt();
      if (Minute > 59) Minute = 0;
      Sekunde = Serial.parseInt();
      if (Sekunde > 59) Sekunde = 0;
      Serial.flush(); // restliche Zeichen im Puffer löschen
    }
    if (Zeichen == 'm') {       // Betriebsart einstellen
      Mode = Serial.parseInt();
      if (Mode > 2) Mode = 0;
    }
    if (Zeichen == 'h') {       // Helligkeit einstellen
      int dimm = Serial.parseInt();
      if (dimm <= 255) strip.setBrightness(dimm);
    }
    if (Zeichen == 'd') {       // Demo-Modus, Uhr läuft schneller
      msPerSec = 10;
    }
    if (Zeichen == 'n') {       // Normal-Modus
      msPerSec = 1000;
      // jetzt muss die Uhrzeit wieder gestellt werden!
    }
    if (Zeichen == 'z') { // Zeit vom Zeitserver holen
      msPerSec = 1000;
      // jetzt wird die Uhrzeit vom NTP-Server gelesen
      getLocalTime(&timeinfo);
      AusgabeZeit();
    }
  }
}


void AusgabeZeit ( ) {
  int Sekunde_Pixel, Minute_Pixel, Stunde_Pixel, Stunde_Anteil;
  Sekunde_Pixel = (map ( Sekunde, 0, 59, 0, LED_RING1 - 1) ) % LED_RING1;
  //Sekunde_Pixel = (map ( Sekunde, 0, 59, 0, LED_RING1 - 1) + 30) % LED_RING1; // connector on top
  Minute_Pixel = (map ( Minute, 0, 59, 0, LED_RING1 - 1) ) % LED_RING1;
  //Minute_Pixel = (map ( Minute, 0, 59, 0, LED_RING1 - 1) + 30) % LED_RING1;
  // Umrechnung der 12h Zeit in Minuten. (0:00 = 0 Min, 11:59 = 719 Min)
  Stunde_Anteil = (Stunde % 12) * 60 + Minute; // für 12h gibt es 12*60 Minuten
  Stunde_Pixel = (map( Stunde_Anteil, 0, 719, 0, LED_RING1 - 1) ) % LED_RING1;
  //Stunde_Pixel = (map( Stunde_Anteil, 0, 719, 0, LED_RING1 - 1) + 30) % LED_RING1;
  strip.clear();
  // Stunden-Ticks zeichnen
  for (int k = 0; k < LED_RING1; k += LED_RING1 / 12)
    strip.setPixelColor( k, 0xeeeeee);            // Stunden-Positionen 1. Ring
  for (int k = 0; k < LED_RING2; k += LED_RING2 / 12)
    strip.setPixelColor(LED_RING1 + k, 0xaaaaaa); // Stunden-Positionen 2. Ring
  // innerer Ring mit 40 LEDs nur für 4 Positionen geeignet
  for (int k = 0; k < LED_RING3; k += LED_RING3 / 4)
    strip.setPixelColor(LED_RING1 + LED_RING2 + k, 0x999999); // 3h/6h/9h/12h-Position 3. Ring

  if (Mode == 0) {        // Nur Grundfarben ausgeben
    strip.setPixelColor(Sekunde_Pixel, ColorSekunde);
    strip.setPixelColor(Minute_Pixel, ColorMinute); // Minute überschreibt Sekundenpixel
    strip.setPixelColor(Stunde_Pixel, ColorStunde); // Stunde überschreibt darunter liegendes Pixel
  }
  else if (Mode == 1) {   // Mischfarbe bei gleichem Pixel
    int colorSt = ColorStunde, colorMi = ColorMinute, colorSe = ColorSekunde;
    if (Sekunde_Pixel == Minute_Pixel) {
      colorSe = colorMi = 0x00FFFF;
    }
    if (Sekunde_Pixel == Stunde_Pixel) {
      colorSe = colorSt = 0xFF00FF;
    }
    if (Stunde_Pixel == Minute_Pixel) {
      colorSt = colorMi = 0xFFFF00;
      if (Stunde_Pixel == Sekunde_Pixel) { // alle 3 gleich!
        colorSt = colorMi = colorSe = 0xFFFFFF;
      }
    }
    strip.setPixelColor(Sekunde_Pixel, colorSe);
    strip.setPixelColor(Minute_Pixel, colorMi); // Minute überschreibt Sekundenpixel
    strip.setPixelColor(Stunde_Pixel, colorSt); // Stunde überschreibt darunter liegendes Pixel
  }
  else if (Mode == 2) {   // Mischfarbe bei gleichem Pixel, Sekunde hat einen Schweif
    int colorSt = ColorStunde, colorMi = ColorMinute, colorSe = ColorSekunde;
    if (Sekunde_Pixel == Minute_Pixel) {
      colorSe = colorMi = 0x00FFFF;
    }
    if (Sekunde_Pixel == Stunde_Pixel) {
      colorSe = colorSt = 0xFF00FF;
    }
    if (Stunde_Pixel == Minute_Pixel) {
      colorSt = colorMi = 0xFFFF00;
      if (Stunde_Pixel == Sekunde_Pixel) { // alle 3 gleich!
        colorSt = colorMi = colorSe = 0xFFFFFF;
      }
    }
    strip.setPixelColor((Sekunde_Pixel + LED_RING1 - 5) % LED_RING1, 0x00000F);
    strip.setPixelColor((Sekunde_Pixel + LED_RING1 - 4) % LED_RING1, 0x00001F);
    strip.setPixelColor((Sekunde_Pixel + LED_RING1 - 3) % LED_RING1, 0x00002F);
    strip.setPixelColor((Sekunde_Pixel + LED_RING1 - 2) % LED_RING1, 0x00004F);
    strip.setPixelColor((Sekunde_Pixel + LED_RING1 - 1) % LED_RING1, 0x00008F);
    strip.setPixelColor(Sekunde_Pixel, colorSe);
    strip.setPixelColor(Minute_Pixel, colorMi); // Minute überschreibt Sekundenpixel
    strip.setPixelColor(Stunde_Pixel, colorSt); // Stunde überschreibt darunter liegendes Pixel
  }
  strip.show();
}

///////////////////
// (re-)start WiFi
///////////////////
void WiFiStart()
{
  // Connect to WiFi network
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  //WiFi.begin("Wokwi-GUEST", "", 6);
  WiFi.begin(ssid, password, channel);

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");

  // Print the IP address
  Serial.println(WiFi.localIP());

}


void setTimezone(String timezone){
  Serial.printf("  Setting Timezone to %s\n",timezone.c_str());
  setenv("TZ",timezone.c_str(),1);  //  Now adjust the TZ.  Clock settings are adjusted to show the new local time
  tzset();
}

void colorWipe(uint32_t color, int wait) {
  for (int i = 0; i < strip.numPixels(); i++) { // For each pixel in strip...
    strip.setPixelColor(i, color);         //  Set pixel's color (in RAM)
    strip.show();                          //  Update strip to match
    delay(wait);                           //  Pause for a moment
  }
}
Taster2
Taster4
LED32
LED33
ESP32 Schulboard mit LEDs und Tastern, I2C-Bus mit Standard-Belegung
SSD1306 Display
WS2812 an Pin26