Sketch bleibt stehen - Verzweifelte Lösungssuche

Hallo zusammen,

ich bin glaube ich kein völliger Anfänger mehr im Arduino Umfeld, Wissen fehlt mir aber trotzdem noch einiges. Trotzdem bin ich auf meine bisherige Arbeit mehr als Stolz.

Leider verzweifle ich gerade und komme nicht mehr weiter. Oder anders gesagt: Ich bin mit meinem Latein echt am Ende :pensive:

Ich suche deshalb bei euch nach Hilfe und hoffe hier findet sich jemand/oder mehrere, die mir helfen können. Vielleicht ja @fony?

Was macht mein Projekt:
Es ist eine Steuerung für ein Balkonkraftwerk und funktioniert mittlerweile so wie es soll. Die Motorpositionen werden entsprechend der aktuellen Uhrzeit angesteuert, der Windsensor regelt ebenfalls und mittlerweile ist sogar via MQTT eine Steuerung über Homeassistant und App machbar. Soweit so gut.

Mein Problem ist, dass der Sketch einmal am Tag einfach stehen bleibt. Die Zeit, wann dies geschieht ist dabei immer eine andere. Heute war es bereits nach zwei Stunden der Fall, gestern erst nach 13 Stunden. Ich kann also nicht sagen, es hakt immer an der gleichen Stelle oder Uhrzeit.

Der Arduino UNO Wifi Rev2 wird jeden Tag um 06.00 Uhr gestartet und um 22.00 Uhr wieder abgeschaltet (Zeitschaltuhr).

Ich habe versucht in meinem Sketch einen Abbruch der WLAN-Verbindung abzufangen, die Zeit regelmäßig zu aktualisieren um Abstürze zu minimieren.

Ich finde aber keinen Weg, dies zu verhindern (das Hängenbleiben des Sketch)

Habe ich ggf. irgendwo Mist im Code drin, der so etwas verursachen könnte?

Ich hoffe ihr könnt mir helfen.

Anbei mein Sketch. Ich hoffe es ist verständlich. Ich habe zumindest immer versucht entsprechend zu kommentieren.

#include <WiFiNINA.h>       // Bibliothek für die WLAN-Kommunikation mit dem Arduino NINA-WiFi-Modul
#include <AccelStepper.h>   // Bibliothek für die Steuerung von Schrittmotoren mit Beschleunigung
#include <TimeLib.h>        // Bibliothek für die Manipulation und Abfrage von Zeitinformationen
#include <WiFiUdp.h>        // Bibliothek für die Kommunikation über UDP/IP-Protokoll im WLAN
#include <PubSubClient.h>   // MQTT-Bibliothek für die Verbindung und Kommunikation mit einem MQTT-Broker
#include <ArduinoJson.h>    // Bibliothek für die Bearbeitung von JSON-Daten
#include "secrets.h"        // Header-Datei für geheime Zugangsdaten (z. B. WLAN-SSID, Passwort, MQTT-Benutzername und -Passwort)

// Pin-Belegungen für den Schrittmotor
#define dirPin 2                  // Richtungs-Pin des Schrittmotors
#define stepPin 4                 // Schritt-Pin des Schrittmotors
#define motorInterfaceType 1      // Art der Motorsteuerung (1 für Treiber wie A4988 oder DRV8825)
#define PIN_BUTTON 12             // Pin für den Windsensor

// Intervall für die Überprüfung der WLAN-Verbindung (10 Minuten)
#define WIFI_CHECK_INTERVAL 600000

// Zugangsdaten für WLAN und MQTT
const char* mqtt_server = "192.168.2.2";    // IP-Adresse oder Domain des MQTT-Brokers
const int mqtt_port = 1883;                 // MQTT-Port (standardmäßig 1883)
const char* mqtt_user = MQTT_USER;          // MQTT-Benutzername
const char* mqtt_password = MQTT_PASSWORD;  // MQTT-Passwort
char ssid[] = WIFI_SSID;                    // WLAN-SSID
char pass[] = WIFI_PASSWORD;                // WLAN-Passwort

// Zeit- und Zeitstempelvariablen
unsigned long lastUpdateTime = 0;  // Variable, um die Zeit des letzten Updates zu speichern
const int timeZoneOffset = 1;       // Zeitverschiebung für die Winterzeit (UTC+1)
int timeZone = 2;                   // Zeitzone für die Umrechnung von lokaler Zeit in UTC
unsigned long currentTimestamp = 0; // Variable für den aktuellen Zeitstempel
time_t currentTime;                 // Variable für die aktuelle Zeit

// Variablen für den Windschutz
unsigned long WindschutzTimer;        // Variable zur Speicherung der Zeit, seit der Windschutz aktiviert wurde
unsigned long WindWartezeit_ms = 900000;  // Zeitdauer in Millisekunden, nach dem der Windschutz automatisch deaktiviert wird (900000 ms = 15 Minuten)
boolean Warteposition_aktiv = false;  // Flag, das angibt, ob sich der Mechanismus in der Warteposition befindet

// Variable zur Speicherung des Zeitpunkts des letzten Sendens von Daten
unsigned long lastSendTime = 0;

//Position des Schrittmotors
const long Startposition = 17000;
const long Vormittag1 = 9000;
const long Vormittag2 = 2000;
const long Mittag1 = 0;
const long Mittag2 = -6000;
const long Nachmittag1 = -12000;
const long Nachmittag2 = -15000;
const long Nachmittag3 = -18000;
const long Endposition = -24000;
const long Windschutz = 0;

AccelStepper stepper(motorInterfaceType, stepPin, dirPin);  // Initialisierung des Schrittmotors

WiFiUDP ntpUDP;              // UDP-Objekt für die NTP-Zeitabfrage
IPAddress timeServerIP;      // IP-Adresse des Zeit-Servers für die NTP-Synchronisation
WiFiUDP udp;                 // UDP-Objekt für die Kommunikation
WiFiClient wifiClient;       // WiFi-Client-Objekt für die Verbindung zum MQTT-Broker
PubSubClient client(wifiClient);  // MQTT-Client-Objekt für die MQTT-Kommunikation
WiFiServer server(80);       // WiFi-Server-Objekt für die Verarbeitung von HTTP-Anfragen

unsigned long wifiCheckTimer = 0;  // Timer für die Überprüfung der WLAN-Verbindung

// Funktion zur Überprüfung, ob Sommerzeit aktiv ist
bool isDaylightSavingTime(time_t currentTime);

// Funktion zur Ermittlung des letzten Sonntags im Monat
int getLastSundayDay(int year, int month);

// Überprüfung, ob Sommerzeit aktiv ist
bool isDaylightSavingTime(time_t currentTime) {
  int currentYear = year(currentTime);
  int currentMonth = month(currentTime);
  int currentDay = day(currentTime);

  // Ermittlung des letzten Sonntags im März
  int lastSundayMarch = getLastSundayDay(currentYear, 3);

  // Ermittlung des letzten Sonntags im Oktober
  int lastSundayOctober = getLastSundayDay(currentYear, 10);

  // Sommerzeit beginnt am letzten Sonntag im März und endet am letzten Sonntag im Oktober
  bool isDST = ((currentMonth > 3 && currentMonth < 10) || (currentMonth == 3 && currentDay > lastSundayMarch) || (currentMonth == 10 && currentDay <= lastSundayOctober));

  return isDST;
}

// Ermittlung des letzten Sonntags im Monat
int getLastSundayDay(int year, int month) {
  const int lastDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };  // Anzahl Tage in jedem Monat

  int lastSunday = lastDays[month - 1];  // Annahme: Monat hat maximal 31 Tage
  tmElements_t timeInfo;

  // Suche den letzten Sonntag des Monats
  while (lastSunday > 0) {
    timeInfo.Year = year - 1970;  // Jahresversatz von 1970
    timeInfo.Month = month;
    timeInfo.Day = lastSunday;

    time_t time = makeTime(timeInfo);
    int dayOfWeek = weekday(time);

    if (dayOfWeek == 1) {  // Sonntag hat den Wert 1, unabhängig davon, wann die Woche beginnt
      return lastSunday;
    }

    lastSunday--;
  }

  return -1;  // Fehler, falls der letzte Sonntag nicht gefunden wird
}

/**
 * Initialisierung der Mikrocontroller-Hardware und Einrichtung der Kommunikationsprotokolle.
 */
void setup() {
  // Initialisierung der seriellen Kommunikation
  Serial.begin(9600);
  while (!Serial)
    ; // Warte, bis die serielle Kommunikation gestartet ist

  // Konfiguration des Schrittmotors
  stepper.setMaxSpeed(1400);
  stepper.setAcceleration(800);

  // Konfiguration der Pins
  pinMode(PIN_BUTTON, INPUT_PULLUP); // Pin für den Windsensor als Eingang mit Pull-Up-Widerstand konfigurieren
  pinMode(LED_BUILTIN, OUTPUT);      // Pin für die eingebaute LED als Ausgang konfigurieren

  // WLAN-Verbindung herstellen
  connectWiFi();

  // Zeit synchronisieren
  setSyncProvider(getNtpTime);

  // HTTP-Server starten
  server.begin();

  // Verbindung zum MQTT-Broker herstellen
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
  if (!client.connected()) {
    reconnect();
  }

  // Abonnement für das gewünschte Topic einrichten
  client.subscribe("solar/bkw-steuerung/app-steuerung");

  // Autodiscovery-Nachrichten senden
  sendMQTTAutoDiscovery();
}

/**
 * Die Haupt-Loop-Funktion, die wiederholt ausgeführt wird.
 * Überprüft die WLAN-Verbindung, den Windsensor und aktualisiert die NTP-Zeit.
 * Sendet MQTT-Daten an Home Assistant und verarbeitet MQTT-Nachrichten.
 */
void loop() {
  // Überprüfe die WLAN-Verbindung
  checkWiFiConnection();
  
  // Überprüfe den Zustand des Windsensors
  checkWindsensor();

  // Sende MQTT-Daten an Home Assistant
  sendMQTTData();

  // Aktualisiere die MQTT-Verbindung und verarbeite eingehende Nachrichten
  mqttLoop();

  // Aktualisiere die NTP-Zeit
  updateNtpTime();
}

/**
  * ###########################################
  * Bereich für WLAN-Verbindung und Zeitupdates
  * ###########################################
  */

/**
 * Stellt eine Verbindung zum WLAN her.
 * Führt mehrere Verbindungsversuche durch und gibt eine Fehlermeldung aus, wenn die Verbindung fehlschlägt.
 */
void connectWiFi() {
  int connectAttempts = 0; // Anzahl der Verbindungsversuche

  // Führe bis zu 10 Verbindungsversuche durch
  while (connectAttempts < 10) {
    WiFi.begin(ssid, pass); // WLAN-Verbindung mit den angegebenen Zugangsdaten herstellen
    unsigned long startTime = millis(); // Startzeit für den Verbindungsversuch

    // Warte bis zur erfolgreichen Verbindung oder bis 30 Sekunden vergangen sind
    while (WiFi.status() != WL_CONNECTED && millis() - startTime < 30000) {
      delay(1000); // Warte 1 Sekunde vor jedem erneuten Verbindungsversuch
    }

    // Wenn die Verbindung erfolgreich hergestellt wurde
    if (WiFi.status() == WL_CONNECTED) {
      Serial.println("WiFi verbunden");
      IPAddress ip = WiFi.localIP();
      //Serial.print("IP-Adresse: ");
      Serial.println(ip);
      return;  // Verbindung erfolgreich, beende die Funktion
    }

    // Wenn die Verbindung fehlschlägt, trenne die Verbindung und warte vor dem nächsten Versuch
    WiFi.disconnect();
    delay(30000); // Warte 30 Sekunden vor dem nächsten Verbindungsversuch

    connectAttempts++; // Erhöhe die Anzahl der Verbindungsversuche
  }

  // Wenn alle Verbindungsversuche fehlschlagen, gebe eine Fehlermeldung aus
  Serial.println("WiFi-Verbindung fehlgeschlagen");
}

/**
 * Überprüft die WLAN-Verbindung und stellt sie gegebenenfalls neu her.
 */
void checkWiFiConnection() {
  if (millis() - wifiCheckTimer >= WIFI_CHECK_INTERVAL) { // Überprüfe das WLAN-Verbindungsintervall
    wifiCheckTimer = millis();  // Timer zurücksetzen

    // Überprüfe die WLAN-Verbindung
    if (WiFi.status() != WL_CONNECTED) { // Wenn keine Verbindung besteht
      Serial.println("Verbindung verloren. Trenne WLAN-Verbindung und stelle neu her.");

      // Trenne WLAN-Verbindung
      WiFi.disconnect();

      // Warte, um sicherzustellen, dass die Verbindung getrennt wurde
      delay(1000);

      // Stelle die WLAN-Verbindung neu her
      connectWiFi();
    } else { // Wenn eine Verbindung besteht
      Serial.println("WLAN-Verbindung aktiv.");
    }
  }
}

/**
 * Holt die aktuelle Zeit von einem NTP-Zeitserver.
 * 
 * @return Die aktuelle Zeit als Unix-Zeitstempel (time_t)
 */
time_t getNtpTime() {
  // Warte, bis eine WLAN-Verbindung hergestellt ist
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
  }

  // Ermittle die IP-Adresse des NTP-Zeitservers ("pool.ntp.org")
  WiFi.hostByName("pool.ntp.org", timeServerIP);
  Serial.println("Zeitserver IP-Adresse erhalten");

  const int NTP_PACKET_SIZE = 48;
  byte packetBuffer[NTP_PACKET_SIZE];

  // Initialisiere das NTP-Paket
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  packetBuffer[0] = 0b11100011;
  packetBuffer[1] = 0;
  packetBuffer[2] = 6;
  packetBuffer[3] = 0xEC;
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;
  packetBuffer[15] = 52;

  WiFiUDP udp;
  udp.begin(123); // Starte die UDP-Kommunikation auf Port 123
  udp.beginPacket(timeServerIP, 123); // Beginne ein UDP-Paket zum NTP-Zeitserver
  udp.write(packetBuffer, NTP_PACKET_SIZE); // Sende das NTP-Paket
  udp.endPacket(); // Beende das UDP-Paket

  // Warte auf eine Antwort vom Zeitserver
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      udp.read(packetBuffer, NTP_PACKET_SIZE);
      // Extrahiere die Sekunden seit 1900 aus dem NTP-Paket
      unsigned long secsSince1900 = (unsigned long)packetBuffer[40] << 24 | (unsigned long)packetBuffer[41] << 16 | (unsigned long)packetBuffer[42] << 8 | (unsigned long)packetBuffer[43];
      
      // Berechne den Unix-Zeitstempel (Anzahl der Sekunden seit 1970) aus den Sekunden seit 1900
      time_t ntpTime = secsSince1900 - 2208988800UL;

      // Führe die Sommerzeit-Korrektur durch, falls erforderlich
      ntpTime += (isDaylightSavingTime(ntpTime) ? 3600 : 0);
      // Füge die Zeitzonenverschiebung hinzu
      ntpTime += (timeZoneOffset * 3600);

      // Gib die aktuelle Zeit zurück
      return ntpTime;
    }
    delay(10);
  }

  // Keine Antwort vom Zeitserver erhalten
  Serial.println("Keine Antwort vom Zeitserver");
  return 0; // Gib 0 zurück, um anzuzeigen, dass die Zeit nicht erfolgreich abgerufen wurde
}

/**
 * Aktualisiert die Systemzeit durch Synchronisierung mit einem NTP-Zeitserver.
 * Die Zeit wird alle 65 Minuten synchronisiert, sofern keine vorherige Synchronisierung erfolgt ist.
 */
void updateNtpTime() {
  unsigned long currentMillis = millis(); // Erfasse die aktuelle Zeit in Millisekunden
  static unsigned long previousSyncMillis = 0; // Speichere den Zeitpunkt des letzten Syncs
  const unsigned long syncInterval = 3900000;  // Synchronisiere die Zeit alle 65 Minuten

  // Überprüfe, ob es Zeit ist, die Zeit zu synchronisieren
  if (currentMillis - previousSyncMillis >= syncInterval || previousSyncMillis == 0) {
    // Wenn die Zeitdifferenz seit dem letzten Sync größer oder gleich dem Synchronisierungsintervall ist oder wenn noch keine vorherige Synchronisierung erfolgt ist

    // Aktualisiere die Zeit durch Aufrufen der getNtpTime() Funktion als Sync-Provider
    setSyncProvider(getNtpTime);
    previousSyncMillis = currentMillis;  // Aktualisiere den Zeitpunkt des letzten Syncs auf die aktuelle Zeit
  }
}

/**
  * ###########################################
  * Bereich für Motorsteuerung
  * ###########################################
  */

/**
 * Bewegt den Schrittmotor auf die angegebene Position, falls die aktuelle Position nicht bereits erreicht ist.
 * @param position Die Zielposition, zu der der Schrittmotor bewegt werden soll.
 */
void moveStepper(int position) {
  if (stepper.currentPosition() != position) {
    stepper.moveTo(position);  // Setzt die Zielposition des Schrittmotors
    stepper.runToPosition();    // Führt den Schrittmotor zur Zielposition aus
  }
}

/**
 * Aktualisiert die Position des Schrittmotors basierend auf der aktuellen Zeit und den definierten Schwellenwerten für Sommer-, Winter- und Zwischenpositionen.
 * Die Funktion bestimmt die Zielposition des Schrittmotors anhand der aktuellen Uhrzeit und des Monats. Je nach Monat werden unterschiedliche Schwellenwerte verwendet, um zwischen Sommer-, Winter- und Zwischenpositionen zu unterscheiden. 
 * Die Position wird entsprechend den definierten Schwellenwerten festgelegt, und der Schrittmotor wird auf die berechnete Position bewegt.
 */
// Diese Funktion aktualisiert die Position des Steppermotors basierend auf der aktuellen Zeit
void managePositions() {
  currentTime = now();  // Aktualisiere den Wert der globalen Variable currentTime
  int currentMonth = month(currentTime);
  int currentDay = day(currentTime);
  int currentHour = hour(currentTime);
  int currentMinute = minute(currentTime);
  int currentSecond = second(currentTime);
  int currentTimeInSeconds = currentHour * 3600 + currentMinute * 60;

  //Debugging
  //Serial.print("Zeit: ");
  //Serial.print(day(currentTime));
  //Serial.print(".");
  //Serial.print(month(currentTime));
  //Serial.print(".");
  //Serial.print(year(currentTime));
  //Serial.print(" ");
  // Führende Null für einstellige Stunden
  //Serial.print(currentHour < 10 ? "0" + String(currentHour) : String(currentHour));
  //Serial.print(":");
  // Führende Null für einstellige Minuten
  //Serial.print(currentMinute < 10 ? "0" + String(currentMinute) : String(currentMinute));
  //Serial.print(":");
  // Führende Null für einstellige Sekunden
  //Serial.println(currentSecond < 10 ? "0" + String(currentSecond) : String(currentSecond));


  // Überprüfe, ob Sommerzeit oder Winterzeit aktiv ist
  bool isDST = isDaylightSavingTime(currentTime);

  // Struktur zur Speicherung von Schwellenwerten für Positionen
  struct PositionThreshold {
    int thresholdHour;
    int thresholdMinute;
    long position;
  };

  // Definition von Schwellenwerten für Sommerpositionen
  PositionThreshold PositionenSommer[] = {
    { 6, 0, Startposition },
    { 11, 0, Vormittag1 },
    { 12, 0, Vormittag2 },
    { 12, 30, Mittag1 },
    { 13, 0, Mittag2 },
    { 13, 30, Nachmittag1 },
    { 14, 0, Nachmittag2 },
    { 15, 0, Nachmittag3 },
    { 16, 0, Endposition },
    { 21, 0, Windschutz }
  };

  // Definition von Schwellenwerten für Winterpositionen
  PositionThreshold PositionenWinter[] = {
    { 8, 0, Vormittag1 },
    { 10, 30, Vormittag2 },
    { 12, 0, Mittag1 },
    { 12, 30, Mittag2 },
    { 13, 0, Nachmittag1 },
    { 14, 0, Nachmittag2 },
    { 15, 0, Nachmittag3 },
    { 17, 30, Windschutz }
  };

  // Definition von Schwellenwerten für Zwischenpositionen
  PositionThreshold PositionenZwischen[] = {
    { 7, 0, Startposition },
    { 9, 0, Vormittag1 },
    { 11, 0, Vormittag2 },
    { 12, 0, Mittag1 },
    { 13, 0, Mittag2 },
    { 14, 0, Nachmittag1 },
    { 15, 0, Nachmittag2 },
    { 16, 0, Nachmittag3 },
    { 17, 0, Endposition },
    { 18, 30, Windschutz }
  };

  int numThresholds;
  PositionThreshold* thresholds;

  // Bestimme die Schwellenwerte basierend auf dem aktuellen Monat
  if (currentMonth == 1 || currentMonth == 2 || currentMonth == 11 || currentMonth == 12) {
    thresholds = PositionenWinter;
    numThresholds = sizeof(PositionenWinter) / sizeof(PositionenWinter[0]);
  } else if (currentMonth == 3 || currentMonth == 4 || currentMonth == 9 || currentMonth == 10) {
    thresholds = PositionenZwischen;
    numThresholds = sizeof(PositionenZwischen) / sizeof(PositionenZwischen[0]);
  } else if (currentMonth >= 5 && currentMonth <= 8) {
    thresholds = PositionenSommer;
    numThresholds = sizeof(PositionenSommer) / sizeof(PositionenSommer[0]);
  } else {
    // Standardpositionen oder eine andere Logik hier einfügen, wenn erforderlich
    thresholds = PositionenSommer;
    numThresholds = sizeof(PositionenSommer) / sizeof(PositionenSommer[0]);
  }

  long position = Startposition;  // Standardposition

  // Durchlaufe die Schwellenwerte und bestimme die aktuelle Position
  for (int i = 0; i < numThresholds; i++) {
    if (currentHour > thresholds[i].thresholdHour || (currentHour == thresholds[i].thresholdHour && currentMinute >= thresholds[i].thresholdMinute)) {
      position = thresholds[i].position;
    } else {
      break;  // Schleife abbrechen, da der Rest der Schwellenwerte nicht mehr relevant ist
    }
  }

  // Bewege den Steppermotor zur berechneten Position
  moveStepper(position);

  // Drucke den aktuellen Status der Positionen basierend auf dem Monat
  if (currentMonth == 1 || currentMonth == 2 || currentMonth == 11 || currentMonth == 12) {
    //Serial.println("Winterpositionen");
  } else if (currentMonth == 3 || currentMonth == 4 || currentMonth == 9 || currentMonth == 10) {
    //Serial.println("Zwischenpositionen");
  } else if (currentMonth >= 5 && currentMonth <= 8) {
    //Serial.println("Sommerpositionen");
  } else {
    //Serial.println("Standardpositionen");  // Standardpositionen oder eine andere Bezeichnung hier einfügen, wenn erforderlich
  }

  // Drucke die aktuelle Position des Steppermotors
  //Serial.print("Position: ");
  //Serial.println(stepper.currentPosition());
}

/**
 * Überprüft den Windsensor und aktualisiert die Position des Schrittmotors entsprechend.
 * Wenn der Windsensor aktiviert ist, wird der Schrittmotor in die Windschutzposition bewegt.
 * Nach Ablauf der definierten Wartezeit wird der Windsensor deaktiviert.
 * Wenn der Windsensor nicht aktiv ist, wird die Funktion `managePositions()` aufgerufen, um die Position des Schrittmotors basierend auf der aktuellen Zeit zu aktualisieren.
 */
void checkWindsensor() {
  // Überprüfe den Zustand des Windsensors
  if (digitalRead(PIN_BUTTON) == HIGH) {
    Warteposition_aktiv = true; // Setze den Wartepositionsstatus auf aktiv
    WindschutzTimer = millis(); // Starte den Timer für die Wartezeit
    stepper.moveTo(Windschutz); // Bewege den Schrittmotor zur Windschutzposition
    stepper.runToPosition(); // Führe die Bewegung zum Windschutz durch
  }

  // Überprüfe, ob die Wartezeit für den Windschutz abgelaufen ist
  if (millis() - WindschutzTimer >= WindWartezeit_ms) {
    Warteposition_aktiv = false; // Setze den Wartepositionsstatus auf inaktiv
  }

  // Wenn der Windsensor nicht aktiv ist, rufe die Funktion managePositions auf
  if (!Warteposition_aktiv) {
    managePositions();
  }
}

/**
  * ###########################################
  * Bereich für MQTT-Verbindung
  * ###########################################
  */

/**
 * Versucht, eine Verbindung zum MQTT-Broker herzustellen.
 * Die Funktion wird solange ausgeführt, bis eine Verbindung erfolgreich hergestellt wurde.
 * Wenn eine Verbindung hergestellt ist, wird eine entsprechende Meldung über die serielle Schnittstelle ausgegeben.
 * Wenn die Verbindung fehlschlägt, wird der Fehlercode auf der seriellen Schnittstelle ausgegeben, und es wird alle 5 Sekunden erneut versucht, eine Verbindung herzustellen.
 */
void reconnect() {
  // Loop, bis eine Verbindung hergestellt wird
  while (!client.connected()) {
    // Verbindung mit MQTT-Broker herstellen, einschließlich Benutzername und Passwort
    if (client.connect("ArduinoClient", mqtt_user, mqtt_password)) {
      Serial.println("verbunden"); // Verbindung erfolgreich
    } else {
      Serial.print("Fehlgeschlagen, rc="); // Verbindung fehlgeschlagen, rc=...
      Serial.print(client.state());
      Serial.println(" Versuche es in 5 Sekunden erneut");
      // 5 Sekunden warten und erneut versuchen
      delay(5000);
    }
  }
}

/**
 * Sendet MQTT-Daten an den Broker.
 * Die Funktion überprüft zunächst, ob eine Verbindung zum MQTT-Broker besteht. Wenn keine Verbindung besteht, wird eine neue Verbindung hergestellt.
 * Die Funktion erfasst dann verschiedene Daten wie die Position des Motors, den Status des Windsensors, die Zeitzone und das Positionsset basierend auf dem aktuellen Monat.
 * Diese Daten werden in MQTT-Nachrichten verpackt und an entsprechende Topics veröffentlicht.
 * Die Funktion sorgt dafür, dass die Daten nur alle 5 Sekunden gesendet werden, um die Netzwerkbelastung zu reduzieren.
 */
void sendMQTTData() {
  // Überprüfe, ob eine Verbindung zum MQTT-Broker besteht
  if (!client.connected()) {
    reconnect(); // Verbindung wiederherstellen, falls nicht verbunden
  }

  // Erfasse die aktuellen Zeit
  unsigned long currentTime = millis();

  // Sende die MQTT-Nachrichten nur, wenn seit dem letzten Senden mehr als 5 Sekunden vergangen sind
  if (currentTime - lastSendTime >= 5000) {
    // Erfasse die Daten, die du übertragen möchtest
    int motorPosition = stepper.currentPosition(); // Aktuelle Position des Motors
    String windsensorStatus = Warteposition_aktiv ? "Aktiviert" : "Deaktiviert"; // Status des Windsensors
    String zeitzone = isDaylightSavingTime(currentTime) ? "Sommerzeit" : "Winterzeit"; // Aktuelle Zeitzone
    String positionsset; // Positionsset entsprechend dem aktuellen Monat
    int currentMonth = month(currentTime);
    if (currentMonth == 1 || currentMonth == 2 || currentMonth == 11 || currentMonth == 12) {
      positionsset = "Winter";
    } else if (currentMonth == 3 || currentMonth == 4 || currentMonth == 9 || currentMonth == 10) {
      positionsset = "Zwischen";
    } else if (currentMonth >= 5 && currentMonth <= 8) {
      positionsset = "Sommer";
    } else {
      positionsset = "Standard";
    }

    // Erfasse die aktuelle Uhrzeit
    String currentTimeStr = String(hour()) + ":" + minute() + ":" + second();

    // Debugging-Ausgabe
    Serial.println("Veröffentliche MQTT-Nachrichten:");

    // Erstelle und veröffentliche die MQTT-Nachrichten mit den erfassten Daten
    Serial.print("Motorposition: ");
    Serial.println(motorPosition);
    client.publish("solar/bkw-steuerung/motorposition", String(motorPosition).c_str());

    Serial.print("Windsensor-Status: ");
    Serial.println(windsensorStatus);
    client.publish("solar/bkw-steuerung/windsensor", windsensorStatus.c_str());

    Serial.print("Zeitzone: ");
    Serial.println(zeitzone);
    client.publish("solar/bkw-steuerung/zeitzone", zeitzone.c_str());

    Serial.print("Positionsset: ");
    Serial.println(positionsset);
    client.publish("solar/bkw-steuerung/positionsset", positionsset.c_str());

    Serial.print("Aktuelle Uhrzeit: ");
    Serial.println(currentTimeStr);
    client.publish("solar/bkw-steuerung/uhrzeit", currentTimeStr.c_str());

    // Setze die Zeit des letzten Sendens auf die aktuelle Zeit
    lastSendTime = currentTime;
  }
}

/**
 * Sendet eine Entdeckungsnachricht an den MQTT-Broker, um ein neues Gerät oder eine neue Entität zu entdecken.
 * Die Funktion akzeptiert den Namen des Geräts, das Topic für den Zustand und optional den state_class-Parameter, um den Typ des Zustands anzugeben.
 * Die Entdeckungsnachricht wird im JSON-Format erstellt und an das entsprechende Topic für die Entdeckung veröffentlicht.
 * 
 * @param topic Das Topic für die Entdeckungsnachricht.
 * @param name Der Name des Geräts oder der Entität.
 * @param stateTopic Das Topic für den Zustand des Geräts oder der Entität.
 * @param stateClass Der optionale Parameter für den Typ des Zustands (standardmäßig leer).
 */
void sendDiscoveryMessage(const char* topic, const char* name, const char* stateTopic, const char* stateClass = "") {
  // Erstelle die Entdeckungsnachricht im JSON-Format
  String discoveryMessage = "{";
  discoveryMessage += "\"name\":\"" + String(name) + "\",";
  discoveryMessage += "\"state_topic\":\"" + String(stateTopic) + "\"";

  // Füge den state_class-Parameter hinzu, wenn er übergeben wurde
  if (strlen(stateClass) > 0) {
    discoveryMessage += ",\"state_class\":\"" + String(stateClass) + "\"";
  }

  discoveryMessage += "}";

  // Sende die Entdeckungsnachricht
  client.publish(("homeassistant/sensor/bkw-steuerung/" + String(name) + "/config").c_str(), discoveryMessage.c_str(), true);
}

/**
 * Sendet automatisch Entdeckungsnachrichten für verschiedene Sensoren der Solarsteuerung.
 * Die Funktion sendet Entdeckungsnachrichten für die Motorposition, den Windsensor, die Zeitzone, das Positionsset und die Uhrzeit.
 * Die Nachrichten werden an die entsprechenden Topics für die Entdeckung veröffentlicht.
 */
void sendMQTTAutoDiscovery() {
  sendDiscoveryMessage("solar/bkw-steuerung/motorposition", "Motorposition", "solar/bkw-steuerung/motorposition", "measurement");
  sendDiscoveryMessage("solar/bkw-steuerung/windsensor", "Windsensor", "solar/bkw-steuerung/windsensor");
  sendDiscoveryMessage("solar/bkw-steuerung/zeitzone", "Zeitzone", "solar/bkw-steuerung/zeitzone");
  sendDiscoveryMessage("solar/bkw-steuerung/positionsset", "Positionsset", "solar/bkw-steuerung/positionsset");
  sendDiscoveryMessage("solar/bkw-steuerung/uhrzeit", "Uhrzeit", "solar/bkw-steuerung/uhrzeit");
}

/**
 * Callback-Funktion, die aufgerufen wird, wenn eine MQTT-Nachricht empfangen wird.
 * Die Funktion gibt den empfangenen Topic und den Payload aus und führt entsprechende Aktionen basierend auf dem Inhalt der Nachricht aus.
 * 
 * @param topic Das Topic der empfangenen MQTT-Nachricht.
 * @param payload Der Payload der empfangenen MQTT-Nachricht.
 * @param length Die Länge des Payloads.
 */
void callback(char* topic, byte* payload, unsigned int length) {
  // Ausgabe des empfangenen Topics
  Serial.print("Nachricht empfangen [");
  Serial.print(topic);
  Serial.print("] ");

  // Ausgabe des Payloads
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  // Debugging-Ausgabe, um sicherzustellen, dass die callback-Funktion aufgerufen wird
  Serial.println("Callback-Funktion wurde aufgerufen!");

  // Unterscheidung der Nachrichten und Ausführung entsprechender Aktionen
  if (strcmp(topic, "solar/bkw-steuerung/app-steuerung") == 0) {
    String message = "";  // Initialisiere den String für die Nachricht

    // Zusammenstellen der Nachricht aus dem Payload
    for (int i = 0; i < length; i++) {
      message += (char)payload[i];  // Füge jeden Byte des Payloads zum String hinzu
    }

    message.trim();  // Entferne führende und abschließende Leerzeichen

    // Ausführen entsprechender Aktionen basierend auf dem Inhalt der Nachricht
    if (message.equals("Windsensor aktivieren")) {
      // Aktion: Windsensor aktivieren
      digitalWrite(PIN_BUTTON, HIGH);
      Serial.println("Windsensor wurde aktiviert.");
      stepper.moveTo(Windschutz);
      stepper.runToPosition();
      Serial.println("Motor fährt zur Windschutzposition.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    } else if (message.equals("Windsensor deaktivieren")) {
      // Aktion: Windsensor deaktivieren
      digitalWrite(PIN_BUTTON, LOW);
      Serial.println("Windsensor wurde deaktiviert.");
      Warteposition_aktiv = false;
    } else if (message.equals("position_setzen")) {
      // Aktion: Setzen der Windschutzposition
      stepper.moveTo(Windschutz);
      stepper.runToPosition();
      Serial.println("Motor fährt zur definierten Windschutzposition.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    } else if (message.equals("Startposition")) {
      // Aktion: Motor zur Startposition bewegen
      stepper.moveTo(Startposition);
      stepper.runToPosition();
      Serial.println("Motor fährt zur Startposition.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    } else if (message.equals("Endposition")) {
      // Aktion: Motor zur Endposition bewegen
      stepper.moveTo(Endposition);
      stepper.runToPosition();
      Serial.println("Motor fährt zur Endposition.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    } else if (message.equals("+1000")) {
      // Aktion: Motor um +1000 Positionen bewegen
      int newPos = stepper.currentPosition() + 1000;
      stepper.moveTo(newPos);
      stepper.runToPosition();
      Serial.println("Motor fährt um +1000 Positionen.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    } else if (message.equals("-1000")) {
      // Aktion: Motor um -1000 Positionen bewegen
      int newPos = stepper.currentPosition() - 1000;
      stepper.moveTo(newPos);
      stepper.runToPosition();
      Serial.println("Motor fährt um -1000 Positionen.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    } else if (message.equals("-17000")) {
      // Aktion: Motor um -17000 Positionen bewegen
      int newPos = stepper.currentPosition() - 17000;
      stepper.moveTo(newPos);
      stepper.runToPosition();
      Serial.println("Motor fährt um -17000 Positionen.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    } else if (message.equals("+24000")) {
      // Aktion: Motor um +24000 Positionen bewegen
      int newPos = stepper.currentPosition() + 24000;
      stepper.moveTo(newPos);
      stepper.runToPosition();
      Serial.println("Motor fährt um +24000 Positionen.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    }
  }
}

/**
 * Funktion zum Aktualisieren des MQTT-Clients und zum Senden/Empfangen von Nachrichten.
 * Wenn keine Verbindung zum Broker besteht, wird eine erneute Verbindung hergestellt.
 */
void mqttLoop() {
  // Überprüfen, ob der Client verbunden ist
  if (!client.connected()) {
    // Wenn nicht verbunden, erneut verbinden
    reconnect();
  }
  // Aktualisiere den MQTT-Client
  client.loop();
}

Du schreibst ja schon einiges auf den seriellen Monitor. Kannst du nicht daran erkennen, wo er hängen bleibt und den Fehler damit eingrenzen? Wenn nicht, solltest du noch weitere serielle Ausgaben einbauen, um damit den Fehler aufzuspüren.

1 Like

Wenn Du 0 zurück gibst, bekommt der Syncprovider das hier:

mit und stellt Deine Uhr auf den 1.1.1970 0:0:0 Uhr.

Ist das gewollt?
Was wird damit evtl. ausgelöst?

1 Like
time_t getNtpTime() {
  // Warte, bis eine WLAN-Verbindung hergestellt ist
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
  }

das kann theoretisch ewig in diesem while verbleiben wenn keine Verbindung aufgebaut werden könnte. Sowas solltest vermeiden.

1 Like

Danke euch schonmal, dass ihr euch den Code im Detail angeschaut habt. Ist ja doch etwas länger.

@HotSystems: Bisher leider nicht. Meinen Beobachten konnten dort nichts sehen, was irgendwie darauf hingedeutet hat. Teilweise hängt der Sketch einfach mitten im Ablauf.

@my_xy_projekt: Der gesamte Sketch funktioniert nur, wenn er auch eine Uhrzeit bezieht. Sonst passiert erstmal nichts zu Beginn. Wo du das jetzt aber noch einmal so zeigst, weiß ich gar nicht mehr, warum ich das damals eingebaut habe. Ich glaube das könnte dann einfach raus. Damit wird auch nichts ausgelöst. Der Sketch würde dann quasi seine Sachen abarbeiten, aber eben mit falschen Uhrzeiten.
Frage, macht eine Alternative da Sinn, wenn es das mal eintreten sollte?

@noiasca: Wie gerade erwähnt, funktioniert der Sketch ohne Zeit nicht. Daher muss er eigentlich so lange prüfen, ob er eine Verbindung hat. So war zumindest mein Gedankengang.

Aber mal angenommen, der Sketch läuft, er verliert die WLAN-Verbindung und findet sie nicht wieder. Dann würde er die Zeit nicht aktualisieren können, würde aber die aktuell hinterlegte Zeit einfach beibehalten?

In so einem Fall dann lieber wie in der void connectWiFi einen Zähler einbauen? und das return 0; aus raus?

Dann ist er erstmal in der Schleife gefangen.

Was noch dazu kommt:
Während Du vor dem Senden des NTP-Anfordrungspaketes auf WiFiStatus prüfst, bist Du auch hier:

wieder gefangen, bis die Zeit abläuft.
Trennt sich in den 1.5 Sekunden die Verbindung und Du hast kein Paket empfangen, hast Du das selbe Ergebnis, als würdest Du oben gar nicht auf WLCONNECT abfragen und blind versuchen eine Anforderung zu erstellen.

Kurz: Die Prüfung auf WLCONNECT macht so keinen Sinn.
Nimm die vollständig raus. (Damit hätte Deine Routune für die Abbruchbehebung auch eine Chance :slight_smile: )

1 Like

die TimeLib.h gibt dir eh auch eine laufende Zeit zurück. NTP ist dann nur dazu da, die Zeit übers Internet "genau" zu stellen. Aus meiner Sicht sollte es daher kein Problem darstellen, wenn die NTP Abfrage mal nicht funktioniert - dann hast halt nur die "intern" ermittelte Zeit zur Verfügung.

*) ich würde mich an deiner Stelle von diesen Arduino Wifi trennen. Besorg dir einen bastlerfreundlichen ESP32 (oder wenns reicht einen ESP8266 in Form eines NodeMCU/Wemos D1), programmiere nur den ESP und lass den Arduino Weg.

ich wette das wird langfristig stabiler als mit diesem Arduino Wifi

Wie begründest Du das?

Alles klar!

Ich habe dann jetzt erstmal folgende Code-Zeilen aus der time_t getNtpTime() rausgenommen.

  // Warte, bis eine WLAN-Verbindung hergestellt ist
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
  }

und

return 0;

Jetzt bleibt dann erstmal wieder nu die Möglichkeit abzuwarten und zu testen oder?

@noiasca Ok, dann würde das ja passen. Danke.
Ein Wechsel kommt aktuell nicht in Frage. Das System tut ja grundsätzlich seinen Dienst. Frage ist halt, ob am Code noch was falsch ist.

Wenn Du das return 0 raus nimmst, müsste Dich der Compiler warnen, dass Du da keinen Rückgabewert definiert hast.
Ich würd es erstmal drin lassen und sehen, ob Du jetzt durchläufst.
Mehr als einen falschen Timestamp kannst Du nicht bekommen, wenn ich mich nicht ganz vertan habe :wink:

gut wäre wenn du mal einen PC mitprotokollieren lassen könntest WO dein Programm wirklich hängenbleibt.

@my_xy_projekt Ich habe das return erstmal rausgenommen mit //

Ne Meldung vom Compiler habe ich nicht bekommen. Lief so durch. Im Debugging sieht man aber schön, dass durch den Wegfall der WLAN-Abfrage direkt erstmal eine Fehlermeldung kam und die falsche Zeit drin stand.

18:37:08.263 -> WiFi verbunden
18:37:08.294 -> 192.168.2.166
18:37:08.359 -> Zeitserver IP-Adresse erhalten
18:37:08.720 -> verbunden
18:37:08.751 -> Zeitserver IP-Adresse erhalten
18:37:10.260 -> Keine Antwort vom Zeitserver
18:37:24.491 -> Veröffentliche MQTT-Nachrichten:
18:37:24.538 -> Motorposition: 17000
18:37:24.555 -> Windsensor-Status: Deaktiviert
18:37:24.587 -> Zeitzone: Winterzeit
18:37:24.619 -> Positionsset: Winter
18:37:24.652 -> Aktuelle Uhrzeit: 7:0:40

Wenn ich das return 0 wieder drin habe, geht es wieder. Da müsste man später mal schauen.

@noiasca Ich schau mal, wie ich das am sinnigsten umsetze....

Oh mann !

Was ist so schwierig daran zur Analyse den Code mal mit Serial.print richtig zu zu pflastern ??
hast du Brandblasen an den Fingerspitzen?
zu große Tastaturabnutzung?
wird das Baby aufgeweckt wenn du zu viel auf der Tastatur klimperst?


#include <WiFiNINA.h>       // Bibliothek für die WLAN-Kommunikation mit dem Arduino NINA-WiFi-Modul
#include <AccelStepper.h>   // Bibliothek für die Steuerung von Schrittmotoren mit Beschleunigung
#include <TimeLib.h>        // Bibliothek für die Manipulation und Abfrage von Zeitinformationen
#include <WiFiUdp.h>        // Bibliothek für die Kommunikation über UDP/IP-Protokoll im WLAN
#include <PubSubClient.h>   // MQTT-Bibliothek für die Verbindung und Kommunikation mit einem MQTT-Broker
#include <ArduinoJson.h>    // Bibliothek für die Bearbeitung von JSON-Daten
#include "secrets.h"        // Header-Datei für geheime Zugangsdaten (z. B. WLAN-SSID, Passwort, MQTT-Benutzername und -Passwort)

// Pin-Belegungen für den Schrittmotor
#define dirPin 2                  // Richtungs-Pin des Schrittmotors
#define stepPin 4                 // Schritt-Pin des Schrittmotors
#define motorInterfaceType 1      // Art der Motorsteuerung (1 für Treiber wie A4988 oder DRV8825)
#define PIN_BUTTON 12             // Pin für den Windsensor

// Intervall für die Überprüfung der WLAN-Verbindung (10 Minuten)
#define WIFI_CHECK_INTERVAL 600000

// Zugangsdaten für WLAN und MQTT
const char* mqtt_server = "192.168.2.2";    // IP-Adresse oder Domain des MQTT-Brokers
const int mqtt_port = 1883;                 // MQTT-Port (standardmäßig 1883)
const char* mqtt_user = MQTT_USER;          // MQTT-Benutzername
const char* mqtt_password = MQTT_PASSWORD;  // MQTT-Passwort
char ssid[] = WIFI_SSID;                    // WLAN-SSID
char pass[] = WIFI_PASSWORD;                // WLAN-Passwort

// Zeit- und Zeitstempelvariablen
unsigned long lastUpdateTime = 0;  // Variable, um die Zeit des letzten Updates zu speichern
const int timeZoneOffset = 1;       // Zeitverschiebung für die Winterzeit (UTC+1)
int timeZone = 2;                   // Zeitzone für die Umrechnung von lokaler Zeit in UTC
unsigned long currentTimestamp = 0; // Variable für den aktuellen Zeitstempel
time_t currentTime;                 // Variable für die aktuelle Zeit

// Variablen für den Windschutz
unsigned long WindschutzTimer;        // Variable zur Speicherung der Zeit, seit der Windschutz aktiviert wurde
unsigned long WindWartezeit_ms = 900000;  // Zeitdauer in Millisekunden, nach dem der Windschutz automatisch deaktiviert wird (900000 ms = 15 Minuten)
boolean Warteposition_aktiv = false;  // Flag, das angibt, ob sich der Mechanismus in der Warteposition befindet

// Variable zur Speicherung des Zeitpunkts des letzten Sendens von Daten
unsigned long lastSendTime = 0;

//Position des Schrittmotors
const long Startposition = 17000;
const long Vormittag1 = 9000;
const long Vormittag2 = 2000;
const long Mittag1 = 0;
const long Mittag2 = -6000;
const long Nachmittag1 = -12000;
const long Nachmittag2 = -15000;
const long Nachmittag3 = -18000;
const long Endposition = -24000;
const long Windschutz = 0;

AccelStepper stepper(motorInterfaceType, stepPin, dirPin);  // Initialisierung des Schrittmotors

WiFiUDP ntpUDP;              // UDP-Objekt für die NTP-Zeitabfrage
IPAddress timeServerIP;      // IP-Adresse des Zeit-Servers für die NTP-Synchronisation
WiFiUDP udp;                 // UDP-Objekt für die Kommunikation
WiFiClient wifiClient;       // WiFi-Client-Objekt für die Verbindung zum MQTT-Broker
PubSubClient client(wifiClient);  // MQTT-Client-Objekt für die MQTT-Kommunikation
WiFiServer server(80);       // WiFi-Server-Objekt für die Verarbeitung von HTTP-Anfragen

unsigned long wifiCheckTimer = 0;  // Timer für die Überprüfung der WLAN-Verbindung

// Funktion zur Überprüfung, ob Sommerzeit aktiv ist
bool isDaylightSavingTime(time_t currentTime);

// Funktion zur Ermittlung des letzten Sonntags im Monat
int getLastSundayDay(int year, int month);

// Überprüfung, ob Sommerzeit aktiv ist
bool isDaylightSavingTime(time_t currentTime) {
  int currentYear = year(currentTime);
  int currentMonth = month(currentTime);
  int currentDay = day(currentTime);

  // Ermittlung des letzten Sonntags im März
  int lastSundayMarch = getLastSundayDay(currentYear, 3);

  // Ermittlung des letzten Sonntags im Oktober
  int lastSundayOctober = getLastSundayDay(currentYear, 10);

  // Sommerzeit beginnt am letzten Sonntag im März und endet am letzten Sonntag im Oktober
  bool isDST = ((currentMonth > 3 && currentMonth < 10) || (currentMonth == 3 && currentDay > lastSundayMarch) || (currentMonth == 10 && currentDay <= lastSundayOctober));

  return isDST;
}

// Ermittlung des letzten Sonntags im Monat
int getLastSundayDay(int year, int month) {
  const int lastDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };  // Anzahl Tage in jedem Monat

  int lastSunday = lastDays[month - 1];  // Annahme: Monat hat maximal 31 Tage
  tmElements_t timeInfo;

  // Suche den letzten Sonntag des Monats
  while (lastSunday > 0) {
    timeInfo.Year = year - 1970;  // Jahresversatz von 1970
    timeInfo.Month = month;
    timeInfo.Day = lastSunday;

    time_t time = makeTime(timeInfo);
    int dayOfWeek = weekday(time);

    if (dayOfWeek == 1) {  // Sonntag hat den Wert 1, unabhängig davon, wann die Woche beginnt
      return lastSunday;
    }

    lastSunday--;
  }

  return -1;  // Fehler, falls der letzte Sonntag nicht gefunden wird
}

/**
   Initialisierung der Mikrocontroller-Hardware und Einrichtung der Kommunikationsprotokolle.
*/
void setup() {
  // Initialisierung der seriellen Kommunikation
  Serial.begin(9600);
  while (!Serial)
    ; // Warte, bis die serielle Kommunikation gestartet ist

  // Konfiguration des Schrittmotors
  stepper.setMaxSpeed(1400);
  stepper.setAcceleration(800);
  Serial.println("setup stepper.set done");
  // Konfiguration der Pins
  pinMode(PIN_BUTTON, INPUT_PULLUP); // Pin für den Windsensor als Eingang mit Pull-Up-Widerstand konfigurieren
  pinMode(LED_BUILTIN, OUTPUT);      // Pin für die eingebaute LED als Ausgang konfigurieren

  // WLAN-Verbindung herstellen
  Serial.println("direkt vor connectWiFi()");
  connectWiFi();

  // Zeit synchronisieren
  Serial.println("direkt vor setSyncProvider(getNtpTime)");
  setSyncProvider(getNtpTime);

  // HTTP-Server starten
  Serial.println("direkt vor server.begin()");
  server.begin();

  // Verbindung zum MQTT-Broker herstellen
  client.setServer(mqtt_server, mqtt_port);
  Serial.println("client.setServer(mqtt_server, mqtt_port) done ");
  client.setCallback(callback);
  Serial.println("client.setCallback(callback) done ");

  if (!client.connected()) {
    Serial.println("if (!client.connected()) direkt vor reconnect()");
    reconnect();
    Serial.println("if (!client.connected()) direkt nach reconnect()");
  }

  // Abonnement für das gewünschte Topic einrichten
  Serial.println("direkt vor client.subscribe(solar");
  client.subscribe("solar/bkw-steuerung/app-steuerung");

  // Autodiscovery-Nachrichten senden
  sendMQTTAutoDiscovery();
  Serial.println("sendMQTTAutoDiscovery() done");
}

/**
   Die Haupt-Loop-Funktion, die wiederholt ausgeführt wird.
   Überprüft die WLAN-Verbindung, den Windsensor und aktualisiert die NTP-Zeit.
   Sendet MQTT-Daten an Home Assistant und verarbeitet MQTT-Nachrichten.
*/
void loop() {
  // Überprüfe die WLAN-Verbindung
  Serial.println("direkt vor checkWiFiConnection()");
  checkWiFiConnection();

  // Überprüfe den Zustand des Windsensors
  Serial.println("direkt vor checkWindsensor()");
  checkWindsensor();

  // Sende MQTT-Daten an Home Assistant
  Serial.println("direkt vor sendMQTTData()");
  sendMQTTData();

  // Aktualisiere die MQTT-Verbindung und verarbeite eingehende Nachrichten
  Serial.println("direkt vor mqttLoop()");
  mqttLoop();

  // Aktualisiere die NTP-Zeit
  Serial.println("direkt vor updateNtpTime()");
  updateNtpTime();
}

/**
    ###########################################
    Bereich für WLAN-Verbindung und Zeitupdates
    ###########################################
*/

/**
   Stellt eine Verbindung zum WLAN her.
   Führt mehrere Verbindungsversuche durch und gibt eine Fehlermeldung aus, wenn die Verbindung fehlschlägt.
*/
void connectWiFi() {
  Serial.println("entering connectWiFi()");

  int connectAttempts = 0; // Anzahl der Verbindungsversuche

  // Führe bis zu 10 Verbindungsversuche durch
  while (connectAttempts < 10) {
    WiFi.begin(ssid, pass); // WLAN-Verbindung mit den angegebenen Zugangsdaten herstellen
    unsigned long startTime = millis(); // Startzeit für den Verbindungsversuch

    // Warte bis zur erfolgreichen Verbindung oder bis 30 Sekunden vergangen sind
    while (WiFi.status() != WL_CONNECTED && millis() - startTime < 30000) {
      delay(1000); // Warte 1 Sekunde vor jedem erneuten Verbindungsversuch
    }

    // Wenn die Verbindung erfolgreich hergestellt wurde
    if (WiFi.status() == WL_CONNECTED) {
      Serial.println("WiFi verbunden");
      IPAddress ip = WiFi.localIP();
      //Serial.print("IP-Adresse: ");
      Serial.println(ip);
      return;  // Verbindung erfolgreich, beende die Funktion
    }

    // Wenn die Verbindung fehlschlägt, trenne die Verbindung und warte vor dem nächsten Versuch
    Serial.println("direkt vor WiFi.disconnect()");    
    WiFi.disconnect();
    delay(30000); // Warte 30 Sekunden vor dem nächsten Verbindungsversuch
    Serial.println("delay(30000); done");    

    connectAttempts++; // Erhöhe die Anzahl der Verbindungsversuche
  }

  // Wenn alle Verbindungsversuche fehlschlagen, gebe eine Fehlermeldung aus
  Serial.println("WiFi-Verbindung fehlgeschlagen");
}

/**
   Überprüft die WLAN-Verbindung und stellt sie gegebenenfalls neu her.
*/
void checkWiFiConnection() {
  if (millis() - wifiCheckTimer >= WIFI_CHECK_INTERVAL) { // Überprüfe das WLAN-Verbindungsintervall
    wifiCheckTimer = millis();  // Timer zurücksetzen

    // Überprüfe die WLAN-Verbindung
    if (WiFi.status() != WL_CONNECTED) { // Wenn keine Verbindung besteht
      Serial.println("Verbindung verloren. Trenne WLAN-Verbindung und stelle neu her.");

      // Trenne WLAN-Verbindung
      WiFi.disconnect();

      // Warte, um sicherzustellen, dass die Verbindung getrennt wurde
      delay(1000);

      // Stelle die WLAN-Verbindung neu her
      connectWiFi();
    } else { // Wenn eine Verbindung besteht
      Serial.println("WLAN-Verbindung aktiv.");
    }
  }
}

/**
   Holt die aktuelle Zeit von einem NTP-Zeitserver.

   @return Die aktuelle Zeit als Unix-Zeitstempel (time_t)
*/
time_t getNtpTime() {
  // Warte, bis eine WLAN-Verbindung hergestellt ist
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
  }

  // Ermittle die IP-Adresse des NTP-Zeitservers ("pool.ntp.org")
  WiFi.hostByName("pool.ntp.org", timeServerIP);
  Serial.println("Zeitserver IP-Adresse erhalten");

  const int NTP_PACKET_SIZE = 48;
  byte packetBuffer[NTP_PACKET_SIZE];

  // Initialisiere das NTP-Paket
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  packetBuffer[0] = 0b11100011;
  packetBuffer[1] = 0;
  packetBuffer[2] = 6;
  packetBuffer[3] = 0xEC;
  packetBuffer[12] = 49;
  packetBuffer[13] = 0x4E;
  packetBuffer[14] = 49;
  packetBuffer[15] = 52;

  WiFiUDP udp;
  udp.begin(123); // Starte die UDP-Kommunikation auf Port 123
  udp.beginPacket(timeServerIP, 123); // Beginne ein UDP-Paket zum NTP-Zeitserver
  udp.write(packetBuffer, NTP_PACKET_SIZE); // Sende das NTP-Paket
  udp.endPacket(); // Beende das UDP-Paket

  // Warte auf eine Antwort vom Zeitserver
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      udp.read(packetBuffer, NTP_PACKET_SIZE);
      // Extrahiere die Sekunden seit 1900 aus dem NTP-Paket
      unsigned long secsSince1900 = (unsigned long)packetBuffer[40] << 24 | (unsigned long)packetBuffer[41] << 16 | (unsigned long)packetBuffer[42] << 8 | (unsigned long)packetBuffer[43];

      // Berechne den Unix-Zeitstempel (Anzahl der Sekunden seit 1970) aus den Sekunden seit 1900
      time_t ntpTime = secsSince1900 - 2208988800UL;

      // Führe die Sommerzeit-Korrektur durch, falls erforderlich
      ntpTime += (isDaylightSavingTime(ntpTime) ? 3600 : 0);
      // Füge die Zeitzonenverschiebung hinzu
      ntpTime += (timeZoneOffset * 3600);

      // Gib die aktuelle Zeit zurück
      return ntpTime;
    }
    delay(10);
  }

  // Keine Antwort vom Zeitserver erhalten
  Serial.println("Keine Antwort vom Zeitserver");
  return 0; // Gib 0 zurück, um anzuzeigen, dass die Zeit nicht erfolgreich abgerufen wurde
}

/**
   Aktualisiert die Systemzeit durch Synchronisierung mit einem NTP-Zeitserver.
   Die Zeit wird alle 65 Minuten synchronisiert, sofern keine vorherige Synchronisierung erfolgt ist.
*/
void updateNtpTime() {
  unsigned long currentMillis = millis(); // Erfasse die aktuelle Zeit in Millisekunden
  static unsigned long previousSyncMillis = 0; // Speichere den Zeitpunkt des letzten Syncs
  const unsigned long syncInterval = 3900000;  // Synchronisiere die Zeit alle 65 Minuten

  // Überprüfe, ob es Zeit ist, die Zeit zu synchronisieren
  if (currentMillis - previousSyncMillis >= syncInterval || previousSyncMillis == 0) {
    // Wenn die Zeitdifferenz seit dem letzten Sync größer oder gleich dem Synchronisierungsintervall ist oder wenn noch keine vorherige Synchronisierung erfolgt ist

    // Aktualisiere die Zeit durch Aufrufen der getNtpTime() Funktion als Sync-Provider
    setSyncProvider(getNtpTime);
    previousSyncMillis = currentMillis;  // Aktualisiere den Zeitpunkt des letzten Syncs auf die aktuelle Zeit
  }
}

/**
    ###########################################
    Bereich für Motorsteuerung
    ###########################################
*/

/**
   Bewegt den Schrittmotor auf die angegebene Position, falls die aktuelle Position nicht bereits erreicht ist.
   @param position Die Zielposition, zu der der Schrittmotor bewegt werden soll.
*/
void moveStepper(int position) {
  if (stepper.currentPosition() != position) {
    stepper.moveTo(position);  // Setzt die Zielposition des Schrittmotors
    stepper.runToPosition();    // Führt den Schrittmotor zur Zielposition aus
  }
}

/**
   Aktualisiert die Position des Schrittmotors basierend auf der aktuellen Zeit und den definierten Schwellenwerten für Sommer-, Winter- und Zwischenpositionen.
   Die Funktion bestimmt die Zielposition des Schrittmotors anhand der aktuellen Uhrzeit und des Monats. Je nach Monat werden unterschiedliche Schwellenwerte verwendet, um zwischen Sommer-, Winter- und Zwischenpositionen zu unterscheiden.
   Die Position wird entsprechend den definierten Schwellenwerten festgelegt, und der Schrittmotor wird auf die berechnete Position bewegt.
*/
// Diese Funktion aktualisiert die Position des Steppermotors basierend auf der aktuellen Zeit
void managePositions() {
  currentTime = now();  // Aktualisiere den Wert der globalen Variable currentTime
  int currentMonth = month(currentTime);
  int currentDay = day(currentTime);
  int currentHour = hour(currentTime);
  int currentMinute = minute(currentTime);
  int currentSecond = second(currentTime);
  int currentTimeInSeconds = currentHour * 3600 + currentMinute * 60;

  //Debugging
  //Serial.print("Zeit: ");
  //Serial.print(day(currentTime));
  //Serial.print(".");
  //Serial.print(month(currentTime));
  //Serial.print(".");
  //Serial.print(year(currentTime));
  //Serial.print(" ");
  // Führende Null für einstellige Stunden
  //Serial.print(currentHour < 10 ? "0" + String(currentHour) : String(currentHour));
  //Serial.print(":");
  // Führende Null für einstellige Minuten
  //Serial.print(currentMinute < 10 ? "0" + String(currentMinute) : String(currentMinute));
  //Serial.print(":");
  // Führende Null für einstellige Sekunden
  //Serial.println(currentSecond < 10 ? "0" + String(currentSecond) : String(currentSecond));


  // Überprüfe, ob Sommerzeit oder Winterzeit aktiv ist
  bool isDST = isDaylightSavingTime(currentTime);

  // Struktur zur Speicherung von Schwellenwerten für Positionen
  struct PositionThreshold {
    int thresholdHour;
    int thresholdMinute;
    long position;
  };

  // Definition von Schwellenwerten für Sommerpositionen
  PositionThreshold PositionenSommer[] = {
    { 6, 0, Startposition },
    { 11, 0, Vormittag1 },
    { 12, 0, Vormittag2 },
    { 12, 30, Mittag1 },
    { 13, 0, Mittag2 },
    { 13, 30, Nachmittag1 },
    { 14, 0, Nachmittag2 },
    { 15, 0, Nachmittag3 },
    { 16, 0, Endposition },
    { 21, 0, Windschutz }
  };

  // Definition von Schwellenwerten für Winterpositionen
  PositionThreshold PositionenWinter[] = {
    { 8, 0, Vormittag1 },
    { 10, 30, Vormittag2 },
    { 12, 0, Mittag1 },
    { 12, 30, Mittag2 },
    { 13, 0, Nachmittag1 },
    { 14, 0, Nachmittag2 },
    { 15, 0, Nachmittag3 },
    { 17, 30, Windschutz }
  };

  // Definition von Schwellenwerten für Zwischenpositionen
  PositionThreshold PositionenZwischen[] = {
    { 7, 0, Startposition },
    { 9, 0, Vormittag1 },
    { 11, 0, Vormittag2 },
    { 12, 0, Mittag1 },
    { 13, 0, Mittag2 },
    { 14, 0, Nachmittag1 },
    { 15, 0, Nachmittag2 },
    { 16, 0, Nachmittag3 },
    { 17, 0, Endposition },
    { 18, 30, Windschutz }
  };

  int numThresholds;
  PositionThreshold* thresholds;

  // Bestimme die Schwellenwerte basierend auf dem aktuellen Monat
  if (currentMonth == 1 || currentMonth == 2 || currentMonth == 11 || currentMonth == 12) {
    thresholds = PositionenWinter;
    numThresholds = sizeof(PositionenWinter) / sizeof(PositionenWinter[0]);
  } else if (currentMonth == 3 || currentMonth == 4 || currentMonth == 9 || currentMonth == 10) {
    thresholds = PositionenZwischen;
    numThresholds = sizeof(PositionenZwischen) / sizeof(PositionenZwischen[0]);
  } else if (currentMonth >= 5 && currentMonth <= 8) {
    thresholds = PositionenSommer;
    numThresholds = sizeof(PositionenSommer) / sizeof(PositionenSommer[0]);
  } else {
    // Standardpositionen oder eine andere Logik hier einfügen, wenn erforderlich
    thresholds = PositionenSommer;
    numThresholds = sizeof(PositionenSommer) / sizeof(PositionenSommer[0]);
  }

  long position = Startposition;  // Standardposition

  // Durchlaufe die Schwellenwerte und bestimme die aktuelle Position
  for (int i = 0; i < numThresholds; i++) {
    if (currentHour > thresholds[i].thresholdHour || (currentHour == thresholds[i].thresholdHour && currentMinute >= thresholds[i].thresholdMinute)) {
      position = thresholds[i].position;
    } else {
      break;  // Schleife abbrechen, da der Rest der Schwellenwerte nicht mehr relevant ist
    }
  }

  // Bewege den Steppermotor zur berechneten Position
  moveStepper(position);

  // Drucke den aktuellen Status der Positionen basierend auf dem Monat
  if (currentMonth == 1 || currentMonth == 2 || currentMonth == 11 || currentMonth == 12) {
    //Serial.println("Winterpositionen");
  } else if (currentMonth == 3 || currentMonth == 4 || currentMonth == 9 || currentMonth == 10) {
    //Serial.println("Zwischenpositionen");
  } else if (currentMonth >= 5 && currentMonth <= 8) {
    //Serial.println("Sommerpositionen");
  } else {
    //Serial.println("Standardpositionen");  // Standardpositionen oder eine andere Bezeichnung hier einfügen, wenn erforderlich
  }

  // Drucke die aktuelle Position des Steppermotors
  //Serial.print("Position: ");
  //Serial.println(stepper.currentPosition());
}

/**
   Überprüft den Windsensor und aktualisiert die Position des Schrittmotors entsprechend.
   Wenn der Windsensor aktiviert ist, wird der Schrittmotor in die Windschutzposition bewegt.
   Nach Ablauf der definierten Wartezeit wird der Windsensor deaktiviert.
   Wenn der Windsensor nicht aktiv ist, wird die Funktion `managePositions()` aufgerufen, um die Position des Schrittmotors basierend auf der aktuellen Zeit zu aktualisieren.
*/
void checkWindsensor() {
  // Überprüfe den Zustand des Windsensors
  if (digitalRead(PIN_BUTTON) == HIGH) {
    Warteposition_aktiv = true; // Setze den Wartepositionsstatus auf aktiv
    WindschutzTimer = millis(); // Starte den Timer für die Wartezeit
    stepper.moveTo(Windschutz); // Bewege den Schrittmotor zur Windschutzposition
    stepper.runToPosition(); // Führe die Bewegung zum Windschutz durch
  }

  // Überprüfe, ob die Wartezeit für den Windschutz abgelaufen ist
  if (millis() - WindschutzTimer >= WindWartezeit_ms) {
    Warteposition_aktiv = false; // Setze den Wartepositionsstatus auf inaktiv
  }

  // Wenn der Windsensor nicht aktiv ist, rufe die Funktion managePositions auf
  if (!Warteposition_aktiv) {
    managePositions();
  }
}

/**
    ###########################################
    Bereich für MQTT-Verbindung
    ###########################################
*/

/**
   Versucht, eine Verbindung zum MQTT-Broker herzustellen.
   Die Funktion wird solange ausgeführt, bis eine Verbindung erfolgreich hergestellt wurde.
   Wenn eine Verbindung hergestellt ist, wird eine entsprechende Meldung über die serielle Schnittstelle ausgegeben.
   Wenn die Verbindung fehlschlägt, wird der Fehlercode auf der seriellen Schnittstelle ausgegeben, und es wird alle 5 Sekunden erneut versucht, eine Verbindung herzustellen.
*/
void reconnect() {
  // Loop, bis eine Verbindung hergestellt wird
  while (!client.connected()) {
    // Verbindung mit MQTT-Broker herstellen, einschließlich Benutzername und Passwort
    if (client.connect("ArduinoClient", mqtt_user, mqtt_password)) {
      Serial.println("verbunden"); // Verbindung erfolgreich
    } else {
      Serial.print("Fehlgeschlagen, rc="); // Verbindung fehlgeschlagen, rc=...
      Serial.print(client.state());
      Serial.println(" Versuche es in 5 Sekunden erneut");
      // 5 Sekunden warten und erneut versuchen
      delay(5000);
    }
  }
}

/**
   Sendet MQTT-Daten an den Broker.
   Die Funktion überprüft zunächst, ob eine Verbindung zum MQTT-Broker besteht. Wenn keine Verbindung besteht, wird eine neue Verbindung hergestellt.
   Die Funktion erfasst dann verschiedene Daten wie die Position des Motors, den Status des Windsensors, die Zeitzone und das Positionsset basierend auf dem aktuellen Monat.
   Diese Daten werden in MQTT-Nachrichten verpackt und an entsprechende Topics veröffentlicht.
   Die Funktion sorgt dafür, dass die Daten nur alle 5 Sekunden gesendet werden, um die Netzwerkbelastung zu reduzieren.
*/
void sendMQTTData() {
  // Überprüfe, ob eine Verbindung zum MQTT-Broker besteht
  if (!client.connected()) {
    reconnect(); // Verbindung wiederherstellen, falls nicht verbunden
  }

  // Erfasse die aktuellen Zeit
  unsigned long currentTime = millis();

  // Sende die MQTT-Nachrichten nur, wenn seit dem letzten Senden mehr als 5 Sekunden vergangen sind
  if (currentTime - lastSendTime >= 5000) {
    // Erfasse die Daten, die du übertragen möchtest
    int motorPosition = stepper.currentPosition(); // Aktuelle Position des Motors
    String windsensorStatus = Warteposition_aktiv ? "Aktiviert" : "Deaktiviert"; // Status des Windsensors
    String zeitzone = isDaylightSavingTime(currentTime) ? "Sommerzeit" : "Winterzeit"; // Aktuelle Zeitzone
    String positionsset; // Positionsset entsprechend dem aktuellen Monat
    int currentMonth = month(currentTime);
    if (currentMonth == 1 || currentMonth == 2 || currentMonth == 11 || currentMonth == 12) {
      positionsset = "Winter";
    } else if (currentMonth == 3 || currentMonth == 4 || currentMonth == 9 || currentMonth == 10) {
      positionsset = "Zwischen";
    } else if (currentMonth >= 5 && currentMonth <= 8) {
      positionsset = "Sommer";
    } else {
      positionsset = "Standard";
    }

    // Erfasse die aktuelle Uhrzeit
    String currentTimeStr = String(hour()) + ":" + minute() + ":" + second();

    // Debugging-Ausgabe
    Serial.println("Veröffentliche MQTT-Nachrichten:");

    // Erstelle und veröffentliche die MQTT-Nachrichten mit den erfassten Daten
    Serial.print("Motorposition: ");
    Serial.println(motorPosition);
    client.publish("solar/bkw-steuerung/motorposition", String(motorPosition).c_str());

    Serial.print("Windsensor-Status: ");
    Serial.println(windsensorStatus);
    client.publish("solar/bkw-steuerung/windsensor", windsensorStatus.c_str());

    Serial.print("Zeitzone: ");
    Serial.println(zeitzone);
    client.publish("solar/bkw-steuerung/zeitzone", zeitzone.c_str());

    Serial.print("Positionsset: ");
    Serial.println(positionsset);
    client.publish("solar/bkw-steuerung/positionsset", positionsset.c_str());

    Serial.print("Aktuelle Uhrzeit: ");
    Serial.println(currentTimeStr);
    client.publish("solar/bkw-steuerung/uhrzeit", currentTimeStr.c_str());

    // Setze die Zeit des letzten Sendens auf die aktuelle Zeit
    lastSendTime = currentTime;
  }
}

/**
   Sendet eine Entdeckungsnachricht an den MQTT-Broker, um ein neues Gerät oder eine neue Entität zu entdecken.
   Die Funktion akzeptiert den Namen des Geräts, das Topic für den Zustand und optional den state_class-Parameter, um den Typ des Zustands anzugeben.
   Die Entdeckungsnachricht wird im JSON-Format erstellt und an das entsprechende Topic für die Entdeckung veröffentlicht.

   @param topic Das Topic für die Entdeckungsnachricht.
   @param name Der Name des Geräts oder der Entität.
   @param stateTopic Das Topic für den Zustand des Geräts oder der Entität.
   @param stateClass Der optionale Parameter für den Typ des Zustands (standardmäßig leer).
*/
void sendDiscoveryMessage(const char* topic, const char* name, const char* stateTopic, const char* stateClass = "") {
  // Erstelle die Entdeckungsnachricht im JSON-Format
  String discoveryMessage = "{";
  discoveryMessage += "\"name\":\"" + String(name) + "\",";
  discoveryMessage += "\"state_topic\":\"" + String(stateTopic) + "\"";

  // Füge den state_class-Parameter hinzu, wenn er übergeben wurde
  if (strlen(stateClass) > 0) {
    discoveryMessage += ",\"state_class\":\"" + String(stateClass) + "\"";
  }

  discoveryMessage += "}";

  // Sende die Entdeckungsnachricht
  client.publish(("homeassistant/sensor/bkw-steuerung/" + String(name) + "/config").c_str(), discoveryMessage.c_str(), true);
}

/**
   Sendet automatisch Entdeckungsnachrichten für verschiedene Sensoren der Solarsteuerung.
   Die Funktion sendet Entdeckungsnachrichten für die Motorposition, den Windsensor, die Zeitzone, das Positionsset und die Uhrzeit.
   Die Nachrichten werden an die entsprechenden Topics für die Entdeckung veröffentlicht.
*/
void sendMQTTAutoDiscovery() {
  sendDiscoveryMessage("solar/bkw-steuerung/motorposition", "Motorposition", "solar/bkw-steuerung/motorposition", "measurement");
  sendDiscoveryMessage("solar/bkw-steuerung/windsensor", "Windsensor", "solar/bkw-steuerung/windsensor");
  sendDiscoveryMessage("solar/bkw-steuerung/zeitzone", "Zeitzone", "solar/bkw-steuerung/zeitzone");
  sendDiscoveryMessage("solar/bkw-steuerung/positionsset", "Positionsset", "solar/bkw-steuerung/positionsset");
  sendDiscoveryMessage("solar/bkw-steuerung/uhrzeit", "Uhrzeit", "solar/bkw-steuerung/uhrzeit");
}

/**
   Callback-Funktion, die aufgerufen wird, wenn eine MQTT-Nachricht empfangen wird.
   Die Funktion gibt den empfangenen Topic und den Payload aus und führt entsprechende Aktionen basierend auf dem Inhalt der Nachricht aus.

   @param topic Das Topic der empfangenen MQTT-Nachricht.
   @param payload Der Payload der empfangenen MQTT-Nachricht.
   @param length Die Länge des Payloads.
*/
void callback(char* topic, byte* payload, unsigned int length) {
  // Ausgabe des empfangenen Topics
  Serial.print("Nachricht empfangen [");
  Serial.print(topic);
  Serial.print("] ");

  // Ausgabe des Payloads
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

  // Debugging-Ausgabe, um sicherzustellen, dass die callback-Funktion aufgerufen wird
  Serial.println("Callback-Funktion wurde aufgerufen!");

  // Unterscheidung der Nachrichten und Ausführung entsprechender Aktionen
  if (strcmp(topic, "solar/bkw-steuerung/app-steuerung") == 0) {
    String message = "";  // Initialisiere den String für die Nachricht

    // Zusammenstellen der Nachricht aus dem Payload
    for (int i = 0; i < length; i++) {
      message += (char)payload[i];  // Füge jeden Byte des Payloads zum String hinzu
    }

    message.trim();  // Entferne führende und abschließende Leerzeichen

    // Ausführen entsprechender Aktionen basierend auf dem Inhalt der Nachricht
    if (message.equals("Windsensor aktivieren")) {
      // Aktion: Windsensor aktivieren
      digitalWrite(PIN_BUTTON, HIGH);
      Serial.println("Windsensor wurde aktiviert.");
      stepper.moveTo(Windschutz);
      stepper.runToPosition();
      Serial.println("Motor fährt zur Windschutzposition.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    } else if (message.equals("Windsensor deaktivieren")) {
      // Aktion: Windsensor deaktivieren
      digitalWrite(PIN_BUTTON, LOW);
      Serial.println("Windsensor wurde deaktiviert.");
      Warteposition_aktiv = false;
    } else if (message.equals("position_setzen")) {
      // Aktion: Setzen der Windschutzposition
      stepper.moveTo(Windschutz);
      stepper.runToPosition();
      Serial.println("Motor fährt zur definierten Windschutzposition.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    } else if (message.equals("Startposition")) {
      // Aktion: Motor zur Startposition bewegen
      stepper.moveTo(Startposition);
      stepper.runToPosition();
      Serial.println("Motor fährt zur Startposition.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    } else if (message.equals("Endposition")) {
      // Aktion: Motor zur Endposition bewegen
      stepper.moveTo(Endposition);
      stepper.runToPosition();
      Serial.println("Motor fährt zur Endposition.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    } else if (message.equals("+1000")) {
      // Aktion: Motor um +1000 Positionen bewegen
      int newPos = stepper.currentPosition() + 1000;
      stepper.moveTo(newPos);
      stepper.runToPosition();
      Serial.println("Motor fährt um +1000 Positionen.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    } else if (message.equals("-1000")) {
      // Aktion: Motor um -1000 Positionen bewegen
      int newPos = stepper.currentPosition() - 1000;
      stepper.moveTo(newPos);
      stepper.runToPosition();
      Serial.println("Motor fährt um -1000 Positionen.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    } else if (message.equals("-17000")) {
      // Aktion: Motor um -17000 Positionen bewegen
      int newPos = stepper.currentPosition() - 17000;
      stepper.moveTo(newPos);
      stepper.runToPosition();
      Serial.println("Motor fährt um -17000 Positionen.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    } else if (message.equals("+24000")) {
      // Aktion: Motor um +24000 Positionen bewegen
      int newPos = stepper.currentPosition() + 24000;
      stepper.moveTo(newPos);
      stepper.runToPosition();
      Serial.println("Motor fährt um +24000 Positionen.");
      Warteposition_aktiv = true;
      WindschutzTimer = millis();
    }
  }
}

/**
   Funktion zum Aktualisieren des MQTT-Clients und zum Senden/Empfangen von Nachrichten.
   Wenn keine Verbindung zum Broker besteht, wird eine erneute Verbindung hergestellt.
*/
void mqttLoop() {
  // Überprüfen, ob der Client verbunden ist
  if (!client.connected()) {
    // Wenn nicht verbunden, erneut verbinden
    reconnect();
  }
  // Aktualisiere den MQTT-Client
  client.loop();
}
1 Like

@StefanL38

Ich war beim Lesen gerade kurz ein wenig schockiert. Beim zweiten Durchlauf habe ich dann gehofft richtig verstanden zu haben und habe deinen Code eingefügt und hochgeladen. Danke dir :slight_smile:

Ich lasse mal laufen und poste dann, wenn er stehen bleibt!

Gemeinsam mit eurem Support habe ich Hoffnung, dass wir den Fehler finden.

Edit:
Das mag jetzt Zufall gewesen sein, aber erst ist gerade scheinbar hängen geblieben.

20:06:51.314 -> direkt vor sendMQTTData()
20:06:51.347 -> direkt vor mqttLoop()
20:06:51.379 -> direkt vor updateNtpTime()
20:06:51.411 -> direkt vor checkWiFiConnection()
20:06:51.443 -> direkt vor checkWindsensor()
20:06:51.475 -> Zeitserver IP-Adresse erhalten
20:06:53.446 -> Keine Antwort vom Zeitserver
20:06:53.478 -> direkt vor sendMQTTData()
20:06:53.510 -> Fehlgeschlagen, rc=-2 Versuche es in 5 Sekunden erneut
20:07:08.411 -> Fehlgeschlagen, rc=-2 Versuche es in 5 Sekunden erneut
20:07:23.370 -> Fehlgeschlagen, rc=-2 Versuche es in 5 Sekunden erneut
20:07:38.292 -> Fehlgeschlagen, rc=-2 Versuche es in 5 Sekunden erneut
20:07:53.242 -> Fehlgeschlagen, rc=-2 Versuche es in 5 Sekunden erneut

Ich habe jetzt noch einmal neugestartet. Vielleicht tritt das ja erneut dort auf.

Ist doch prima - jetzt hast Du einen möglichen Fehlerort.

Er bleibt vielleicht beim reconnect() zum MQTT stecken.
Da hast Du Dir eine schöne Endlosschleife gebastelt.

Das Problem mit Fehlerabfragen (nicht nur) im Embedded-Bereich ist m.E. immer, das man eine gültige Abhilfe bzw. Alternative zum Weitermachen braucht. Im einfachsten Fall ein System-Neustart, im schlimmsten Fall eine nichtssagende Nachricht an den Nutzer.

Dazwischen ist die Welt sehr grau - was also ist Dein Plan, wenn nach N mal fünf Sekunden die Verbindung zum MQTT nicht wieder steht?

1 Like

Deine reconnect function sieht so aus

void reconnect() {
  // Loop, bis eine Verbindung hergestellt wird
  while (!client.connected()) {
    // Verbindung mit MQTT-Broker herstellen, einschließlich Benutzername und Passwort
    if (client.connect("ArduinoClient", mqtt_user, mqtt_password)) {
      Serial.println("verbunden"); // Verbindung erfolgreich
    } else {
      Serial.print("Fehlgeschlagen, rc="); // Verbindung fehlgeschlagen, rc=...
      Serial.print(client.state());
      Serial.println(" Versuche es in 5 Sekunden erneut");
      // 5 Sekunden warten und erneut versuchen
      delay(5000);
    }
  }
}

also davon das du einfach nur 5 Sekunden per delay(5000) wartest
wird nicht unbedingt eine neue Verbindung aufgebaut.

Jetzt kommt es darauf an an welcher Stelle es da klemmt.
Ist die WLAN-Verbindung weg?
Will dieser "server" hier

  server.begin();

nicht mehr?

will der mqtt_server nicht mehr?

Was gibt es für Möglichkeiten diese Fehlerursachen durch Serial.print von
was auch immer auch auf den seriellen Monitor zu bringen?
Kann man auf der Gegenseite auch ein Log aktivieren?
Am besten mit Zeitstempel damit man auch zeitliche Zusammenhänge zwischen dem ESP und dem anderen MQTT-gedöns nachvollziehen kann

Habe keine Ahnung vom MQTT, jedoch sollte bei

server.begin();

die zugehörige Portnummer eingetragen werden 8883 oder1883 ?

Ich habe gerade wieder einen Abbruch gehabt. Das ist aber schon arg häufig jetzt. Das Gerät ist nicht an seiner ursprünglichen Position. Was aber sogar besser ist, weil nur noch ein paar Meter vom Router entfernt.

20:48:46.940 -> direkt vor checkWiFiConnection()
20:48:46.973 -> direkt vor checkWindsensor()
20:48:47.005 -> Zeitserver IP-Adresse erhalten
20:48:48.993 -> Keine Antwort vom Zeitserver
20:48:49.025 -> direkt vor sendMQTTData()
20:48:49.058 -> Fehlgeschlagen, rc=-2 Versuche es in 5 Sekunden erneut
20:49:03.983 -> Fehlgeschlagen, rc=-2 Versuche es in 5 Sekunden erneut
20:49:18.903 -> Fehlgeschlagen, rc=-2 Versuche es in 5 Sekunden erneut
20:49:33.833 -> Fehlgeschlagen, rc=-2 Versuche es in 5 Sekunden erneut

Auffällig ist wieder, dass kurz vorher wieder "Keine Antwort vom Zeitserver" kam. Danach reißt die Verbindung zum MQTT-Server scheinbar ab. Auf der Gegenseite kann ich auch sehen, dass keine Verbindung mehr besteht. Da der gesamte Sketch stehen bleibt, ist natürlich dann auch keine Verbindung zum WLAN mehr da.

Idee von mir was ggf. hilft. Die "void reconnect" anpassen. Die Zeit auf 10 Sekunden erhöhen, maximal drei Versuche zulassen und dann die Verbindung zum MQTT Server beenden und komplett neu aufbauen lassen.

Zusätzlich hätte ich jetzt die Wartezeit in der "time_t getNtpTime" von 1500 auf 5000 erhöht.


  // Warte auf eine Antwort vom Zeitserver
  uint32_t beginWait = millis();
  while (millis() - beginWait < 5000) {
    int size = udp.parsePacket();

Was meint ihr? Könnte das helfen?


Zu

server.begin();

Danke für den Hinweis. Das kann komplett raus. Das ist noch von früher sehe ich gerade. Hatte den Zugriff damals via http-Server gemacht und nicht via MQTT.

Das wird hier inkl. Port ausgeführt.

// Verbindung zum MQTT-Broker herstellen
  client.setServer(mqtt_server, mqtt_port);
  Serial.println("client.setServer(mqtt_server, mqtt_port) done ");
  client.setCallback(callback);
  Serial.println("client.setCallback(callback) done ");

Ok Habe in ganzen Sketch nicht geschaut, Stimmt natürlich.

1 Like

Was hast Du für einen Router? Hat der evtl. einen Zeitserver? Die Fritzbox hat einen.

Gruß Tommy