{"id":1496,"date":"2023-02-26T12:23:33","date_gmt":"2023-02-26T11:23:33","guid":{"rendered":"https:\/\/lobo.uber.space\/wp\/?p=1496"},"modified":"2023-02-26T12:51:29","modified_gmt":"2023-02-26T11:51:29","slug":"automatische-bewaesserung","status":"publish","type":"post","link":"https:\/\/lobo.uber.space\/wp\/?p=1496","title":{"rendered":"Automatische Bew\u00e4sserung"},"content":{"rendered":"\n<p>Nachdem ich nun die Bodenfeuchtigkeit meiner Topfpflanzen messen kann, m\u00f6chte ich nat\u00fcrlich auch die Bew\u00e4sserung automatisieren.<\/p>\n\n\n\n<p>Ein wichtige Vor\u00fcberlegung dazu ist, dass dieses Projekt potentiell f\u00fcr eine \u00dcberschwemmung sorgen kann. Dem m\u00f6chte ich mit verschiedenen Sicherheitsvorkehrungen entgegenwirken:<\/p>\n\n\n\n<ul>\n<li>Nicht zu oft gie\u00dfen.<br>Egal, was die Feuchtigkeitssensoren sagen, die Pflanzen sollen nicht h\u00e4ufiger als alle zwei Tage gegossen werden. Diesen Mechanismus werde ich im Skript in ioBroker realisieren, welches das Signal zum Gie\u00dfen der Pflanzen erzeugt.<\/li>\n\n\n\n<li>Die Dauer der Pumpenansteuerung limitieren.<br>Wenn der ioBroker das Signal zum Gie\u00dfen einer Pflanze gibt, soll die Pumpe auf jeden Fall wieder ausgeschaltet werden, auch wenn der ioBroker zwischendurch abst\u00fcrzen sollte oder das WLAN ausf\u00e4llt. D.h. auf dem steuernden ESP8266 wird eine Logik implementiert, die nach einer vorgegebenen Zeit die Pumpe wieder abschaltet.<\/li>\n\n\n\n<li>Die Logik muss so konzipiert sein, dass die Pumpen nicht anspringen wenn der ESP8266 startet. D.h. ESPHome wird so eingestellt, dass die entsprechenden Ausg\u00e4nge beim Starten auf Low gezogen werden. Solange die GPIOs als Eingang konfiguriert sind darf die Pumpe nicht laufen.<\/li>\n\n\n\n<li>Die Durchflussmenge sollte reduziert werden k\u00f6nnen, damit die T\u00f6pfe nicht \u00fcberlaufen. Es darf also nicht schneller Wasser in den Topf gepumpt werden als in der Erde versickern kann.<\/li>\n<\/ul>\n\n\n\n<p>Urspr\u00fcnglich wollte ich zur Ansteuerung der Pumpen ein Relais-Modul verwenden. Die Durchflussmenge h\u00e4tte ich in diesem Fall \u00fcber ein Ventil reguliert. Das Relais-Modul habe ich bereits besorgt und auch ein Ventil habe ich bereits konstruiert und gedruckt.<br>Den Plan habe ich dann jedoch \u00fcber den Haufen geworfen und die Ansteuerung der Pumpen \u00fcber einen Transistor realisiert. Dadurch ist die Durchflussmenge \u00fcber PWM-Steuerung der Pumpen m\u00f6glich, au\u00dferdem ist die Schaltung dadurch g\u00fcnstiger und hat einen geringeren Stromverbrauch. Da die Stromversorgung bei mir nicht \u00fcber Batterien sondern \u00fcber Netzteil erfolgen soll, ist der Verbrauch zwar nicht so entscheidend und die ca. 50 mA zus\u00e4tzlich f\u00fcr das Relais w\u00e4hrend der Aktivierung der Pumpen w\u00e4ren auch gut zu verkraften, aber nett ist das nat\u00fcrlich trotzdem.<\/p>\n\n\n\n<p>Als Transistor habe ich einen S8050 gew\u00e4hlt, da dieser mit 500mA Collector-Strom (I<sub>C<\/sub> &#8211; angegeben bei Weitron) auf jeden Fall genug Leistung hat um die Motoren anzutreiben. Vermutlich h\u00e4tte auch ein schw\u00e4cherer Transistor gen\u00fcgt, da die Motoren mit max. 200mA bei 5V angegeben sind. Da Motoren jedoch eine induktive Last sind und damit der Einschaltstrom etwas h\u00f6her ausf\u00e4llt, habe ich lieber zu einem etwas zu hoch dimensionierten Bauteil gegriffen.<br>F\u00fcr den Transistor ist eine Base-Emitter Saturation Voltage (V<sub>BE<\/sub>) von 1,2V angegeben. Damit ergibt sich, bei einer Spannung von 3,3V und einer maximalen Belastung von 12mA am GPIO des ESP8266 ein Basis-Vorwiderstand von mindestens (3,3V-1,2V)\/0,012A=175 Ohm. Hier habe ich einen Widerstand von 220 Ohm (5%) gew\u00e4hlt, um den Ausgang nicht zu hoch zu belasten. Der Basis-Strom wird damit bei 9,1 &#8211; 11 mA liegen (gerechnet f\u00fcr V<sub>BE<\/sub> von 1,0 &#8211; 1,2V (1,0V angegeben bei UTC f\u00fcr I<sub>C<\/sub> von 10mA) und einem tats\u00e4chlichen Widerstandswert von 209 &#8211; 231 Ohm). Das reicht in jedem Fall um den Transistor bei einem Collector-Strom von max. 200mA voll durchzuschalten (niedrigster Verst\u00e4rkungsfaktor h<sub>FE<\/sub> ist angegeben bei Weitron mit 85).<br>Zus\u00e4tzlich wird noch eine <a rel=\"noreferrer noopener\" href=\"https:\/\/www.youtube.com\/watch?v=wXqEVLcNF8E\" target=\"_blank\">Freilaufdiode zum Schutz<\/a> des Transistors ben\u00f6tigt.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"1024\" height=\"671\" src=\"https:\/\/lobo.uber.space\/wp\/wp-content\/uploads\/2022\/07\/grafik-1024x671.png\" alt=\"\" class=\"wp-image-1499\" srcset=\"https:\/\/lobo.uber.space\/wp\/wp-content\/uploads\/2022\/07\/grafik-1024x671.png 1024w, https:\/\/lobo.uber.space\/wp\/wp-content\/uploads\/2022\/07\/grafik-300x197.png 300w, https:\/\/lobo.uber.space\/wp\/wp-content\/uploads\/2022\/07\/grafik-768x504.png 768w, https:\/\/lobo.uber.space\/wp\/wp-content\/uploads\/2022\/07\/grafik.png 1304w\" sizes=\"(max-width: 706px) 89vw, (max-width: 767px) 82vw, 740px\" \/><figcaption class=\"wp-element-caption\">Schaltplan f\u00fcr die Ansteuerung von zwei Pumpen (M1 und M2)<\/figcaption><\/figure>\n\n\n\n<p>Bei ESPHome konnte ich dieses Mal leider nicht die API verwenden um die entsprechenden Elemente direkt im ESPHome-Adapter zur Verf\u00fcgung zu haben, da der Adapter zur Zeit <em>number<\/em> noch nicht akzeptiert. Die Kommunikation muss deshalb \u00fcber MQTT laufen. Dabei ist es ein wenig l\u00e4stig, dass lesen und setzen eines Wertes \u00fcber zwei unterschiedliche Topics laufen muss. Im ioBroker habe ich mir damit beholfen, zu den zwei Topics einen Alias zu definieren und dabei unterschiedliche Datenpunkte f\u00fcr Lesen und Schreiben zu definieren.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">esphome:\n  name: pflanzenwasser\n\nesp8266:\n  board: d1_mini\n\nwifi:\n  ssid: !secret wifi_ssid\n  password: !secret wifi_password\n\n  # Enable fallback hotspot (captive portal) in case wifi connection fails\n  ap:\n    ssid: !secret ap_ssid\n    password: !secret ap_password\n\nota:\n  password: !secret ota_password\n\n# Enable logging\nlogger:\n\n# Enable Home Assistant API\n#api:\n#  password: !secret api_password\n#  encryption:\n#    key: !secret api_key\n\nmqtt:\n  broker: 192.168.1.2\n  username: username\n  topic_prefix: pflanzenwasser\n\ncaptive_portal:\n\nweb_server:\n  port: 80\n\nsensor:\n  - platform: wifi_signal\n    name: \"Wifi Power\"\n    update_interval: 10s\n    state_topic: pflanzenwasser\/wifi_power\n\nnumber:\n  - platform: template\n    name: \"Dauer1\"\n    id: dauer1\n    unit_of_measurement: ms\n    optimistic: true\n    min_value: 0\n    max_value: 30000\n    step: 1\n    restore_value: true\n    initial_value: 3500\n    state_topic: pflanzenwasser\/dauer1\n    command_topic: pflanzenwasser\/dauer1-set\n\n  - platform: template\n    name: \"Dauer2\"\n    id: dauer2\n    unit_of_measurement: ms\n    optimistic: true\n    min_value: 0\n    max_value: 30000\n    step: 1\n    restore_value: true\n    initial_value: 3500\n    state_topic: pflanzenwasser\/dauer2\n    command_topic: pflanzenwasser\/dauer2-set\n\noutput:\n  - platform: esp8266_pwm\n    pin: D1\n    frequency: 200 Hz\n    id: wasser1_pwm\n    min_power: 0.3\n    max_power: 1.0\n    zero_means_zero: true\n\n  - platform: esp8266_pwm\n    pin: D2\n    frequency: 200 Hz\n    id: wasser2_pwm\n    min_power: 0.3\n    max_power: 1.0\n    zero_means_zero: true\n\nfan:\n  - platform: speed\n    id: wasser1\n    output: wasser1_pwm\n    name: \"Wasser1\"\n    on_turn_on:\n      - delay: !lambda |-\n          return id(dauer1).state;\n      - fan.turn_off: wasser1\n\n  - platform: speed\n    id: wasser2\n    output: wasser2_pwm\n    name: \"Wasser2\"\n    on_turn_on:\n      - delay: !lambda |-\n          return id(dauer2).state;\n      - fan.turn_off: wasser2<\/pre>\n\n\n\n<p>Jetzt fehlt nur noch das Script im ioBroker, mit dem dann die Pumpen aktiviert werden. Hier mit einem sch\u00f6nen Gemisch aus Deutsch und Englisch:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted has-medium-font-size\">var cacheSelectorBodenfeuchte\n   = $('state[id=*](functions=\"Bodenfeuchte\")');\nlet warning_timer = null;\nconst warning_timeout = 240; \/\/ Minuten\nconst watering = true;\n\ncacheSelectorBodenfeuchte.on (checkAllFeuchtigkeit);\n\ncheckAllFeuchtigkeit (null);\n\n\/\/cacheSelectorBodenfeuchte.each (function(id, i) {\n\/\/    console.log (\"id: \" + JSON.stringify (id));\n\/\/    console.log (\"i: \" + JSON.stringify (i));\n\/\/});\n\nfunction holePflanzen () {\n    const obj\n      = getObject(\"enum.functions.Bodenfeuchte\");\n    \n    if (obj === null)\n        return [];\n\n    let pflanzen = [];\n\n    for (let i in obj.common.members) {\n        const c\n          = getObject (obj.common.members[i]).common;\n        let warn_low = 40;\n        let warn_high = 50;\n\n        if (\"warning_range\" in c) {\n            \/\/console.log\n            \/\/  (\"Warning range: \"\n            \/\/   + JSON.stringify (c.warning_range));\n            warn_low = c.warning_range[0];\n            warn_high = c.warning_range[1];\n        }\n\n        pflanzen.push\n          ({ id: obj.common.members[i],\n             warn_low: warn_low,\n             warn_high: warn_high });\n    }\n    \n    return pflanzen;\n}\n\nasync function checkAllFeuchtigkeit (obj) {\n    let r = {finish: false, enough_water: true };\n\n    let pflanzen = holePflanzen ();\n\n    let trockene_pflanzen = [];\n    let trockene_pflanzen_ids = [];\n\n    for (let i in pflanzen) {\n        r = checkFeuchtigkeit\n              (pflanzen[i], r.enough_water);\n\n        if (r.finish) {\n            trockene_pflanzen.push\n              (pflanzen[i].id.replace (\/^.*\\.([^.]+)$\/,\n                                       \"$1\"));\n            trockene_pflanzen_ids.push (pflanzen[i].id);\n        }\n    }\n\n    if (trockene_pflanzen.length > 0) {\n        if (watering)\n            waterPlants (trockene_pflanzen_ids);\n\n        if (warning_timer === null) {\n            warning_timer\n              = setTimeout (async function () {\n                    warning_timer = null;\n                  }, warning_timeout * 60 * 1000);\n            \n            let msg = 'Pflanzen ben\u00f6tigen Wasser: ';\n            for (let i=0;\n                 i &lt; trockene_pflanzen.length; ++i) {\n                if (i > 0)\n                    msg += ', ';\n\n                msg\n                  += trockene_pflanzen[i] + ' ('\n                     + String (getState(\n                         trockene_pflanzen_ids[i]).val)\n                     + '%)';\n            }\n            sendTo (\"telegram\", \"send\",\n                    {text: msg,\n                     disable_notification: true});\n        }\n        return;\n    }\n    \n    if (r.enough_water)\n        setState\n          (\"0_userdata.0.Warnings.Pflanze_ohne_Wasser\",\n           false, true);\n}\n\nfunction checkFeuchtigkeit (pflanze,\n                            previous_enough_water) {\n    const f = getState (pflanze.id).val;\n    let finish = false;\n\n    if (f &lt; pflanze.warn_low) {\n        setState\n          (\"0_userdata.0.Warnings.Pflanze_ohne_Wasser\",\n           true, true);\n        finish = true;\n    }\n\n    return { finish: finish,\n             enough_water: previous_enough_water\n                           &amp;&amp; f > pflanze.warn_high };\n}\n\nvar warning_timer_not_watering = null;\n\nfunction msToReadable (time) {\n    if (time &lt; 5)\n        return String (time \/ 1000) + \" s\";\n    \n    let s = Math.round (time \/ 1000);\n\n    if (s &lt; 60)\n        return String (s) + \" s\";\n\n    let m = Math.floor (s \/ 60);\n    s -= m * 60;\n\n    if (m &lt; 5)\n        return String (m) + \" min, \"\n               + String (s) + \" s\";\n\n    if (m &lt; 60)\n        return String (m) + \" min\";\n\n    let h = Math.floor (m \/ 60);\n    m -= h * 60;\n\n    if (h &lt; 5)\n        return String (h) + \":\"\n               + String (m) + \" Stunden\";\n\n    if (h &lt; 24)\n        return String (h) + \" Stunden\";\n\n    let d = Math.floor (h \/ 24);\n    h -= d * 24;\n\n    if (d &lt; 5)\n        return String (d) + \" Tage, \"\n               + String (h) + \" Stunden\";\n\n    if (d &lt; 14)\n        return String (d) + \" Tage\";\n\n    let w = Math.floor (d \/ 7);\n    d -= w * 7;\n\n    if (w &lt; 5)\n        return String (w) + \" Wochen, \"\n               + String (d) + \" Tage\";\n\n    return String (w) + \" Wochen\";\n}\n\nfunction getWasserIdFromPflanzenId (pflanzen_id) {\n  switch (trockene_pflanzen_ids[i]) {\n    case\n      \"alias.0.Wohnzimmer.Bodenfeuchte_Bitterorange\":\n      return \"alias.0.Wohnzimmer.Pflanzenbew\u00e4sserung\"\n             + \".Bitterorange.state\";\n      break;\n    case \"alias.0.Wohnzimmer.Bodenfeuchte_Schefflera\":\n      return \"alias.0.Wohnzimmer.Pflanzenbew\u00e4sserung\"\n             + \".Schefflera.state\";\n      break;\n  }\n  return null;\n}\n\nfunction waterPlants (trockene_pflanzen_ids) {\n  for (let i in trockene_pflanzen_ids) {\n    let wasser_id\n      = getWasserIdFromPflanzenId\n          (trockene_pflanzen_ids[i]);\n\n    if (wasser_id !== null) {\n      let s = getState (wasser_id);\n      let last = Date.now () - s.lc;\n\n      let msg = null;\n      if (last > 5 * 24 * 60 * 60 * 1000) {\n        let szene = getState (SZENE_ID).val;\n        if (getState\n              (\"0_userdata.0.Presence.Gast_anwesend\")\n              .val\n            &amp;&amp; (!compareTime (\"10:00\", \"20:00\",\n                              \"between\")\n                || szene === SZENE_NACHT\n                || szene === SZENE_FILM)) {\n          msg = \"Wasser f\u00fcr \" + wasser_id\n                + \" nicht aktiviert wegen Gast\"\n                + \" anwesend, Feuchte: \"\n                + String (getState\n                    (trockene_pflanzen_ids[i]).val)\n                + \"%\";\n        } else {\n          msg = \"Wasser f\u00fcr \" + wasser_id\n                + \" aktiviert, Feuchte: \"\n                + String (getState\n                    (trockene_pflanzen_ids[i]).val)\n                + \"%\";\n          setState (wasser_id, true);\n        }\n      } else {\n        if (warning_timer_not_watering === null) {\n          warning_timer_not_watering\n          = setTimeout (async function () {\n              warning_timer_not_watering = null;\n             }, warning_timeout * 60 * 1000);\n          msg = \"Wasser f\u00fcr \" + wasser_id\n                + \" nicht aktiviert, Feuchte: \"\n                + String (getState\n                    (trockene_pflanzen_ids[i]).val)\n                + \"%, letzte W\u00e4sserung vor \"\n                + msToReadable (last);\n        }\n      }\n\n      if (msg !== null) {\n        console.log (msg);\n        sendTo (\"telegram\", \"send\",\n                {text: msg,\n                 disable_notification: true});\n      }\n    }\n  }\n}<\/pre>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Nachdem ich nun die Bodenfeuchtigkeit meiner Topfpflanzen messen kann, m\u00f6chte ich nat\u00fcrlich auch die Bew\u00e4sserung automatisieren. Ein wichtige Vor\u00fcberlegung dazu ist, dass dieses Projekt potentiell f\u00fcr eine \u00dcberschwemmung sorgen kann. Dem m\u00f6chte ich mit verschiedenen Sicherheitsvorkehrungen entgegenwirken: Urspr\u00fcnglich wollte ich zur Ansteuerung der Pumpen ein Relais-Modul verwenden. Die Durchflussmenge h\u00e4tte ich in diesem Fall \u00fcber &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/lobo.uber.space\/wp\/?p=1496\" class=\"more-link\"><span class=\"screen-reader-text\">\u201eAutomatische Bew\u00e4sserung\u201c<\/span> weiterlesen<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[126,119,120,110,98,112,127],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/lobo.uber.space\/wp\/index.php?rest_route=\/wp\/v2\/posts\/1496"}],"collection":[{"href":"https:\/\/lobo.uber.space\/wp\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/lobo.uber.space\/wp\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/lobo.uber.space\/wp\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/lobo.uber.space\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1496"}],"version-history":[{"count":9,"href":"https:\/\/lobo.uber.space\/wp\/index.php?rest_route=\/wp\/v2\/posts\/1496\/revisions"}],"predecessor-version":[{"id":1534,"href":"https:\/\/lobo.uber.space\/wp\/index.php?rest_route=\/wp\/v2\/posts\/1496\/revisions\/1534"}],"wp:attachment":[{"href":"https:\/\/lobo.uber.space\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1496"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/lobo.uber.space\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1496"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lobo.uber.space\/wp\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1496"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}