Gewächshaus_Temperatursteuerung mit Windalarm

Hallo Zusammen

Ich bin in einer Gruppe welche ein Gewächshaus 10 x 30 Meter betreibt und suchte vor einigen Monaten nach einer Gewächshaussteuerung mit Windmesser, für die Schliessung der Seitenwände bei Sturm. Kam dann auf den Arduino UNO R und kaufte mir ein Starterset. Ich kannte da die Programmiersprache C noch nicht, bin somit totaler Anfänger, seit gnädig mit eurer Kritik!

Habe das Programm so mit Kommentaren versehen, dass möglichst auch ein Laie aus unserer Gruppe die Befehle verstehen könnte.

Jetzt möchte ich von euch Experten wissen, ob mein Programm auch in der Praxis funktionieren würde.

Im Testaufbau funktioniert es und macht was ich möchte.

Den Temperaturfühler würde für den Einsatz noch geändert werden (DS18B20 Wasserdichte Version) mit einem 1,8kOhm für die Kabellänge von ca. 20 Meter.

Was würdet Ihr mir empfehlen, mit welchem Bord soll ich die Steuerung fertigen? Arduino Uno R, ESP32 oder ein anderes?

Kurze Auflistung von den Einstellmöglichkeiten über die Taster im Display:
minTemp: Ab wann Seitenwand schliessen
maxTemp: Ab wann Seitenwand öffnen
maximaler Wind: ab wann schliessen bei Sturm
SturmPause: Wie lange die Pause nach Sturmalarm sein soll in Minuten
Motorenlaufzeit: Wie lange die Motoren laufen sollen, bis die Seitenwand-Position erreicht ist, um dann den Temperaturausgleich zu starten (Intervall)
Intervall: Wie lange die Pause für den Temperaturausgleich sein soll, bis die Seitenwände die Position wieder ändern

Sicherheit Überspannung bei Arduino Steuerung:
Relais 1-4 sind Schliesser Relais (Stromdurchlass, wenn Relais Strom erhält)
Relais 5 & 6 sind Öffner Relais (Stromdurchlass wird unterbrochen, wenn Relais Strom erhält)
Damit der Strom, Seitenwand schliessen und Seitenwand öffnen nicht gleichzeitig zu den Motoren fliessen kann, werden die L1 und L2 von Seitenwand öffnen durch Relais 5 und 6 geleitet. So wird der Stromdurchfluss bei Relais 5 und 6 unterbrochen, wenn Seitenwand schliessen aktiv ist.

Motoren Drehrichtung (400V)
Bei Seitenwand öffnen wird L1 und L2 vertauscht, um die Drehrichtung der Motoren zu ändern.

Ich habe einen nicht sehr professionellen Schaltplan erstellt, mit manueller Schaltung und automatischer Steuerung durch Arduino Uno R, ich werde versuchen, dass pdf hochzuladen.

Originalprogramm war von draeger-it.blog/arduino-temperaturueberwachung-relais-per-schwellwert-tastersteuerung-mit-lcd-anzeige/) (vielen Dank dafür) dieses habe ich dann abgeändert.

#include <LCDI2C_Latin.h>  // Bibliothek für das Display

// Library von Adafruit (benötigt auch Adafruit Unified Sensor Library)
// Bibliotheken für den Temperatursensor
#include "DHT.h"
#include <DHT_U.h>
#include <Adafruit_Sensor.h>

// Bibliothek zum Speichern & Lesen von Daten
// aus dem internen Speicher
#include <EEPROM.h>

// Bibliothek zum Entprellen eines Tasters
#include <Bounce2.h>

// Bibliotheken, um mit dem LCD-Display zu kommunizieren
#include <Wire.h>

#define DHTPIN 3  // Digitaler Pin 3 wird dem DHT-Sensor zugewiesen

#define DHTTYPE DHT11  // Bibliothek für Temperatursensor DHT 11

DHT dht1(DHTPIN, DHTTYPE); // Initialisiere den DHT-Sensor.

// Das Relais ist am digitalen Pin D4 angeschlossen.
//#define pin 4, OUTPUT
unsigned long TimelineIntervalTemperaturmesser;  // Variable Speicher für Zeitlinie Temperaturmessung.
int StatusSeitenwandOeffnen = LOW;
int StatusSeitenwandSchliessen = LOW;
unsigned long TimelineRelais;
unsigned long TimelineInterval;  // Variable Speicher für Systemzeit.

// Taster "select" am digitalen Pin D12 angeschlossen.
#define tasterSelect 12
// Taster "hoch" am digitalen Pin D11 angeschlossen.
#define tasterHoch 11
// Taster "runter" am digitalen Pin D10 angeschlossen.
#define tasterRunter 10

#define relais4 4  // Relais 4 ist an Pin D4
#define relais5 5  // Relais 5 ist an Pin D5
#define relais6 6  // Relais 6 ist an Pin D6
#define relais7 7  // Relais 7 ist an Pin D7
#define relais8 8  // Relais 8 ist an Pin D8
#define relais9 9  // Relais 9 ist an Pin D9

// Initialisieren des LCD-Displays mit der I2C-Adresse 0x27
LCDI2C_Latin lcd(0x27, 20, 4);

// Minimale Temperatur Speicherort
int minTemp = 0;
// Maximale Temperatur Speicherort
int maxTemp = 0;
// Maximaler Wind Speicherort
int maxWind = 0;
// Aktuelle Temperatur Speicherort
int aktuelleTemp = 0;
// Aktueller Wind km/h Speicherort
int aktuellerWind = 0;
// Sturmpause Speicherort
int SturmPause = 0; 
// Motorlaufzeit Speicherort
int Motorlaufzeit = 0;  // Wie lange der Motor läuft, um die Lüftungsposition zu erreichen
// Lüftungsintervall Speicherort
int Lueftungsintervall = 0;  // Pause zwischen Seitenwandpositionierung.

// Bounce2-Objekte für die Taster
Bounce btnSelect = Bounce();
Bounce btnUp = Bounce();
Bounce btnDown = Bounce();

// Intervall für das Entprellen der Taster
const int BTN_INTERVALL = 25;

// Auswahl für Cursor auf Display
// 0 - min. Temperatur
// 1 - max. Temperatur
// 2 - aktuelle Temperatur
int auswahl = 0;  // > min Temp

// Feld zum Speichern des Zeitstempels der letzten Aktualisierung.
unsigned long lastUpdate = 0UL;

int ADRESSE_MINTEMP = 0;  // Speicherort für Mintemp
int ADRESSE_MAXTEMP = 2;  // Speicherort für Maxtemp
int ADRESSE_MAXWIND = 4;  // Speicherort für Maxwind
int ADRESSE_STURMPAUSE = 6;  // Speicherort für Sturmpause
int ADRESSE_MOTORLAUFZEIT = 8;  // Speicherort für Motorlaufzeit
int ADRESSE_LUEFTUNGSINTERVALL = 10;  // Speicherort für Lüftungsintervall

// NEU: Signatur zur Erkennung eines leeren EEPROMs
int ADRESSE_SIGNATUR = 12;       // Neue freie Adresse nutzen
const int EEPROM_SIGNATUR = 22;  // Beliebige Zahl als "Erkennungsmerkmal"

unsigned long lastDebounceTime = 0;  // Das letzte Mal, als der Ausgangspin umgeschaltet wurde
unsigned long debounceDelay = 3000;  //3000 ist die Messdauer für Windmessung

int pinInterrupt = 2;  // Eingang für Windmesser
unsigned long TimelineWind = millis(); // Speicherort für Zeitlinie TimelineWind
int Count = 0;         // Speicherplatz für Windmesser Impulszählung
int WindStatus = LOW;  // Windstatus am Start LOW

// Windmesser Impuls
void onChange() {
  if (digitalRead(pinInterrupt) == LOW)  // Ist der pinInterrupt LOW, beginne die Impulszählung des Windmessers
    Count++;                             // Immer pro Impuls +1 dazuzählen
}

//___________________________________________________________________________________________
// SETUP WIRD NUR EINMAL BEIM START DURCHGEFÜHRT
void setup() {
  WindStatus = LOW; // Windstatus auf LOW setzen

  pinMode(pinInterrupt, INPUT_PULLUP);  // Einstellung Interrupt-Pin

  // Aktivieren
  attachInterrupt(digitalPinToInterrupt(pinInterrupt), onChange, FALLING);

  // Den Pin der Relais als Ausgang definieren
  pinMode(relais4, OUTPUT);
  pinMode(relais5, OUTPUT);
  pinMode(relais6, OUTPUT);
  pinMode(relais7, OUTPUT);
  pinMode(relais8, OUTPUT);



  Serial.begin(9600);  // Starte den seriellen Monitor am PC
  

  // Beginn der Kommunikation mit dem Sensor DHT11
  dht1.begin();

  // Beginn der Kommunikation mit dem LCD-Display
  lcd.init();       // LCD initialisieren
  lcd.backlight();  // Backlight einschalten

  // Initialisieren der Taster
  // Die Taster sind über den internen 10 kOhm Widerstand verbunden
  btnSelect.attach(tasterSelect, INPUT_PULLUP);  // Select-Taster
  btnSelect.interval(BTN_INTERVALL);

  btnUp.attach(tasterHoch, INPUT_PULLUP);  // Hoch-Taster
  btnUp.interval(BTN_INTERVALL);

  btnDown.attach(tasterRunter, INPUT_PULLUP);  // Ab-Taster
  btnDown.interval(BTN_INTERVALL);

  // Beschreiben des EEPROM-Speichers beim Start
  int gespeicherteSignatur = readFromEEPROM(ADRESSE_SIGNATUR);  // Prüfen, ob die Signatur bereits im Speicher existiert

  if (gespeicherteSignatur != EEPROM_SIGNATUR) {  // Weicht der Wert(!) von gespeicherteSignatur ab, dann EEPROM-Signatur schreiben
    // EEPROM IST LEER -> Standardwerte definieren
    minTemp = 22;             // Gewünschten Standardwert eintragen
    maxTemp = 25;             // Gewünschten Standardwert eintragen
    maxWind = 45;             // Gewünschten Standardwert eintragen
    SturmPause = 20;          // Gewünschten Standardwert eintragen
    Motorlaufzeit = 30;       // Gewünschten Standardwert eintragen
    Lueftungsintervall = 60;  // Gewünschten Standardwert eintragen

    // Werte einmalig in das leere EEPROM schreiben
    writeToEEPROM(ADRESSE_MINTEMP, minTemp);
    writeToEEPROM(ADRESSE_MAXTEMP, maxTemp);
    writeToEEPROM(ADRESSE_MAXWIND, maxWind);
    writeToEEPROM(ADRESSE_STURMPAUSE, SturmPause);
    writeToEEPROM(ADRESSE_MOTORLAUFZEIT, Motorlaufzeit);
    writeToEEPROM(ADRESSE_LUEFTUNGSINTERVALL, Lueftungsintervall);

// Signatur setzen, damit dieser Block nie wieder ausgeführt wird
    writeToEEPROM(ADRESSE_SIGNATUR, EEPROM_SIGNATUR);

    Serial.println("EEPROM war leer. Standardwerte wurden initialisiert."); // Serieller Monitor Ausdruck
  } else { // Sonst
    // EEPROM ENTHÄLT GÜLTIGE DATEN -> Normal auslesen
    minTemp = readFromEEPROM(ADRESSE_MINTEMP);
    maxTemp = readFromEEPROM(ADRESSE_MAXTEMP);
    maxWind = readFromEEPROM(ADRESSE_MAXWIND);
    SturmPause = readFromEEPROM(ADRESSE_STURMPAUSE);
    Motorlaufzeit = readFromEEPROM(ADRESSE_MOTORLAUFZEIT);
    Lueftungsintervall = readFromEEPROM(ADRESSE_LUEFTUNGSINTERVALL);

    Serial.println("Daten erfolgreich aus EEPROM geladen.");
  }

  writeLcdDisplay();  // Befehl für das Beschreiben des Displays im Setup
}


void writeToEEPROM(int adresse, int wert) {
  // EEPROM.update spart Schreibzyklen, indem es Werte vor dem Schreiben vergleicht
  int aktuellerWert;
  EEPROM.get(adresse, aktuellerWert);
  if (aktuellerWert != wert) {
    EEPROM.put(adresse, wert);
  }
}

int readFromEEPROM(int adresse) {      //
  int gelesenerWert = 0;               // Speicherort wird definiert und auf 0 gesetzt, dann..
  EEPROM.get(adresse, gelesenerWert);  // werden Daten aus dem EEPROM-Speicher eingelesen
  return gelesenerWert;                // Gelesener Wert wird zurückgesendet
}

// Schreibt eine Zeile auf das Display
void printTextAt(int lineNumber, int column, String text) {  // Speicherort für: lineNumber (0, 0) / column (auswahl == 0 ? ">") / String Text " ");
  lcd.setCursor(column, lineNumber);                         // Setze Cursor auf Zeilennummer
  lcd.print(text);                                           // Schreibe Text auf LCD
}

// Beschreibt das Display mit Text in Abhängigkeit von der
// aktuellen Auswahl.
void writeLcdDisplay() {  // Befehl für das Beschreiben des LCD-Displays (Wird in LOOP aufgerufen)
  lcd.clear();            // LCD löschen
  if (auswahl < 3) {      // Ist die Auswahl kleiner als 3, dann druckt es diesen Abschnitt (minTemp, maxTemp und maxWind)

    printTextAt(0, 0, auswahl == 0 ? ">" : " ");  // 0 ist die erste Zeile
    printTextAt(1, 0, auswahl == 1 ? ">" : " ");  // 1 ist die zweite Zeile / 0 ist die Position zum Schreiben auf dem Display / Auswahl ist 1 (max Temp)
    printTextAt(2, 0, auswahl == 2 ? ">" : " ");
    printTextAt(0, 1, "min. Temp.:" + String(minTemp, DEC));  // + String(minTemp): Es wird der minTemp-Wert abgerufen und an...
    printTextAt(1, 1, "max. Temp.:" + String(maxTemp, DEC));  // DEC: Das erzwingt die Darstellung als Dezimalzahl (was bei int der Standard ist).
    printTextAt(2, 1, "max Wind: " + String(maxWind, DEC));

    printTextAt(3, 0, " akt.T." + String(aktuelleTemp, DEC));  // Beschreibe Zeile 4 mit aktT & Wert
    printTextAt(3, 9, "");                                     // Zeichen für °C
    printTextAt(3, 11, " Wind." + String(aktuellerWind));      // Ab Spalte 10 druckt es Wind. & Wert

  } else {  // Sonst druckt es diesen Abschnitt (Sturmpause, Motorlaufzeit, Lüftungsintervall)

    printTextAt(0, 0, auswahl == 3 ? ">" : " ");
    printTextAt(1, 0, auswahl == 4 ? ">" : " ");
    printTextAt(2, 0, auswahl == 5 ? ">" : " ");
    printTextAt(0, 1, "Sturm Pause: " + String(SturmPause, DEC));
    printTextAt(1, 1, "Motorlaufzeit: " + String(Motorlaufzeit, DEC));
    printTextAt(2, 1, "Lueft. Int.:" + String(Lueftungsintervall, DEC));

    printTextAt(3, 0, " akt.T." + String(aktuelleTemp, DEC));
    printTextAt(3, 9, " Wind." + String(aktuellerWind));

    // Schreibe Werte in den internen EEPROM-Speicher
    writeToEEPROM(ADRESSE_MINTEMP, minTemp);
    writeToEEPROM(ADRESSE_MAXTEMP, maxTemp);
    writeToEEPROM(ADRESSE_MAXWIND, maxWind);
    writeToEEPROM(ADRESSE_STURMPAUSE, SturmPause);
    writeToEEPROM(ADRESSE_MOTORLAUFZEIT, Motorlaufzeit);
    writeToEEPROM(ADRESSE_LUEFTUNGSINTERVALL, Lueftungsintervall);
  }
}

void SeitenwandOeffnen() {  // Wenn im LOOP dieser Befehl steht, wird das ausgeführt:
  // Zur Sicherheit werden alle Relais zum Schließen auf LOW gesetzt
  digitalWrite(relais4, LOW);  // Nuller Berg
  digitalWrite(relais5, LOW);  // L1 und L2 Berg Schließen
  digitalWrite(relais7, LOW);  // Nuller Tal
  digitalWrite(relais8, LOW);  // L1 und L2 Tal Schließen
  StatusSeitenwandSchliessen = LOW;

  // Alle Relais zum Öffnen werden auf HIGH gesetzt
  digitalWrite(relais4, HIGH);  // Nuller Berg
  digitalWrite(relais6, HIGH);  // L1 und L2 Berg Öffnen
  digitalWrite(relais7, HIGH);  // Nuller Tal
  digitalWrite(relais9, HIGH);  // L1 und L2 Tal Öffnen
  StatusSeitenwandOeffnen = HIGH;
  Serial.println("Seitenwand wird geöffnet");
  Serial.println("________________________");
}

void SeitenwandOeffnenBeenden() {
  // Zur Sicherheit werden alle Relais zum Schließen auf LOW gesetzt
  digitalWrite(relais4, LOW);  // Nuller Berg
  digitalWrite(relais5, LOW);  // L1 und L2 Berg Schließen
  digitalWrite(relais7, LOW);  // Nuller Tal
  digitalWrite(relais8, LOW);  // L1 und L2 Tal Schließen
  StatusSeitenwandSchliessen = LOW;

  // Alle Relais zum Öffnen werden auf LOW gesetzt
  digitalWrite(relais4, LOW);  // Nuller Berg
  digitalWrite(relais6, LOW);  // L1 und L2 Berg Öffnen
  digitalWrite(relais7, LOW);  // Nuller Tal
  digitalWrite(relais9, LOW);  // L1 und L2 Tal Öffnen
  StatusSeitenwandOeffnen = LOW;
  Serial.println("Seitenwand öffnen beendet");
  Serial.println("________________________");
}

void SeitenwandSchliessen() {
  // Zur Sicherheit werden alle Öffnen-Relais auf LOW gesetzt
  //digitalWrite(relais4, LOW);  // Nuller Berg
  digitalWrite(relais6, LOW);  // L1 und L2 Berg Öffnen
  digitalWrite(relais7, LOW);  // Nuller Tal
  digitalWrite(relais9, LOW);  // L1 und L2 Tal Öffnen
  StatusSeitenwandOeffnen = LOW;

  // Alle Relais zum Schließen werden auf HIGH gesetzt
  digitalWrite(relais4, HIGH);        // Nuller Berg
  digitalWrite(relais5, HIGH);        // L1 und L2 Berg Schließen
  digitalWrite(relais7, HIGH);        // Nuller Tal
  digitalWrite(relais8, HIGH);        // L1 und L2 Tal Schließen
  StatusSeitenwandSchliessen = HIGH;  // Status auf HIGH setzen
  Serial.println("Seitenwand wird geschlossen (Relais)");
  Serial.println("________________________");
}

void SeitenwandSchliessenBeenden() {

  // Zur Sicherheit werden alle Öffnen-Relais auf LOW gesetzt
  digitalWrite(relais4, LOW);  // Nuller Berg
  digitalWrite(relais6, LOW);  // L1 und L2 Berg Öffnen
  digitalWrite(relais7, LOW);  // Nuller Tal
  digitalWrite(relais9, LOW);  // L1 und L2 Tal Öffnen
  StatusSeitenwandOeffnen = LOW;

  // Alle Relais zum Schließen werden auf LOW gesetzt
  digitalWrite(relais4, LOW);                       // Nuller Berg
  digitalWrite(relais5, LOW);                       // L1 und L2 Berg Schließen
  digitalWrite(relais7, LOW);                       // Nuller Tal
  digitalWrite(relais8, LOW);                       // L1 und L2 Tal Schließen
  StatusSeitenwandSchliessen = LOW;                 // Status auf LOW setzen
  Serial.println("Seitenwand schliessen beenden");  // Drucken Seitenwand...
  Serial.println("________________________");
}

//_________________________________________________________________________________________________________
// LOOP WIRD IMMER WIEDER WIEDERHOLT
void loop() { 

  if (aktuellerWind > maxWind) {  // Aktueller Wind ist höher als die eingestellte maximale Windgeschwindigkeit
    WindStatus = HIGH;            // Windstatus auf HIGH setzen
    TimelineWind = millis();      // Starten der TimelineWind-Zeitlinie neu
    SeitenwandSchliessen();       // Befehl für die Ansteuerung der Relais

    Serial.println("Windalarm! Seitenwand schliessen");  // Seriellen Monitor bedrucken...
  }

  if (WindStatus == HIGH && aktuelleTemp <= maxTemp) {    // Ist der WindStatus HIGH und aktuelleTemp tiefer als maxTemp, dann:
    if (millis() - TimelineWind > SturmPause * 60000L) {  // Sturmpause ist die Zeitpause nach einem Windalarm. Auf dem Display einstellbar in Minuten (millis x 60000 = Min.)
      SeitenwandSchliessenBeenden();                      // Befehl für die Ansteuerung der Relais
      WindStatus = LOW;                                   // Bereit für den nächsten Alarm, wenn der Wind wieder okay war
      Serial.println("Windalarm beendet");                // Seriellen Monitor bedrucken: Windalarm beendet
      TimelineWind = millis();                            // Starten der TimelineWind-Zeitlinie neu
    }
  }


  if (WindStatus == HIGH && aktuelleTemp > maxTemp && StatusSeitenwandOeffnen == LOW) {  // Ist der WindStatus HIGH, aktuelleTemp höher als maxTemp und StatusSeitenwandOeffnen LOW, dann:

    if (millis() - TimelineWind > SturmPause * 60000L) {  // Sturmpause ist die Zeitpause nach einem Windalarm. Auf dem Display einstellbar in Minuten (millis x 60000 = Min.)

      SeitenwandSchliessenBeenden();                                     // Befehl für die Ansteuerung der Relais
      delay(1000);                                                       // Pause für Relais bei Umschaltung von Schließen auf Öffnen
      SeitenwandOeffnen();                                               // Befehl für die Ansteuerung der Relais
      StatusSeitenwandOeffnen = HIGH;                                    // Dieses unlogische HIGH wird für die nächste if-Abfrage gebraucht, um die Bedingung zu erfüllen.
      Serial.println("Seitenwand wird nach Windalarm wieder geöffnet");  // Serieller Monitor druckt: Seitenwand wird nach Windalarm wieder geöffnet
      TimelineWind = millis();                                           // Starten der TimelineWind-Zeitlinie neu
    }
  }


  if (WindStatus == HIGH && StatusSeitenwandOeffnen == HIGH) {  // Ist der WindStatus HIGH und StatusSeitenwandOeffnen HIGH, dann:

    if (millis() - TimelineWind > 15000) {  // Zeitdauer für das vollständige Öffnen der Seitenwand nach dem Windalarm bei Temperaturen höher als MaxTemp

      Serial.println("Seitenwand nach Windalarm wieder offen");  // Serieller Monitor druckt: Seitenwand nach Windalarm wieder offen

      SeitenwandOeffnenBeenden();  // Befehl für die Ansteuerung der Relais

      WindStatus = LOW;         // Bereit für den nächsten Alarm, wenn der Wind wieder okay war
      TimelineWind = millis();  // Starten der TimelineWind-Zeitlinie neu
    }
  }



  {
    if ((millis() - lastDebounceTime) > debounceDelay) {  // Ist millis minus lastDebounceTime größer als debounceDelay. Eingestellter unsigned long Wert (oberster Abschnitt) für die Messdauer der Windmessung
      lastDebounceTime = millis();                        // Starten der lastDebounceTime-Zeitlinie neu

      aktuellerWind = ((Count / (debounceDelay / 1000)) * 10);  // Formel für die Berechnung der Windgeschwindigkeit; je nach Windmesser. Stimmt jetzt noch nicht!!!

      // Ausdrucken auf dem seriellen Monitor, wie die Werte im EEPROM-Speicher sind:

      Serial.print(aktuelleTemp);
      Serial.println(" aktuelle Temperatur");
      Serial.print(minTemp);
      Serial.println(" minTemp");
      Serial.print(maxTemp);
      Serial.println(" maxTemp");
      Serial.print(aktuellerWind);
      Serial.println(" km/h Aktueller Wind");
      Serial.print(SturmPause);
      Serial.println(" Minuten Sturm Pause");
      Serial.print(Motorlaufzeit);
      Serial.println(" Sekunden für Motorlaufzeit");
      Serial.print(Lueftungsintervall);
      Serial.println(" Sekunden für Lueftungsintervall");
      Serial.println("           ");

      Count = 0;  // Count wieder auf 0 zurücksetzen
    }
    delay(1);  // Pause 1 Millisekunde, wird für die Messung der Windgeschwindigkeit benötigt
  }


  // Aktualisieren der Taster
  btnSelect.update();  // Taster für die Auswahl, welche Zeile
  btnUp.update();      // Taster für Wert erhöhen
  btnDown.update();    // Taster für Wert senken

  // Wenn der Taster "select" gedrückt wurde, dann...
  if (btnSelect.fell()) {
    if (auswahl < 5) {  // Wenn die Auswahl kleiner als 5 ist, dann...
      auswahl += 1;     // Auswahl um 1 erhöhen
    } else {            // Sonst...
      auswahl = 0;      // Auf Auswahl 0 springen
    }

    writeLcdDisplay();  // Beschreiben des Displays. Befehl im Setup
  }

// Wenn der Taster "hoch" gedrückt wurde, dann...
  if (btnUp.fell()) {
    if (auswahl == 0) {         // Ist die Auswahl 0 (Zeile 1 min Temp), dann...
      minTemp += 1;             // Erhöhe den Wert bei minTemp um 1
    } else if (auswahl == 1) {  // Ist die Auswahl 1 (Zeile 2 max Temp), dann...
      maxTemp += 1;             // Erhöhe den Wert bei maxTemp um 1
    } else if (auswahl == 2) {  // Ist die Auswahl 2 (Zeile 3 maximaler Wind), dann...
      maxWind += 1;             // Erhöhe den Wert bei maxWind um 1
    } else if (auswahl == 3) {  // Ist die Auswahl 3 (Zeile 3 Sturmpause), dann...
      SturmPause += 1;          // Erhöhe den Wert bei Sturmpause um 1
    } else if (auswahl == 4) {  // Ist die Auswahl 4 (Zeile 4 Motorlaufzeit), dann...
      Motorlaufzeit += 1;       // Erhöhe den Wert bei Motorlaufzeit um 1
    } else if (auswahl == 5) {  // Ist die Auswahl 5 (Zeile 5 Lüftungsintervall), dann...
      Lueftungsintervall += 1;  // Erhöhe den Wert bei Lüftungsintervall um 1
    }
    writeLcdDisplay();           // Beschreiben des Displays. Befehl im Setup
    SeitenwandOeffnenBeenden();  // Wird gemacht, damit das Programm keinen Hänger hat
  }

  // Wenn der Taster "runter" gedrückt wurde, dann...
  if (btnDown.fell()) {
    if (auswahl == 0) {         // Ist die Auswahl 0 (Zeile 1 min Temp), dann...
      minTemp -= 1;             // Senke den Wert bei minTemp um 1
    } else if (auswahl == 1) {  // Ist die Auswahl 1 (Zeile 2 max Temp), dann...
      maxTemp -= 1;             // Senke den Wert bei maxTemp um 1
    } else if (auswahl == 2) {  // Ist die Auswahl 2 (Zeile 3 maximaler Wind), dann...
      maxWind -= 1;             // Senke den Wert bei maxWind um 1
    } else if (auswahl == 3) {  // Ist die Auswahl 3 (Zeile 3 Sturmpause), dann...
      SturmPause -= 1;          // Senke den Wert bei Sturmpause um 1
    } else if (auswahl == 4) {  // Ist die Auswahl 4 (Zeile 4 Motorlaufzeit), dann...
      Motorlaufzeit -= 1;       // Senke den Wert bei Motorlaufzeit um 1
    } else if (auswahl == 5) {  // Ist die Auswahl 5 (Zeile 5 Lüftungsintervall), dann...
      Lueftungsintervall -= 1;  // Senke den Wert bei Lüftungsintervall um 1
    }

    writeLcdDisplay();  // Beschreiben des Displays. Befehl im Setup

    SeitenwandOeffnenBeenden();  // Wird gemacht, damit das Programm keinen Hänger hat
  }

  // Wenn der Zeitpunkt der letzten Ausführung plus dem Wert
  // des Intervalls größer als die aktuellen Millisekunden sind, dann...
  if (millis() - TimelineIntervalTemperaturmesser > 5000) { // Temperaturwert wird alle 5 Sekunden (5000 Millisekunden) ausgelesen
    // Anfordern der Temperaturwerte
    float t = dht1.readTemperature();
    aktuelleTemp = dht1.readTemperature();

    TimelineIntervalTemperaturmesser = millis();

    writeLcdDisplay();
    // Überschreiben des Wertes für die letzte Ausführung
  }

// STEUERUNG DER SEITENWÄNDE / LÜFTUNG
  {

    if (WindStatus == LOW) {  // Nur ausführen, wenn der Windstatus LOW ist

      if ((aktuelleTemp <= (minTemp - 2.0)) || (aktuelleTemp >= (maxTemp + 2.0)))  // Springt zurück (return), wenn die Temperatur 2 Grad Differenz zu min Temp oder max Temp hat.
                                                                                   // Das wird gemacht, um die Relais zu schonen und nicht unnötig Strom zu verbrauchen.

        return;  // Befehl, um den Loop neu zu beginnen.

      // Wenn die gelesene Temperatur größer als der Wert der Konstante MAX_TEMP ist, dann soll die Lüftung öffnen.
      if (aktuelleTemp >= maxTemp) {

        if (StatusSeitenwandOeffnen == LOW) {  // Ist der Status LOW, dann...

          if (millis() - TimelineRelais > Motorlaufzeit * 1000L) {  // Motorlaufzeit x 1000 = Sekunden

            SeitenwandOeffnen();  // Im Setup oben der Befehl für die Ansteuerung der Relais

            Serial.println("Seitenwand wird jetzt geöffnet");
            TimelineRelais = millis(); // TimelineRelais neu starten
          }
        }

        if (StatusSeitenwandOeffnen == HIGH) {  // Wenn HIGH, dann...
          if (millis() - TimelineRelais > Lueftungsintervall * 1000L) {  // Lueftungsintervall x 1000 = Sekunden

            SeitenwandOeffnenBeenden();  // Im Setup oben der Befehl für die Ansteuerung der Relais

            Serial.println("Seitenwand öffnen wurde beendet");
            TimelineRelais = millis();  // TimelineRelais neu starten
          }
        }
      }
      // Wenn die gelesene Temperatur kleiner als der Wert der Konstante MIN_TEMP ist,
      // dann soll die Lüftung schließen.
      if (aktuelleTemp <= minTemp) {

        if (StatusSeitenwandSchliessen == LOW) { // Wenn LOW, dann...

          if (millis() - TimelineRelais > Motorlaufzeit * 1000L) {  // Motorlaufzeit x 1000 = Sekunden

            SeitenwandSchliessen();  // Im Setup oben der Befehl für die Ansteuerung der Relais

            Serial.println("Seitenwand wird geschlossen");
            TimelineRelais = millis();  // TimelineRelais neu starten 
          }
        }

        if (StatusSeitenwandSchliessen == HIGH) {  // Wenn HIGH, dann...
          if (millis() - TimelineRelais > Lueftungsintervall * 1000L) {  // Lueftungsintervall x 1000 = Sekunden

            SeitenwandSchliessenBeenden();  // Im Setup oben der Befehl für die Ansteuerung der Relais

            Serial.println("Seitenwand schliessen beendet");
            TimelineRelais = millis();  // TimelineRelais neu starten
          }
        }
      }
    }
  }
}

Ein normaler UNO tut's - ausse du brauchst WiFi für die Fernsteuerung. Wenn der Testaufbau funktioniert probier's einfach aus. Vergiss den Notaus-Schalter nicht (Kraftstrom unterbrechen)

ESP32 für Funk (Wifi, WLAN), Darstellung von Meßwerten auf einer Webseite.

Gewächshaus → Feuchtigkeit → Steckkontakte beim UNO → Korrosion daher einen Nano zum Löten, da ist der selbe µC wie beim UNO drauf.

  1. "Sicherung Erdung" besser nicht, Erdung muß immer vorhanden sein.
  2. I²C mit SDA und SCL.
  3. VIN vom UNO benötigt eine Spannung größer 5 V.

Das nennt man Funktion.

Das Relais wird mechanisch nicht folgen können.

Es ist C++, aber garnicht so übel!

Da frag ich mal als erstes nach, was da verwendet wird.

Dann ist die Überlegung, ob der Controller die manuelle Steuerung mit verfolgen soll, um z.B. das Pausentiming mit zu steuern, wenn Du manuell die Position der Seitenwände veränderst.

Haben die Antriebe Endschalter?

Ein UNO R3 arbeitet mit 5V auf den Pins. Ein UNO R4 und ein ESP mit 3,3V.
Das ist zu beachten, bei der Auswahl für die Perepherie.

Ich würde einen R3 vorziehen, der ist toleranter :slight_smile:

Der Code ist interessant.
Gibt sicher noch einige Ecken die übersichtlicher zu gestalten sind und definitiv sollten Module geschaffen werden, aber für den Anfang nicht übel.

Da ist es besser nicht ein Relais für öffnen und eines für schließen der Seitenwand zu nehmen, sondern eins für Ein/ Aus und ein zweites für die Drehrichtung.
Eine elektrische gegenseitige Verriegelung ist zwar gut, verhindert aber nicht den Fall wenn ein Kontakt in Zu-Stellung vershweißt.

Grüße Uwe

Vielen Dank für deine Antwort und Anregungen.
Erdung über eine Sicherung :see_no_evil_monkey: War beim Zeichnen einfach, vier Vierecken zu machen. Wird geändert.

Danke für deine Antwort.
Die manuelle Steuerung wird nur verwendet, zB für Servicearbeiten, oder für das Durchlüften bei arbeiten im Treibhaus. Normalbetrieb ist Automatisch.
Die Antriebe haben einen Endschalter.

Danke für deine Antwort.
Sehr hilfreicher Hinweis! Was bei einem Relais Kontakt verschweissung passieren kann, hatte ich noch nicht auf dem Schirm!

Das ist gut. Komme ich ggfls. nochmal drauf zurück.
Verrätst Du noch, was das für Relais sind, die Du da verwendest?

Habe zum testen einen CQC 10A 250VAC 6er Block verwendet. Aber habe mich noch nicht wirklich damit befasst betreffend Qualität.
Hast du eine Empfehlung?

So, jetzt kommen wir dem auch endlich näher und ich verstehe was Du da baust.

Das glaub ich nicht.
Du benutzt zwar zwei Aussenleiter, aber der Antrieb ist ein Rolladenmotor, der über die jeweilige Wicklung die Drehrichtung bekommt.
Du brauchst also nur 1 Aussenleiter und das Null-Potential.

Kurze Zwischeninfo: Die Relais sind zwar so ausgelegt, aber oftmals stimmen die Platinen nicht mit den VDE-Vorgaben. Das muss ggfls. noch geprüft werden ist ein Nebenschauplatz und ich glaube Du weißt was Du tust.

Theoretisch (1) kannst Du die Relais als solches benutzen. Ich würde die aber nur im stromlosen Zustand schalten und für die Stromzufuhr auf die Relaiskontakte vorher ein SSR setzen.

Theoretisch (2) kann das auch mit 2 SSR abgehandelt werden, aber die müssten dann softwareseitig verriegelt werden, damit nicht auf beiden Aussenleitern des Antrieb gleichzeitig Saft anliegt.

Als erstes die sicherere Lesitungschaltung mit Relais als Umschalter:

// (1)
N    L
*    *
|     \   SSR
|      \  oder REL1
|    +  +
|    |
|    |
|    +
|     \   REL2
|      \
|    +  +
|    |  |
N   L1  L2 

In der SSR bzw. Relaisvariante mit softwareseitiger Verriegelung wäre der Leistungskreis dann:

// (2)
N    L             
*    *---------*
|     \  SSR1   \ SSR2
|      \         \
|    +         +
|    |         |
N   L1        L2 

Egal für welche Version Du Dich entscheidest, empfiehlt es sich eine Funktion zu schreiben, die Dir alles abnimmmt.

In der V1 also sicherstellen, dass REL1/SSR abgeschaltet ist, umschalten in Richtung auf/ab, kurze Pause, SSR ein.

In der V2 wäre das alle SSR's abschalten, kurze Pause, Richtungs-SSR wieder ein.

wie schon gesagt, ich würde die V1 vorziehen, aber das ist Geschmackssache.

SSR's wären das hier meine Favoriten:

Die funktionieren schon bei 3V sicher und wenn Du später mal doch auf einen ESP umbauen willst, kannst Du die weiterverwenden.

Ob Du das mit 2 LED oder 2 Relais (am Lastkreis unbeschaltet) aufbaust ist egal.
Der folgende Code zeigt dir (hoffentlich) wie das funktionieren könnte.

// Forensketch
// https://forum.arduino.cc/

constexpr uint8_t enablePin {2};
constexpr uint8_t dirPin {3};

constexpr bool enable {HIGH};
constexpr bool up {HIGH};

constexpr bool disable {!enable};
constexpr bool down {!up};

void setup()
{
  Serial.begin(115200);
  delay(500);
  Serial.println(F("\r\nStart...\r\n"));
  pinMode(enablePin, OUTPUT);
  digitalWrite(enablePin, disable);
  pinMode(dirPin, OUTPUT);
  digitalWrite(dirPin, down);
  randomSeed(analogRead(A0));
}

void loop()

{
  antrieb(random (2));  // random gibt 0 oder 1 zurück! der max-Wert ist exclusive!!
  delay(500);
}

void antrieb(const bool dir)                            // dir 0 == LOW / 1 oder größer == HIGH
{
  Serial.println(F("Richtung: "));                      // Zur Kontrolle

  if (dir == up)                                        // Feststellung welche Richtung
  { Serial.println("up"); }                             // passende Ausgabe
  else
  { Serial.println(F("down"));}

  if (digitalRead(dirPin) != dir)                       // Abfrage ob neue Richtung
  {
    Serial.println(F("Setze neue Richtung! enable == OFF"));
    digitalWrite(enablePin, disable);                   // gesichert aus machen
    delay(1);                                           // one Moment please
    digitalWrite(dirPin, dir);                          // Richtung setzen
  }

  digitalWrite(enablePin, enable);                      // gesichert an machen
  Serial.println(F("Neuer Zustand: "));
  Serial.print(F("Direction: "));

  if (digitalRead(dirPin) == up)
  { Serial.print("UP"); }
  else
  { Serial.print("DOWN"); }

  Serial.print(F("Aktiv: "));

  Serial.println(digitalRead(enablePin) == enable) ? "EIN" : "AUS";
}

Das geht auch ganz ohne LED's, nur mit dem seriellen Monitor.

Achte darauf, dass die Geschwindigkeit bei mir auf 115200 gesetzt ist - solltest Du auch tun, mit 9600 ist es doch recht langsam.

Danke dir für diese ausführliche Antwort!
Wir haben wirklich 400 Volt Antriebe, mussten für unsere 230 Volt Inselanlage noch einen VFD-Wechselrichter kaufen, um die Motoren zu betreiben.

Vielen Dank für den Tipp mit den SSR. Kannte ich noch nicht und habe das geplante Projekt mit unserem Treibhaus Elektriker Profi noch nicht besprochen, da ich ihn nicht mit etwas belästigen wollte, wenn die Steuerung nicht machbar wäre.

Vielen Danke für den Sketch! Die überprüfende Abfrage, ob Strom am Relais/SSR anliegt, hatte ich noch nicht auf dem Schirm!

Ok, dann ist das mit der Drehrichtungsumkehr auch nicht viel anders. zu beachten ist ein etwaiger Nachlauf, wo der Motor dann zum Generator wird. Alles machbar.
ABER! dann nicht mit Einzelrelais!

Dafür gibt es Relais mit 2 bzw. 4 Kontaktpaaren für die Phasenumkehr und ein Hilfsrelais bzw. SSR in 3P.

Der Code ändert sich dabei nicht - wenn das Budget es hergibt, solltest Du 3P SSR für die Versorgung und mindestens 2 fach Umschalter als Relais für die Drehrichtung vorsehen.

z.B. sowas hier:

Oder in der Variante Händler aus DE:

Und bei den Relais würde ich bei Finder mal nachschlagen.
Die Mehrfach-Relais kommen selten mit 5V, aber bei 12V kommst du mit einem Transistor-Treiber aus.

Na dann! Viel Erfolg.

Die Antriebe gehen über ein Getriebe mit starker Untersetzung, somit sollte das kein Problem sein.

Du hast einen flinken Mikroprozessor, ein nicht ganz so flinkes mechanisches Relais und einen durch seine Rotationsmasse trägen Motor. Alles in Relation zueinander betrachtet. Auch ein gebremster Motor braucht Zeit zum Anhalten.

Wichtig ist, den Mikroprozessor mit Pausen für die mechanischen Elemente arbeiten zu lassen, und seien es auch nur ein paar Millisekunden.

Danke, werde ich neu berücksichtigen :+1: