Regentonnen Füllstandsanzeige +

Danke Euch beiden.
Ich glaube ich bin wieder einen Schritt weiter.
Es kommt halt nicht nur darauf an das Richtige zu schreiben. Es muss halt auch noch an der richtigen Stelle sein....
Ich hoffe hier bald besser zu werden...
Gruß Frank

Du solltest in kleinen Schritten voran gehen.
Baue einen einfachen Sketch, den du mittels Taster startest und dann über die Funktion mit millis() eine LED für die gewünschte Zeit zum Leuchten bringst.
Wenn dies funktioniert, baust du es in deinen Gesamtsketch (2 mal) ein, für die jeweilige Ventilsteuerung.
So wird es für dich deutlich einfacher.

Das machst du mit einer Hysterese, die du anstatt des else-Zweiges einbaust.
Da setzt du einen genügend niedrigen Wert ein.

Allerdings ist mir nicht ganz klar, ob dein Sketch auch immer sicher die Abfrage

if (millis() - currentMillis >= interval){

erreicht. Also bei jedem Loop-Durchlauf.

Wie hast du denn aktuell vor, dein Ventil zu steuern ?
Transistoren oder Relais ?

Hi,
Aktuell bin ich bei den Transistoren.
Hysterese ist dann also mein übernächstes Stichwort... :grinning_face_with_smiling_eyes:

Ich habe 4 logische cases. Die zwei bei denen keine Aktion nötig ist habe ich erstmal weggelassen. Wenn die drin sind und alle möglichen Kombinationen berücksichtigt werden müsste die If-Abfrage doch erreicht werden, oder hast Du Bedenken an einer anderen Stelle?

Kleinere Schritte sind auch ein guter Tipp.
Ich hab mir das halt mal wieder zu einfach vorgestellt. Das werde ich mal machen..

Danke und Gruß Frank

Dazu kann ich dir später nochmal ein Beispiel posten.
Muss dazu an den PC.

Edit:
Hier mein Beispielsketch:

// Variable im Deklarationsbereich
unsigned long last_ms     = 0;
unsigned int merker       = 0;


void OutputTimer()											  // aus der loop() aufrufen	
{
  int tasterValue = digitalRead (taster);
  if (!tasterValue)                                           // Start der Zeit (Led an)
  {
    last_ms = millis();
    Serial.println("Start");
    merker = 1;
    digitalWrite(led1, HIGH);
  }

  if (merker)                                                 // Merker, Ausgang ist aktiv
  {
    if (millis() - last_ms >= 10000)
    {                                                         // Ende der Zeit (Led aus)
      digitalWrite(led1, LOW);
      merker = 0;
      Serial.println("Stopp");
    }
  }
}

Tasten und Pins müssen noch definiert werden.
Nach dem Drücken der Taste bleibt die Led für 10sek an.

Evtl. geht das Ganze auch einfacher, aber damit habe ich schon einige Projekte am Laufen.

Und zu den Transistoren fällt mir noch ein, da das Ventil mit +Spannung gesteuert wird und diese über 5Volt ist, brauchst du pro Weg 2 Transistoren. 1 x PNP und 1 x NPN je Leitung.

Kannst du schon von Erfolgen mit deiner Schaltung berichten ?
Würde mich schon interessieren, wie du deine Schaltung aufgebaut hast.

Hallo,
Leider noch nicht. Ich häng grad noch am Sketch. Habs auch Dank Deiner Hilfe jetzt fast geschafft.
Im nächsten Schritt kommt dann die Verdrahtung.
Ich denke ich werde die von Dir angesprochenen Relais verwenden. Das mit den Transistoren wird mir zu komplex wenn ich da PNP und NPN kombinieren muss..
Aber ich denke Am Dienstag werde ich Neues zu Berichten haben.

Alles klar, danke für die Rückmeldung.

Hallo Zusammen,

Ich bin soweit fertig. Den code hänge ich mal an.
Ich habe jetzt noch eine StatusLED (rot=Kugelhahn geschlossen, grün=geöffnet) mit eingebaut. Soll ja nicht nur den Füllstand anzeigen...

Die Schaltung hab ich jetzt mit den oben von HotSystems empfohlenen Relaismodulen umgesetzt. (war einfacher weil unterm Strich weniger Bauteile)

jetzt hab ich noch zwei Fragen:

  1. Das ganze soll ganzjährig laufen. Was passiert mit den millis() nach 49,7x Tagen?
    Muss ich da noch was beachten?

  2. Wie kann ich den code aufräumen? Ich hab jetzt schon 77% RAM durch die Variablen belegt und will eigentlich in Stufe 2 noch ein HC-06 für die Wintersteuerung via Bluetooth einbauen... (wenn es der Nano packt)

Besten Dank Euch allen für die Hilfe bis hierher!

#include <Wire.h>                                             //fuer I2C Kommunikation
#include <Adafruit_GFX.h>                                     //fuer OLED
#include <Adafruit_SH1106.h>                                  //fuer OLED
#include <NewPing.h>                                          //fuer Ultraschall-Sensor

#define LEVELFULL 100                                         //Hoehe Wassersaeule wenn voll (=100%) in cm
#define LEVELEMPTY 100                                        //Abstand Boden zu Sensor in cm
#define VOLUME 2000                                           //Volumen des zu messenden Mediums bei 100% in l
#define OLED_RESET -1                                         //Reset pin beim SH1106 nicht vorhanden
#define trigPin  2                                            //Trigger Pin
#define echoPin  3                                            //Echo Pin
#define MAX_DISTANCE 320                                      //Begrenzung fuer Ultraschallsensor
#define NUMFLAKES 10
#define XPOS 0
#define YPOS 1
#define DELTAY 2

Adafruit_SH1106 display(OLED_RESET);

//globale Variablen & Konstanten

const int schaltdauer = 6000;                                 //später 6 Sekunden Dauer für Vorgang Oeffnen oder Schliessen d. Kugelhahns
bool statusClosed;                                            //aktueller Status Kugelhahn geschlossen
bool Vorgang;                                                 //Kugelhahn öffnet oder schließt gerade
int Zustand;                                                  //var fuer switch-case

NewPing sonar(trigPin, echoPin, MAX_DISTANCE);                // NewPing Setup



void setup() {
  Serial.begin(9600);                                         // nur fuer Debugzwecke
  pinMode(8, OUTPUT);                                         // fuer rotes Kabel Kugelhahn (schliessen)
  pinMode(7, OUTPUT);                                         // fuer blaues Kabel Kugelhahn (oeffnen)
  pinMode(9, OUTPUT);                                        //StatusLED Offen
  pinMode(10, OUTPUT);                                         //StatusLED Geschlossen
  display.begin(SH1106_SWITCHCAPVCC, 0x3C);
  statusClosed = true;
}

void RoteLED() {
  static unsigned long start1 = 0;                           //Startzeit fürs Schließen
  static unsigned long merker1;

  if (statusClosed == false && Vorgang == false)   {          //Bedingungen zum Schließen
    start1 = millis();
    merker1 = 1;
    digitalWrite(8, HIGH);
    Vorgang = true;
    Serial.println(merker1);
  }

  if (merker1)  {                                             // Merker, Ausgang ist aktiv
    if (millis() > start1 + schaltdauer) {                    //Ende der Schaltzeit schließen
      digitalWrite(8, LOW);
      merker1 = 0;
      Vorgang = false;
    }
  }
}


void GrueneLED() {
  static unsigned long start2 = 0;                            //Startzeit fürs Schließen
  static unsigned long merker2;

  if (statusClosed == true && Vorgang == false)  {            //Bedingungen zum Öffnen
    start2 = millis();
    merker2 = 1;
    digitalWrite(7, HIGH);
    Vorgang = true;
  }

  if (merker2)  {                                             // Merker, Ausgang ist aktiv

    if (millis() > start2 + schaltdauer) {
      // Ende der Zeit (Led aus)
      digitalWrite(7, LOW);
      merker2 = 0;
      Vorgang = false;

    }
  }
}


void loop() {

//Variablen für Loop

  float duration;
  int lefty, distance, levelact, levelpercent, volumeact;
  bool levelFull;                                           //Grenzwert zum Auslösen des Schaltvorgangs
  bool levelFull_new;                                       //zur Berechnung des ChangeStateDetection levelFull
  bool chngLevel;                                           //Veränderung zum bisherigen Füllstand

  delay(50); // Wartezeit zwischen Pings (ca. 20 pings/sec). nicht kleiner als 29ms!

//Messung mit Mittelung
  int iterations = 5;
  duration = sonar.ping_median(iterations);                 //duration = sonar.ping() ohne Mittelung
  distance = int((duration / 2) * 0.0343);                  //Abstand in cm

//Fuellstand berechnen
  levelact = (LEVELEMPTY - distance);

//Fuellstand in Prozent berechnen
  levelpercent = (levelact * 100 / LEVELFULL);

//Berechnung Volumen
  volumeact = ((VOLUME / 100) * levelpercent);

////Steuerung Kugelhahn


  if (levelpercent >= 95) {                                  //um nur wahr oder falsch als Bedingung zu haben
    levelFull = true;
  }
  else {
    levelFull = false;
  }

  if (Vorgang != true) {                                      //wenn derKugelhahn gerade öffnet od. schließt darf nix passieren
    if (levelFull != levelFull_new) {                         //ChangeState Detection für den Füllstand
      chngLevel = true;
      levelFull_new = levelFull;
    }
  }

  else {
    delay (schaltdauer);                                      //wartet auf Beendigung des Vorgangs öffnen/schließen
  }


  if (chngLevel == true && levelFull == false && statusClosed == true) {     //zur Definitionen der Cases
    Zustand = 1;
  }

  if (chngLevel == true && levelFull == true && statusClosed == false) {
    Zustand = 2;
  }

  if (levelFull == false && chngLevel == false) {
    Zustand = 3;
  }
  if (levelFull == true &&  chngLevel == false) {
    Zustand = 4;
  }


  switch (Zustand) {
    case 1:
      GrueneLED();
      statusClosed = false;
    break;
      
    case 2:
      RoteLED();
      statusClosed = true;
    break;

    case 3:
    break;

    case 4:
    break;
  }
  
  delay(2);                                                     //zur Sicherheit

if (statusClosed == true){
  digitalWrite(10, HIGH);
  digitalWrite(9, LOW);}

if (statusClosed != true){
  digitalWrite(9, HIGH);
  digitalWrite(10, LOW);}
  

  //Ausgabe Display
  display.clearDisplay(); // Clear display buffer

  //Ueberschrift
  display.setTextSize(2);      // Schriftgroesse
  display.setTextColor(WHITE); // Textfarbe
  display.setCursor(0, 0);     // obere linke Ecke
  display.print("Levelmeter");

  //Fuellstand in Prozent
  display.setTextSize(2);      // Schriftgroesse
  display.setTextColor(WHITE); // Textfarbe
  display.setCursor(50, 24);   // obere linke Ecke
  display.print(levelpercent);
  display.print(" %");

  //Fuellstand in Litern
  display.setTextSize(2);
  display.setCursor(50, 48);
  display.print(volumeact);
  display.print(" l");

  //Balkenanzeige
  levelpercent = unsigned constrain(levelpercent, 0, 100);  //um nur Werte zwischen 0 und 100 zu erhalten
  lefty = map(levelpercent, 0, 100, 63, 16);       //Hoehe des Balkens ermitteln

  //aeusserer Rahmen zeichnen
  display.drawRect(0, 16, 38, 48, WHITE);

  //Balken zeichnen
  display.fillRect(0, lefty, 38, 48, WHITE);

  //Display anzeigen
  display.display();
}

Ich vermute mal es geht viel für die Bibliotheken drauf, die du benutzt.

Als Anfänger(Ich) erstmal Serial.begin() und case 3+4

raus wird ja nicht benutzt

Mit millis() mach dir keine Sorgen, Du benutzt zum jedem Startpunkt immer "neue" millis() und damit ist egal zum welchen Zeitpunkt der Überlauf stat findet

nur Teste machen Erfolg

Falsch!!

Siehe:

Das kracht im Gebälk, um die Zeit des Überlaufes herum.

Denn es macht einen 2ten Überlauf, wo eigentlich ein Unterlauf zur Kompensation des millis() Überlaufs zu finden sein sollte.
blink without delay, zeigt wie es richtig geht.

Warum verwendest du nicht die Anweisung, so wie ich es im Beispiel gezeigt habe ?

So handelst du dir Probleme ein.

Die Adafruit-Bibliothek ist leider nicht resourcenschonend programmiert, weshalb ich gerne auf U8g2 von @olikraus ausweiche. Der hat bei der Konzeption gleich den geringen Speicher der Arduinos berücksichtigt. Leider gibt es immer den Spagat zwischen "einfach" und "kann viel" und U8g2 tendiert eher zu letzterer Möglichkeit. Aber die Mühe lohnt:

Der Sketch verwendet 13206 Bytes (40%) des Programmspeicherplatzes. Das Maximum sind 32256 Bytes.
Globale Variablen verwenden 876 Bytes (42%) des dynamischen Speichers, 1172 Bytes für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes.

grafik

#include <Wire.h>                                             //fuer I2C Kommunikation
#include "U8g2lib.h"
#include <NewPing.h>                                          //fuer Ultraschall-Sensor

#define LEVELFULL 100                                         //Hoehe Wassersaeule wenn voll (=100%) in cm
#define LEVELEMPTY 100                                        //Abstand Boden zu Sensor in cm
#define VOLUME 2000                                           //Volumen des zu messenden Mediums bei 100% in l
#define OLED_RESET -1                                         //Reset pin beim SH1106 nicht vorhanden
#define trigPin  2                                            //Trigger Pin
#define echoPin  3                                            //Echo Pin
#define MAX_DISTANCE 320                                      //Begrenzung fuer Ultraschallsensor
#define NUMFLAKES 10
#define XPOS 0
#define YPOS 1
#define DELTAY 2

U8G2_SH1106_128X64_NONAME_1_HW_I2C display(U8G2_R0);

//globale Variablen & Konstanten

const int schaltdauer = 6000;                                 //später 6 Sekunden Dauer für Vorgang Oeffnen oder Schliessen d. Kugelhahns
bool statusClosed;                                            //aktueller Status Kugelhahn geschlossen
bool Vorgang;                                                 //Kugelhahn öffnet oder schließt gerade
byte Zustand;                                                 //var fuer switch-case

NewPing sonar(trigPin, echoPin, MAX_DISTANCE);                // NewPing Setup



void setup() {
  Serial.begin(9600);                                         // nur fuer Debugzwecke
  pinMode(8, OUTPUT);                                         // fuer rotes Kabel Kugelhahn (schliessen)
  pinMode(7, OUTPUT);                                         // fuer blaues Kabel Kugelhahn (oeffnen)
  pinMode(9, OUTPUT);                                        //StatusLED Offen
  pinMode(10, OUTPUT);                                         //StatusLED Geschlossen
  display.begin();
  statusClosed = true;
}

void RoteLED() {
  static unsigned long start1 = 0;                           //Startzeit fürs Schließen
  static unsigned long merker1;

  if (statusClosed == false && Vorgang == false)   {          //Bedingungen zum Schließen
    start1 = millis();
    merker1 = 1;
    digitalWrite(8, HIGH);
    Vorgang = true;
    Serial.println(merker1);
  }

  if (merker1)  {                                             // Merker, Ausgang ist aktiv
    if (millis() > start1 + schaltdauer) {                    //Ende der Schaltzeit schließen
      digitalWrite(8, LOW);
      merker1 = 0;
      Vorgang = false;
    }
  }
}


void GrueneLED() {
  static unsigned long start2 = 0;                            //Startzeit fürs Schließen
  static unsigned long merker2;

  if (statusClosed == true && Vorgang == false)  {            //Bedingungen zum Öffnen
    start2 = millis();
    merker2 = 1;
    digitalWrite(7, HIGH);
    Vorgang = true;
  }

  if (merker2)  {                                             // Merker, Ausgang ist aktiv

    if (millis() > start2 + schaltdauer) {
      // Ende der Zeit (Led aus)
      digitalWrite(7, LOW);
      merker2 = 0;
      Vorgang = false;

    }
  }
}


void loop() {

  //Variablen für Loop

  float duration;
  int lefty, distance, levelact, levelpercent, volumeact;
  bool levelFull;                                           //Grenzwert zum Auslösen des Schaltvorgangs
  bool levelFull_new;                                       //zur Berechnung des ChangeStateDetection levelFull
  bool chngLevel;                                           //Veränderung zum bisherigen Füllstand

  delay(50); // Wartezeit zwischen Pings (ca. 20 pings/sec). nicht kleiner als 29ms!

  //Messung mit Mittelung
  int iterations = 5;
  duration = sonar.ping_median(iterations);                 //duration = sonar.ping() ohne Mittelung
  distance = int((duration / 2) * 0.0343);                  //Abstand in cm

  //Fuellstand berechnen
  levelact = (LEVELEMPTY - distance);

  //Fuellstand in Prozent berechnen
  levelpercent = (levelact * 100 / LEVELFULL);

  //Berechnung Volumen
  volumeact = ((VOLUME / 100) * levelpercent);

  ////Steuerung Kugelhahn


  if (levelpercent >= 95) {                                  //um nur wahr oder falsch als Bedingung zu haben
    levelFull = true;
  }
  else {
    levelFull = false;
  }

  if (Vorgang != true) {                                      //wenn derKugelhahn gerade öffnet od. schließt darf nix passieren
    if (levelFull != levelFull_new) {                         //ChangeState Detection für den Füllstand
      chngLevel = true;
      levelFull_new = levelFull;
    }
  }

  else {
    delay (schaltdauer);                                      //wartet auf Beendigung des Vorgangs öffnen/schließen
  }


  if (chngLevel == true && levelFull == false && statusClosed == true) {     //zur Definitionen der Cases
    Zustand = 1;
  }

  if (chngLevel == true && levelFull == true && statusClosed == false) {
    Zustand = 2;
  }

  if (levelFull == false && chngLevel == false) {
    Zustand = 3;
  }
  if (levelFull == true &&  chngLevel == false) {
    Zustand = 4;
  }


  switch (Zustand) {
    case 1:
      GrueneLED();
      statusClosed = false;
      break;

    case 2:
      RoteLED();
      statusClosed = true;
      break;

    case 3:
      break;

    case 4:
      break;
  }

  delay(2);                                                     //zur Sicherheit

  if (statusClosed == true) {
    digitalWrite(10, HIGH);
    digitalWrite(9, LOW);
  }

  if (statusClosed != true) {
    digitalWrite(9, HIGH);
    digitalWrite(10, LOW);
  }


  //Balkenanzeige
  levelpercent = unsigned constrain(levelpercent, 0, 100);  //um nur Werte zwischen 0 und 100 zu erhalten
  lefty = map(levelpercent, 0, 100, 63, 16);       //Hoehe des Balkens ermitteln

  //Ausgabe Display
  display.setFont(u8g2_font_logisoso16_tr);
  display.clearBuffer();
  display.firstPage();
  do {
    display.drawStr(0, 15, "Levelmeter");
    display.setCursor(50, 40);
    display.print(levelpercent);
    display.print(" %");
    display.setCursor(50, 60);
    display.print(volumeact);
    display.print(" l");
    display.drawFrame(0, 16, 38, 48);
    display.drawBox(0, lefty, 38, 48);
  } while ( display.nextPage() );
}

Zeitaufwändig ist die Auswahl eines schönen Fonts, es gibt einfach zu viele im Angebot. Bitte beachte U8g2 Font Names.

Da denke ich an ESP32, weil mehr Speicher und Funk verschiedenster Art eingebaut. Der kann Dir eine HTML-Seite zur Konfiguration im Browser anzeigen. Kein Anfängerniveau, aber der Winter ist lang, testen würde ich allerdings im Warmen.
:snowman_with_snow:

Tja, gute Frage. Das hatte ich anfangs. Beim Testen ist das wohl wieder verloren gegangen... Code bereits wieder geändert. Danke

Danke für den Tipp. Aber so wie ich das verstanden habe kommt die Auslastung des RAM vorallem durch global definierte Variablen. Das Bisschen Code ändert da nicht viel, zumal ich die cases in V2 dann brauche.

Danke agmue, das werde ich probieren. Wird aber vermutlich gerade etwas dauern bis alles umgesetzt ist. Und wenn es soweit ist und die Einsparung wirklich so groß ist, dann teste ich erstmal den HC-06 (hab schon zwei hier rumliegen) und anschließend den ESP32. Will eh demnächst mit WeMos spielen... und mit 433MHz... und.... und... und...

Der Winter naht!

Ich danke Euch nochmal für die Tipps und Anregungen. jetzt komm ich wieder weiter.