2026-08-15

Das Bewässerungsprojekt ist auch für eine Nutzung ohne zusätzliche Systeme konzipiert. Wer eine solche Anlage in dieser Weise einsetzen will, mag vielleicht die Wasserverbrauchsdaten ablesen und ggf. in eine Tabellenkalkulation importieren können. Hierfür ist das im folgenden dargelegte, optionale Skript geeignet, welches die Daten aufsummierend sammelt und in einer Datei lokal auf dem Master speichert. 

Inhalt

  1. Voraussetzung
  2. Einschränkung
  3. Funktionsweise
  4. Handhabung
  5. Das Skript
  6. Anmerkungen

1. Voraussetzung

Mindestens ein Assistenten zur Volumenmessung muss arbeiten. Eine virtuelle, numerische Komponente für das finale Volumen einer Bewässerung muss angelegt sein. Die Datei "collect_<Version>.js" muss auf dem Master (via Copy & Paste) installiert werden. Zusätzlich ist mindestens eine, besser zwei nichtleere Dateien zur Speicherung der gesammmelten Volumina anzulegen. Das derzeitge Skript beschreibt nur eine dieser Dateien mit den gesammelten Werten. Zukünftig sollen aber mindestens zwei Dateien beschrieben werden. 

2. Einschränkung

In der aktuellen Version werden ausschließlich Verbrauchsdaten des abgelaufenen Tages gespeichert. Das Auslösen der Speicherung erfolgt über einen Schedule Job (Zeitplan). Deshalb braucht der Master nach dem Hochfahren eine Zeitsynchonisation. Danach arbeitet die intern getaktete Firmware Uhr, solange das Shelly nicht neu gestartet wird - und dies mit hinreichender Genauigkeit. Wie der Anwender diese Synchronisation realisiert, bleibt ihm überlassen. Hierfür genügt bspw. ein Smartphone vor Ort für max. eine Minute als Hotspot, mit dem sich das Shelly automatisch verbindet und seine Zeit mit der des eingetragenen Zeitservers synchronisiert. Solange die Anlage mit Strom versorgt ist, können alle Schedule Jobs ohne Einschränkung genutzt werden, weil die interne Uhr die zumeist erforderliche Zeit zur Verfügung stellt.

3. Funktionsweise

Der oder die messenden Assistenten schreiben am Ende einer Bewässerung deren finales Volumen in die auf dem Master vorhandene (ggf. noch anzulegende) virtuelle Komponente. Damit eine Änderung des Wertes dieser Komponente erzwungen wird, schreiben die Assistenten kurzzeitig (1s) eine 0 und danach den gemessenen finalen Volumenwert.

Zu jedem messenden Assistenten bzw. jeder vorhandenen virtuellen Komponente für das finale Volumen existiert ein "on change" Eventhandler. Jeder Aufruf dieses Handlers bewirkt die Addition des neuen Wertes zur bisherigen Summe. Somit erfolgt dies jeweils am Ende einer Bewässerung.

Der tagesbezogene Zeitplan ruft um 00:00 Uhr die Skriptfunktion store() auf. Diese hängt an die Protokolldatei für Tagesverbrauche die akkumulierten finalen Volumina (ohne Einheit Liter) an und beginnt eine neue Zeile mit dem nun aktuellen Tagesdatum im Format JJJJ-MM-TT.

Jede komplette Zeile besitzt folgende Struktur.

JJJJ-MM-TT, <Volumen Kanal 0>[, <Volumen Kanal 1>[, <Volumen Kanal 2>] ...]

Die Teile in eckigen Klammen sind optional. Sie hängen von der Anzahl an geschalteten Bewässerungskanälen ab.

Unsere Protokolldatei hat am 2026-08-15 folgenden Inhalt.

2026-08-02, 1.6, 13.2
2026-08-03, 4.5, 13.3
2026-08-04, 0, 19.4
2026-08-05, 5.6, 17.2
2026-08-06, 13.7, 21.5
2026-08-07, 0, 12.7
2026-08-08, 13.8, 12.5
2026-08-09, 6.9, 12.6
2026-08-10, 5.1, 10.2
2026-08-11, 4.4, 14
2026-08-12, 4.4, 15.5
2026-08-13, 12.5, 18.3
2026-08-14, 5.7, 19.9
2026-08-15

Jeweils links steht das Tagesdatum. An zweiter Stelle steht das Tagesvolumen von Kanal 0, an dritter Stelle das von Kanal 1. Mit jeder Speicherung wird die Datei um eine Zeile länger, solange die im Skript konfigurierte maximale Länge von bspw. 10000 Bytes mit dem zu speichernden Eintrag nicht überschritten würde.

Der derzetige Probelauf soll ggf. vorhandene Laufzeitprobleme zeigen. Bisher wurden keine Probleme festgestellt. Später kann ich dieses Skript um Speicherung von wöchentlichen und/oder monatlichen Volumina erweitern. Hierzu wird jeweils ein zusätzlicher Schedule Job erforderlich sein.

4. Handhabung

Vor der ersten Speicherung muss die dafür vorgesehene Datei angelegt werden. Sie muss mindestens ein Zeichen enthalten, weil die Firmware keine leere Datei speichert. Hier genügt bspw. ein Punkt oder noch besser und vorbereitend das aktuelle Tagesdatum in der Form JJJJ-MM-TT (ohne anschließendes Komma!). Der Identifier (id) dieser Datei wird noch gebraucht. Diese Nummer kann im Skripteintrag unten rechts hinter "script:" abgelesen werden. Als vielsagender Dateiname sei "Tagesvolumina" o.ä. empfohlen. Dieser Name hat für die Skripte keine Bedeutung, weil darin ausschließlich der Identifier entscheidend ist.

Das WebUI gestattet zwar ein Starten dieser Datei, was aber total sinnfrei ist, weil diese keine ShellyScript Anweisungen enthält. Zum lesen der Tagesverbrauche kann sie aber geöffnet werden. Der Anwender kann prinzipiell diese Datei editieren, bspw. um Hinweise an komplette Zeilen anzuhängen.

Beispiel:
(Hinzugefügtes rechts in dunkelrot, incl. vorangestelltem Komma, um das CSV Format beizubehalten!)

2026-08-02, 1.6, 13.2, Beginn der Aufzeichnung
2026-08-03, 4.5, 13.3
2026-08-04, 0, 19.4

Ein Löschen von kompletten Zeilen ist ebenfalls zweckmäßig, bspw. weil diese nicht von Interesse oder sie bereits in eine Tabellenkalkulation eingeflossen sind. Hierbei ist es zielführend, die letzte Zeile mit dem Datum zu belassen oder das aktuelle Datum einzutragen. Man bedenke, dass neuere Einträge nicht mehr einfließen, wenn die konfigurierte maximale Dateilänge erreicht ist. Die vorkonfigurierte maximale Länge von 10000 Bytes sollte nicht überschritten werden. Andernfalls kann es wegen nicht auffangbarem Laufzeitfehler erforderlich werden, den Master neu zu starten (reboot) oder gar kurzzeitig auszuschalten - wobei die synchronisierte Zeit verloren gehen kann.

Jede vom Skript geschriebene, komplette Zeile wird maximal 25 Zeichen enthalten (bei bis zu je 999.9 Liter auf zwei Kanälen, incl. Zeilenvorschub '\n'). Dies ergibt bis zu 10000/25 = 400 Zeilen, also Einträge von 400 Tagen. Wer bspw. nur bis zu 200 Tage Speicherung braucht, sollte im Skript die max. Länge auf LogMax = 5000 reduzieren. Die nutzbare Kapazität einer solchen Protokolldatei hängt davon ab, wie die Firmware die Nutzung des persistenten (=nichtflüchtigen) Speichers organisiert. Solches kann einer Änderung seitens Shelly unterworfen sein.

5. Das Skript

Ausschließlich die ersten 5 Konstanten (unter dem ersten const) sind zu konfigurieren, also den eigenen Vorgaben anzupassen.

// collecting and store values
// GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007
// More information: https://www.gnu.org/licenses/gpl-3.0.txt
// ABSOLUTELY NO WARRANTY!!!
// Author: Gerhard Eichelsdörfer alias eiche
// Version 2026-08-01-01
// using virtual components of type number to collect values and triggering schedule jobs

const
  // begin of configuration

  MqttEn = true, // Using MQTT?
  DayTopic = "Bewaesserung-2/volume/day",
  Vkeys = ["number:202", "number:205"],
  LogId = [5, 6], // daily log id, weekly or monthly log id (storage not yet implemented)
  LogMax = 10000, // maximum length of each log file
  // end of configuration

    Pattern = "yyyy-mm-dd",
  DateLen = Pattern.length;

let
  FinalVol = [0, 0], // accumulated values for two chennels
  LogLen = [null, null]; // daily, weekly (storage not yet implemented)

const Handle = [Virtual.getHandle(Vkeys[0]), Virtual.getHandle(Vkeys[1])];

// Get the length of the logfile, a not runnable script.
function getLogLen() {
  Shelly.call("Script.GetCode", {id:LogId[0], offset:0, len:1},
    function(res, errc, errm) {
      if(errc) {
        console.log(errc, errm);
        return;
      }
      LogLen[0] = res.left+1;
      Shelly.call("Script.GetCode", {id:LogId[1], offset:0, len:1},
        function(res, errc, errm) {
          if(errc) {console.log(errc, errm); return;}
          LogLen[1] = res.left+1;
        });
    });
}

function collectFinalVol(id, val) {
  val = Number(val);
  if(id<0 || id>=FinalVol.length || isNaN(val) || val<0) {
    console.log("collectFinalVol: wrong id or wong value");
    return;
  }
  FinalVol[id] += val;
  print("Kanal", id, ", gesamtes Volumen:", FinalVol[id]);
}

Handle[0].on("change", function(e) {
  if(e.value) collectFinalVol(0, e.value);
});

Handle[1].on("change", function(e) {
  if(e.value) collectFinalVol(1, e.value);
});

function d2(n) {return n>9 ? n.toString() : "0" + n;}

function getDateStr() {
  let d = new Date;
  return d.getFullYear() + "-" + d2(d.getMonth()+1) + "-" + d2(d.getDate());
}

function matchDate(val) {
  return val.length==DateLen && val[4]==Pattern[4] && val[7]==Pattern[7];
}

function appendDate() {
  let d = (LogLen[0]<DateLen ? "" : "\n") + getDateStr();
  //print(d);
  Shelly.call("Script.PutCode", {id:LogId[0], code:d, append:LogLen[0]>=DateLen},
    function(res, errc, errm) {
      if(errc) {console.log(errc, errm); return;}
      LogLen[0] = res.len;
  });

}

function store() {
  let d = getDateStr();
  let c = ", " + String(FinalVol[0]) + ", " + String(FinalVol[1]) + "\n" + d;
  //print(d);
  Shelly.call("Script.PutCode", {id:LogId[0], code:c, append:LogLen[0]>=DateLen},
    function(res, errc, errm) {
      if(errc) {console.log(errc, errm); return;}
      LogLen[0] = res.len;
  });
  if(MqttEn) MQTT.publish(DayTopic, JSON.stringify(FinalVol), 1, true);
  FinalVol = [0, 0]; // reset collected values
}

getLogLen();

Timer.set(2000, false, function() {
  if(!LogLen[0]) {console.log("no log file"); return;}
  if(LogLen[0]<DateLen) appendDate();
  else {
    Shelly.call("Script.GetCode", {id:LogId[0], offset:LogLen[0]-DateLen}, function(res, errc, errm) {
      if(errc) {console.log(errc, errm); return;}
      //print(res.data);
      if(!matchDate(res.data)) appendDate(); // no valid date format
    });
  }
});

6. Anmerkungen

  1. Das Skript, insbesondere die Funktion store() wird voraussichtlich zur Nutzung einer weiteren Speicherung - wie wöchentliche Volumina - geändert. Ich bin mir derzeit noch nicht im klaren bzgl. eines zweckmäßigen Aufrufparameters und dessen Verarbeitung, weshalb ich die Skriptänderung aufschiebe.
  2. Es ist durchaus möglich, von einer Lokation ohne Internetzugang die gespeicherten Daten via Smartphone und VPN auf ein zentrales (Heim-)Computernetzwerk zu übertragen. Solches habe ich in einem anderen Projekt für einen Freund implementiert. Hierfür habe ich einen MQTT Broker, Node-RED und InfluxDB im zentralen Netzwerk genutzt. Dazu ist zusätzlicher Skript Aufwand erforderlich, um eine komplette Datei in Paketen zu übertragen. Diese Arbeit will ich an dieser Stelle (noch) nicht leisten.

 

- in Arbeit -