Forum: Mikrocontroller und Digitale Elektronik ESP 32: Smarthome Beet über MQTT: FAST fertig


You were forwarded to this site from EmbDev.net. Back to EmbDev.net
von Timo G. (unknown-user)


Angehängte Dateien:

Lesenswert?

Ich grüße Euch,

ich programmiere gerade meinen ersten µc. Ich habe ein Hochbeet, welches 
automatisch gewässert, gelüftet und beleuchtet werden soll. Die 
Messdaten dafür kommen von einem Xiaomi MiFlora Sensor. Die Auswertung 
läuft über einen smarthomeng-Server, der mit dem ESP32 im Beet über MQTT 
kommuniziert. Ausgangsbasis war dieser Code: 
https://github.com/e6on/ESP32_MiFlora_MQTT
Jetzt funktioniert fast alles, für die verbliebenen Probleme erhoffe ich 
mir hier Hilfe :)

Den gesamten Code habe ich als Textfile beigefügt.

Problem 1:
Der Eingang für den Schwimmer des Wassertanks soll sofort übertragen 
werden. Der Interrupt macht aber nichts, heißt er wird einfach 
ignoriert. Es könnte auch ein physischen Problem mit dem Schalter sein, 
das überprüfe ich am WE
1
void setupPins() {
2
  for (int i = 0; i < sizeof(ausgangspins) / sizeof(ausgangspins[0]); i++) {
3
    pinMode(ausgangspins[i], OUTPUT);
4
    digitalWrite(ausgangspins[i], LOW);
5
  }
6
7
  pinMode(waterSensorPin, INPUT); // Eingangspin für Wasserstandssensor
8
  attachInterrupt(digitalPinToInterrupt(waterSensorPin), sendWaterLevel, CHANGE);
9
}
1
void sendWaterLevel() {
2
  // Überprüfe den Wasserstand
3
  Serial.println ("-- read Water Level");
4
  bool wasserstand = digitalRead(waterSensorPin);
5
  Serial.print("Level is ");
6
  if (wasserstand) {
7
    Serial.println("HIGH");
8
  } else {
9
    Serial.println("LOW");
10
  }
11
  client.publish((MQTT_BASE_TOPIC + "/wasser").c_str(), wasserstand ? "1" : "0");
12
  Serial.println(" >> published");
13
14
}


Problem 2:
Was kann ich in meinem loop optimieren hinsichtlich:
- mehr Energie-Effizienz, also weniger idle Rechenleistung während ich 
auf MQTT Nachrichten oder Interrupts vom Input warte
- Selbstüberwachung: Der µc soll im Fehlerfall das Problem erstmal 
versuchen durch einen Neustart zu lösen. Frage: Sind regelmäßige 
geplante Reboots sinnig und wie würdet ihr sie umsetzen?
1
void loop() {
2
  // check MQTT connection
3
  if (!client.connected()) {
4
    Serial.println("!Not connected >> reconnect");
5
    connectMqtt();
6
    subscribePins();
7
  } else {
8
    client.loop();
9
    if (bootCount >200000) {
10
      bootCount = 0;
11
      Serial.println("-- connection alive, connect.loop sent");
12
    } else {
13
      bootCount ++;
14
    }
15
  }
16
  //sendWaterLevel();
17
18
}

Problem 3:
Prinzipiell kommen die MQTT Nachrichten an. Nur bei "pumpe" passiert 
nichts. Wahrscheinlich nur ein Tippfehler, aber ich finde ihn partout 
nicht :(

Serial Monitor:
1
23:16:17.147 -> --Pin set HIGH; returning to main programm
2
23:16:17.147 -> Running loop
3
23:16:17.147 -> Running loop
4
23:16:22.355 -> -- connection alive, connect.loop sent
5
23:16:29.428 -> Message arrived [beet0/motor] false
6
23:16:29.428 -> topicTail: motor
7
23:16:29.428 -> Running loop
8
23:16:29.428 -> Running loop
9
23:16:29.428 -> Running loop
10
23:16:29.428 -> --Pin set LOW; returning to main programm
11
23:16:29.428 -> Running loop
12
23:16:29.428 -> Running loop
13
23:16:37.627 -> Message arrived [beet0/dir] true
14
23:16:37.627 -> topicTail: dir
15
23:16:37.627 -> Running loop
16
23:16:37.627 -> Running loop
17
23:16:37.627 -> --Pin set HIGH; returning to main programm
18
23:16:37.627 -> Running loop
19
23:16:37.627 -> Running loop
20
23:16:37.627 -> Running loop
21
23:16:38.940 -> Message arrived [beet0/dir] false
22
23:16:38.940 -> topicTail: dir
23
23:16:38.940 -> Running loop
24
23:16:38.940 -> Running loop
25
23:16:38.940 -> --Pin set LOW; returning to main programm
26
23:16:38.940 -> Running loop
27
23:16:38.940 -> Running loop
28
23:16:38.940 -> Running loop
29
23:16:40.364 -> Message arrived [beet0/dir] true
30
23:16:40.364 -> topicTail: dir
31
23:16:40.364 -> Running loop

Und die Abo-funktion:
1
void subscribePins() {
2
  Serial.println("-- Subscribe to properties");
3
  client.setCallback(callback);
4
5
  for (int i = 0; i < sizeof(properties) / sizeof(properties[0]); i++) {
6
    if (client.connected()) {
7
      String topic = String(MQTT_BASE_TOPIC) + "/" + String(properties[i]);
8
      if (client.subscribe(topic.c_str())) {
9
        Serial.println("Subscribed to topic: " + topic);
10
      } else {
11
        Serial.println("Failed to subscribe to topic: " + topic);
12
      }
13
    }
14
  }
15
  String topic1 = String(MQTT_BASE_TOPIC) + "/MiFloraRead";
16
  if (client.subscribe(topic1.c_str())) {
17
    Serial.println("Subscribed to topic: " + topic1);
18
  } else {
19
    Serial.println("Failed to subscribe to topic: " + topic1);
20
  }
21
  String topic2 = String(MQTT_BASE_TOPIC) + "/MiFloraBatt";
22
   if (client.subscribe(topic2.c_str())) {
23
    Serial.println("Subscribed to topic: " + topic2);
24
  } else {
25
    Serial.println("Failed to subscribe to topic: " + topic2);
26
  }
27
}

Allgemein freue ich mich sehr über Verbesserungsvorschläge, da ich 
bestimmt einiges zu krude und ungelenk gemacht habe.

Liebe Grüße Timo

von Timo G. (unknown-user)


Angehängte Dateien:

Lesenswert?

Hier noch ein Screenshot der MQTT Abos des smarthome servers

von J. S. (jojos)


Lesenswert?

Im Interrupt Kontext darf man nicht beliebige API Funktionen aufrufen, 
schon gar nicht Serial.print oder MQTT senden. Außerdem fehlt das 
Attribut das die Funktion im RAM gehalten wird.
Suche erstmal nach Interruptprogrammierung des ESP.

Sicher das der pumpe topic gesendet wird? Zum testen würde ich eher 
sowas wie MQTTExplorer verwenden.

von Timo G. (unknown-user)


Lesenswert?

Danke für deine Antwort.
Das Pumpen-Problem war tatsächlich ähnlich einfach wie angenommen: Da 
das Signal des Wasser-Sensors fehlt, gibt der smarthomeng-Server das 
Pumpenitem nicht frei. Deshalb wird nichts gesendet. Also gelöst.

Die Interruptprogrammierung werde ich mir dann mal zu Gemüte führen, mal 
sehen, ob ich selbst auf die Lösung komme :)

Im ersten Go bin ich auf folgenden Artikel gestossen: 
https://wolles-elektronikkiste.de/esp32-mit-arduino-code-programmieren#interrupts
Hier wird ein Interrupt über einen GPIO beschrieben, also eigentlich so 
wie es bei mir auch geplant ist.
Die Krux: Ich halte den Interrupt so für völlig unnütz! Die ISR setzt ja 
eine bool Variable, die dann im loop ausgelesen wird. Dann kann ich ja 
auch einfach den Input im loop auslesen und mir den Interrupt sparen, 
oder?
1
int interruptPin = 4; // define GPIO4 as interrupt pin
2
volatile bool event = false;
3
4
void IRAM_ATTR eventISR(){
5
  event = true;
6
  detachInterrupt(interruptPin);
7
}
8
9
void setup() {
10
  Serial.begin(115200);
11
  while(!Serial){}
12
  pinMode(interruptPin, INPUT_PULLDOWN);
13
  attachInterrupt(interruptPin, eventISR, RISING);
14
}
15
16
void loop() {
17
  if(event){
18
    Serial.println("Interrupt!");
19
    delay(1000); //debouncing
20
    event = false;
21
    attachInterrupt(interruptPin, eventISR, RISING);   
22
  }
23
}

Wie würdet ihr den Eingang für den Schwimmerschalter überwachen? 
Vielleicht einfach ein eigener Task, der den Sensor überwacht?

: Bearbeitet durch User
von Sascha W. (sascha-w)


Lesenswert?

Ja du kannst deinen Schwimmerschalter auch regelmäßig in der loop 
einlesen was für den Zweck vollkommen ausreicht.
Beim ESP kannst du auch keinen Strom sparen indem du in der loop weniger 
nichts machst, nicht benutzte Rechenzeit wird immer irgendwo vertrödelt 
- egal ob mit dem Code in der loop oder einem delay im Hintergrund.
Der Interrupt macht für Eingänge nur Sinn wenn das Signal so kurz 
anliegt das die Gefahr besteht das es bei entsprechender Durchlaufzeit 
der loop bis zur nächsten Abfrage schon wieder weg ist.

Sascha

von Steve van de Grens (roehrmond)


Lesenswert?

Timo G. schrieb:
> Sind regelmäßige geplante Reboots sinnig und wie würdet ihr sie
> umsetzen?

Mikrocontroller laufen normalerweise viele Jahre lang fehlerfrei durch. 
Wenn nicht, würde ich das gerne wissen um es beheben zu können. 
Regelmäßige Neustarts doktorn am Symptom herum, ohne das Problem zu 
lösen.

Der ESP8266 (vermutlich auch der ESP32) hat schon einen Watchdog, der 
den Chip automatisch neu startet, wenn er hängt. Nach meiner Erfahrung 
nützt das wenig, weil er kurz danach wieder Fehlfunktionen hat, und oft 
sogar schon beim Booten (bevor die erste Zeile eigener Code ausgeführt 
wird) stecken bleibt.

Ernsthaft Strom sparen kannst du nur, indem du WLAN deaktivierst oder 
den light sleep Modus nutzt.

: Bearbeitet durch User
von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Steve van de Grens schrieb:

> Mikrocontroller laufen normalerweise viele Jahre lang fehlerfrei durch.

So sollte es sein.

> Wenn nicht, würde ich das gerne wissen um es beheben zu können.
> Regelmäßige Neustarts doktorn am Symptom herum, ohne das Problem zu
> lösen.

Genau so ist das.

von Rolf (rolf22)


Lesenswert?

Sascha W. schrieb:
> Der Interrupt macht für Eingänge nur Sinn wenn das Signal so kurz
> anliegt das die Gefahr besteht das es bei entsprechender Durchlaufzeit
> der loop bis zur nächsten Abfrage schon wieder weg ist.

Nicht nur dann. Sinnvoll ist ein Interrupt auch dann, wenn besonders 
schnell darauf reagiert werden muss. Wenn man immer erst wartet, bis 
loop() an der Abfrage vorbei kommt, kann es für manche Anwendungen zu 
spät sein.

Im Fall des OP trifft beides nicht zu, da sollte man ohne Interrupt 
arbeiten.

von Rolf (rolf22)


Lesenswert?

Timo G. schrieb:
> Hier wird ein Interrupt über einen GPIO beschrieben, also eigentlich so
> wie es bei mir auch geplant ist.

Das ist ja nur eine Demo, die zeigen soll, wie man mit Interrupts 
umgehen kann. Sie sagt nicht, unter welchem Umständen das sinnvoll ist.

Übrigens steckt da ein "Makel" drin, den man immer wieder sieht. Wozu 
denn in der ISR den Interrupt aushängen und später wieder einhängen? Das 
führt in vielen Szenarien zu gelegentlichen Fehlern.
Was passiert z. B., wenn das nächste Ereignis erscheint, während die ISR 
gerade ausgehängt ist? Dann wird es unter Umständen nicht erkannt. 
Besser lässt man die ISR ständig eingehängt und lässt sie (oder loop) 
entscheiden, ob sie das Ereignis ignorieren will oder nicht.

Klar, man kann immer Gegenbeispiele für obige Argumente finden. Aber die 
sinnvolle Grundregel ist IMHO so.

von Rainer W. (rawi)


Lesenswert?

Timo G. schrieb:
> Wie würdet ihr den Eingang für den Schwimmerschalter überwachen?
> Vielleicht einfach ein eigener Task, der den Sensor überwacht?

Mir scheint, du bist auf den falschen Zeitskalen unterwegs.
Auch wenn ein richtige Flutwelle über dein Beet schwappt und es 
innerhalb von Bruchteilen von Sekunden flutet, nützt es dir überhaupt 
nichts, den Sensor im Mikrosekundenbereich auszuwerten. Was willst du 
dadurch veranlassen?

Timo G. schrieb:
> Der Eingang für den Schwimmer des Wassertanks soll sofort übertragen
> werden.

Was heißt in diesem Zusammenhang "sofort"?
Wie zeitkritisch ist diese Meldung?
µs[ ],  ms[ ] oder  s[ ] (zutreffendes bitte ankreuzen)

von Timo G. (unknown-user)


Lesenswert?

Vielen Dank für eure Antworten und euren Input!
Der ESP läuft, momentan noch auf einem Breadboard, am Blumenbeet. 
Bislang läuft alles wie erhofft, jetzt stehen erstmal die Restarbeit am 
Beet selbst an.

Steve van de Grens schrieb:
> Der ESP8266 (vermutlich auch der ESP32) hat schon einen Watchdog, der
> den Chip automatisch neu startet, wenn er hängt.

Den Watchdog habe ich auch schon entdeckt =) Du hast natürlich recht, er 
löst keine Code Probleme, aber er ist ja nunmal da.

Steve van de Grens schrieb:
> Ernsthaft Strom sparen kannst du nur, indem du WLAN deaktivierst oder
> den light sleep Modus nutzt.

Also WLAN ausschalten geht schonmal nicht, das Beet muss ja Befehle via 
MQTT empfangen können. sleep ist eine gute Idee, um das Beet nachts 
länger zu pausieren. Da reicht es ja, wenn es alle 1-2h aufwacht, der 
Server ggf. die Lüftung verfährt und ihn danach wieder schlafen schickt. 
Das wäre dann aber ziemlich weit unten auf der ToDo Liste.

Rainer W. schrieb:
> Mir scheint, du bist auf den falschen Zeitskalen unterwegs.
> Auch wenn ein richtige Flutwelle über dein Beet schwappt und es
> innerhalb von Bruchteilen von Sekunden flutet, nützt es dir überhaupt
> nichts, den Sensor im Mikrosekundenbereich auszuwerten. Was willst du
> dadurch veranlassen?

Ja, da hast du definitiv recht. Wenn der Sensor "leer" anzeigt, zieht 
die Pumpe ja nicht direkt Luft. Insofern:

Rainer W. schrieb:
> Was heißt in diesem Zusammenhang "sofort"?
> Wie zeitkritisch ist diese Meldung?
> µs[ ],  ms[ ] oder  s[x] (zutreffendes bitte ankreuzen)

Ich habe jetzt folgendes im loop stehen:
1
  // check if input changed
2
  if (!wasserstand == digitalRead(waterSensorPin)) {
3
    Serial.println("input changed - sending value");
4
    delay(500);
5
    wasserstand = digitalRead(waterSensorPin);
6
    MQTTsendWLL();
7
  }

Das funktioniert wie gewünscht. Das delay von 500ms soll den Sensor 
etwas entprellen, dass nicht ständig eine Änderung geschickt wird wenn 
der Schwimmer umschaltet und die Pumpe so zum stottern bringt.

von Timo G. (unknown-user)


Lesenswert?

Ich schreibe einfach mal hier weiter und mache keinen neuen Post dafür 
auf:
Kaum läuft die erste Version, habe ich schon erste Veränderungen im 
Sinn. Denn der µc muss sowieso nochmal getauscht werden. Der WLAN 
Empfang der onboard Antenne lässt zu wünschen übrig und derzeit steht 
deshalb Repeater vor der Terassentür. Also will ich auf einen Seeed Xiao 
wechseln, da ich hier eine "richtige" WLAN Antenne anschließen kann. 
Außerdem läuft alles derzeit noch auf einem Breadboard, auch kein 
Dauerzustand. Die Schaltung von Beleuchtung (12V LED Streifen, 
definitive Länge und damit Stromverbrauch steht noch nicht fest), 
Bewässerungspumpe (12V 5A Membranpumpe) und Linearmotor für die 
Beetöffnung läuft derzeit über eine fertige 4 Kanal Relais + 1 Finder 
Doppelrelais für die Polaritätsumkehr des Linearantriebes. Für die 
Zukunft möchte ich den Anschluss für eine kleine Schlauch-/Dosierumpe 
für Düngemittel vorsehen. Funktioniert alles, finde es aber irgendwie 
nicht elegant genug. Außerdem nutze ich meine Projekte ja immer um etwas 
zu lernen.

Von Euch erhoffe ich mir nun Input zu folgenden Fragen:
- Welche Art der Ansteuerung könnt ihr mir für welchen Ausgang 
empfehlen?
- Wo finde ich einen guten Artikel zum Thema "Welche Last wie schalten"?

Meine aktuelle Idee:
- 2 Kanal Motortreiber für Linearantrieb (wg. Richtungswechel) und 
Dosierpumpe (PWM zur feinen Dosierung)
- FET oder Relais für LED-Licht: FET könnte man zwar dimmen, aber 
braucht man das in diesem Anwendungsfall?
- Relais + ?Kondensator? für Bewässerungspumpe: Hier habe ich in der 
Hauptsache Sorge um den µc, da er via 7805 an der selben 12V 
Spannungsversorgung hängt. Kondensator dann im 12V Teil oder im 5V Teil?

Vielen Dank für Euren Input

von Steve van de Grens (roehrmond)


Lesenswert?

Timo G. schrieb:
> Wo finde ich einen guten Artikel zum Thema "Welche Last wie schalten"?

Mal abgesehen von speziellen Motortreibern sollte man die 
Grundschaltungen der Transistoren kennen.

Siehe http://stefanfrings.de/transistoren/index.html

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.