2026-07-24

Die Anwenderin sieht nur eine Anlage ohne strukturelle Einzelheiten, die weitestgehend durch den Master repräsentiert wird. Nicht nur dafür bietet der Master fast alle erforderlichen Funktionen. Zwei weitere, optionale Funktionen sind zusätzlich nützlich, die nicht im Master Bewässerungsprozess zu implementieren sind - Volumenmessungen und Überwachungen.

Zu Teil 1, zu Teil 2, zum einführenden Beitrag

Inhalt

  1. Einführung mit technischen Grundlagen
  2. Volumenmessungen mit geeignetem Verarbeitungsprozess
    1. Konfiguration
    2. Funktionen
    3. Code
  3. Das Überwachungsskript
    1. Konfiguration
    2. Funktionen
    3. Code

8. Einführung mit technischen Grundlagen

Für Volumenmessungen sind Assistenten wie Shelly Plus UNI erforderlich, weil diese einen gut geeigneten Eingang für eintreffende Impulse besitzen, welche von einem Firmware internen Zähler gezählt werden. Ein solcher Assistent empfängt vom Master ausschließlich den Wert des ihm zugeordneten Anwendungsparameters "Volumen". Der Assistent arbeitet aus folgenden Gründen autonom. 

  1. Der Beginn der Zählens für die Volumenmessung ist nicht von außen auslösbar.
    Mit dem Start einer Bewässerung treffen sehr zügig die ersten Zählimpulse ein. Während solcher Impulse kann der per Firmware implementierte Zähler leider nicht rückgesetzt werden, um ihn neu zu starten. Deshalb müsste ein von außen kommender Trigger zum Rücksetzen vor dem Einschalten eintreffen, was wegen unterschiedlicher Einschaltquellen ohne zusätzliche Verzögerung und wiederholtem Schalten unmöglich ist.
  2. So kann die Anlage robuster arbeiten, weil das Ausschalten wegen Überschreitung des max. Volumens (Anwendungsparameter) auch bei angehaltenem Master Skript gelingt.

Ein Überwachungsskript, bisher nur auf dem Master, überwacht das Laufen des Master Skripts mit Nutzung von mindestens einem Zeitplan. Solche Zeitpläne können bis einschließlich Firmware Version 2.0 nicht wie hier erforderlich im Web UI parametriert werden. Das liegt an den Einschränkung der dort via Auswahl verfügbaren Methoden. Somit muss dafür ein anderer Weg genutzt werden, bspw. meine Webseiten

Der administrative Anwender sollte sich von einem Hinweis im Web UI oder der Shelly smart control App nicht verunsichern lassen, die sinngemäß besagt, dass der Zeitplan nicht wie gewünscht arbeiten könnte.

Das Überwachungsskript kann über den Aufruf der Funktion checkScripts

  • abhängig von den Aufrufparametern lokale oder remote Skripte überwachen und bei Bedarf starten,
  • zu gestoppten Skripten Messenger Nachrichten via callmebot.com mit Angaben zur Quelle, Uhrzeit, betreffendem Shelly, Skriptname und ggf. zum "gestartet haben" senden,
  • alternativ zu Messenger Nachrichten in einem nicht ausführbaren Skript als logfile entsprechende Nachrichten ablegen, die vom administrativen Anwender gelesen werden können.

Dies soll

  1. die Bewässerungsanwendung auch nach Ausfällen automatisch reaktivieren können,
  2. Nachrichten hinterlassen, die solche Ausfälle leicht erkennbar machen und
  3. bei Bedarf zur Verbesserung der Skripte beitragen.

Zusätzlich können weitere solcher Überachungsskripte auch auf anderen Shelly Geräten, insbesondere Volumen messenden Assistenten, redundant genutzt werden.

9. Volumenmessungen mit geeignetem Verarbeitungsprozess

Am Ende des Skripts ist ein RPC Aufruf zu finden, der bei Skriptstart die Anwendungsparameter von der virtuellen Textkomponente des Master holt und den max. Volumenwert (Index=1) nach xmax kopiert. Damit ist sichergestellt, dass nach dem Skriptstart ein gültiger Wert in xmax vorliegt.

9.1 Konfiguration

  • Diff wird im Vergleich des gemessenen Volumens (xtotal) mit dem Anwendungsparameter max. Volumen (xmax) eingesetzt (xtotal >= xmax - Diff). Wegen eines Wassernachlaufs nach dem Ausschalten ist das finale Volumen ein wenig größer als das zuletzt gemessene. Mit dem Diff Wert kann diese Differenz bei Bedarf ausgeglichen werden.
  • Latency ist die Timerzeit zum feststellen des Bewässerungsendes. Mit jedem neu eintreffenden Volumenwert xtotal wird ein Timer neu gestartet. Wenn an dessen Ende kein neuer Volumenwert eintrifft, gilt dies als Ende einer Bewässerung und das finale Volumen wird gesendet.
  • MasterRpc beinhaltet den ersten Teil eines RPC mit IP-Adresse des Master.
  • MasterParamsId muss die Zahl im Schlüssel der virtuellen Textkomponente des Master für die Anwendungsparameter enthalten. Damit wird der Anwendungsparameterwert von "Volumen" für xmax nach dem Skriptstart geholt, via Shelly.call am Ende des Skripts. Dieser Weg bezieht das Master-Skript nicht ein, was leider irregulär ist. Damit ist aber gewährleistet, dass xmax nach Skriptstart auch dann gemäß Anwendungsparameter gesetzt ist, wenn das Master-Skript nicht läuft.
  • Mit Hilfe von MasterVolumeId und MasterVolFinId werden die Werte des aktuellen Volumens und des Volumens am Ende einer Bewässerung in die beiden virtuellen Komponenten des Masters geschrieben.
  • SwitchUrl beinhaltet den URL zum ausschalten des zugehörigen Ausgangs im Master Shelly.
  • Die Topic-Werte für die MQTT-Kommunikation sind mit denen des Masters abzustimmen. Damit sendet der Assistent eigenständig die beiden Volumenwerte zur IoT-Außenwelt.
  • ValidityThreshold legt fest, ab wievielen gezählten Impulsen der Zählerstand zum Messbeginn als valide gilt. Dies unterdrückt das Zählen und Senden der Volumen auf Grund ungültiger Impulse, die wegen kleiner Bewegungen innerhalb des Sensors eintreffen.

9.2 Funktionen

  • remoteSwitchOff() schaltet gemäß SwitchUrl via Methode HTTP.Get den zugeordneten Ausgang aus.
  • sendVolume() sendet das aktuell gemessene Volumen an die zugeordnete numerische virtuelle Komponente des Master und via MQTT, jeweils gerundet mit einer Nachkommastelle.
  • sendVolFin() sendet das Gesamtvolumen einer abgeschlossenen Bewässerung an die zugeordnete numerische virtuelle Komponente des Master und via MQTT, jeweils gerundet mit einer Nachkommastelle.
  • setParams() importiert ein Array mit allen zu nutzenden Anwendungsparameterwerten. Hier ist dies nur ein Wert für das max. Volumen. Dieser wird bei Gültigkeit in der Variablen xmax gespeichert. Der Wert von xmax wird an den Aufrufer zurückgeliefert (eingebettet in HTML Code).
  • init() setzt in beiden folgenden Fällen den Zähler zurück. Sie prüft, ob die gezählten Impulse auf eine gültige Messung schließen lassen (total >= ValidityThreshold). Wenn nicht, wird diese Zählung verworfen. Andernfalls wird der letzte Wert vor dem Rücksetzen in xtotal gespeichert und via sendVolume() und sendVolFin() gesendet. init() wird bei Skriptstart und insbesondere am Ende jeder Bewässerung aufgerufen, damit der Zähler vorbereitend für die nächste Messung mit 0 beginnt.
  • process() ist der Eventhandler dieses Skripts. Er verarbeitet ausschließlich "input:2" (Zähleingang) Ereignisse. Wenn sich der Zählerstand geändert hat, wird das Volumen (xtotal) via sendVolume() gesendet. Weiterhin wird der Timer mit dem Latency-Wert neu gestartet (Retriggerung), an dessen Ende die Messung als abgeschlossen gilt, weil dann zwischenzeitlich kein neuer Zählerstand eingetroffen ist.

9.3 Code

// water meter
// 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-07-18_01
 
let Debug = false;
 
// ----- begin of configuration -----
const
  // environment dependent parameters
  Diff = 0.1, // determined value in tests for switch off before exceeding xmax
  Latency = 10*1000,
  
  // communication dependent parameters
  // master = switching device, possibly with virtual components
  MasterRpc = "http://192.168.33.1/rpc/",
  MasterParamsId = "200", // to import the parameter list, get this virtual component type text id for parameters from master device
  MasterVolumeId = "200", // to write the current volume, get this virtual component type number id from master device
  MasterVolFinId = "202", // to write the finished volume, get this virtual component type number id from master device
  SwitchUrl = MasterRpc + "switch.set?id=1&on=false",
 
  PreTopic = "Bewaesserung-2/0/", // this should be the same as at master + id of the channel
  Topic = {
    Volume: PreTopic + "status/volume",  // publishing actual volume
    VolumeFinish: PreTopic + "status/volfin"  // publishing volume at finished measurement
  },
 
  // sensor dependent threshold
  ValidityThreshold = 3; // minimum number of counts to avoid measurement errors caused by mechanical disruptions
// ----- end of configuration -----
 
let
  finished = false,
  total = 0,  // count
  xtotal = 0, // custom count 
  xmax = 1, // or 0, maximal volume for remote switching off
  Th = null; // Timer handle
  
function remoteSwitchOff() {
  if(!finished) {
    if(Debug) print("remote switch off");
    Shelly.call("http.get", {url:SwitchUrl},
      function(res, errc, errm) {
        if(errc) {console.log(errc, errm); return;}
        finished = true;
      }
    );
  }
}
 
function sendVolume() {
  let url = MasterRpc+"Number.Set?id="+MasterVolumeId+"&value="+String(Math.round(xtotal*10)/10);
  //print(url);
  Shelly.call("HTTP.Get", {url:url});
  MQTT.publish(Topic.Volume, String(Math.round(xtotal*10)/10), 1, true);
}
 
function sendVolFin() {
  let url = MasterRpc+"Number.Set?id="+MasterVolFinId+"&value="+String(Math.round(xtotal*10)/10);
  //print(url);
  Shelly.call("HTTP.Get", {url:url});
  MQTT.publish(Topic.VolumeFinish, String(Math.round(xtotal*10)/10), 1, true);
}
 
// communication function for receiving and setting the process parameter(s)
function setParams(p) {
  if(isNaN(p[0]) || p[0]<0) return xmax;
  xmax = p[0];
  if(Debug) print("assist receive xmax value:", xmax);
  return xmax;
}

function init() {
  let counts = Shelly.getComponentStatus("input", 2).counts.total;
  if(counts < ValidityThreshold) { // invalid counts
    Shelly.call("input.resetcounters", {id:2, type:["counter"]});
    return;
  }
  Shelly.call("input.resetcounters", {id:2, type:["counter"]},
    function(res, errc, errm) {
      if(errc) {console.log(errc, errm); return;}
      finished = false;
      if(Debug) print("init:", JSON.stringify(res));
      if(res.counts.total>=ValidityThreshold) {
        total = res.counts.total;
        xtotal = res.counts.xtotal;
sendVolume();
sendVolFin();
      }
    }
  );
}
 
function process(e) {
  //print(JSON.stringify(e));
  if(e.component!=="input:2") return;
  let i = e.info;
  let c = Shelly.getComponentStatus("input", 2).counts;
  //print(JSON.stringify(i));
  switch(i.event) {
    case "count_change":
      if(Debug) print("changed to", i.total, "->", i.xtotal, ", ", c.total, "->", c.xtotal);
      total = c.total;
      xtotal = c.xtotal;
      sendVolume();
      if(xmax!==null && xmax>0 && xtotal>=xmax-Diff) remoteSwitchOff();
      Timer.clear(Th);
      Th = Timer.set(Latency, false, init);
      break;
    case "count_measurement":
      if(c.total!==total) { // may be obsolete
        total = c.total;
        xtotal = c.xtotal;
        if(Debug) print("measured:", i.total, "->", i.xtotal, ", ", total, "->", xtotal);
      }
      break;
    case "counts_reset":
      if(Debug) print("Zähler rückgesetzt");
      break;
    case "freq_measurement":
      if(Debug) print(JSON.stringify(i));
      break;
    default:
      if(Debug) print("not yet implemented:", i.event);
  }
}
 
Shelly.addEventHandler(process);
 
init();
 
// optionally
// fetch the xmax value on script start
// The xmax value is the second parameter in the parameter list.
Shelly.call("HTTP.Get", {url:MasterRpc+"Text.GetStatus?id="+MasterParamsId},
  function(res, errc, errm) {
    if(errc) {console.log(errc, errm); return;}
    if(Debug) print(JSON.stringify(res.body));
    try {
      let p = JSON.parse(res.body).value;
      if(Debug) print(JSON.stringify(p));
      xmax = Number(p.split(',')[1].split(':')[1]);
      if(Debug) print(xmax);
    } catch(e) {
      console.log(e.message);
    }
  }
);

 

10. Das Überwachungsskript

Dieses Skript sendet bei Bedarf Nachrichten via callmebot.com. Der Nachrichtentext darf keine Leerzeichen enthalten, weil er via HTTP Get gesendet wird. Deshalb müssen die Leerzeichen entweder durch %20 oder, für callmebot.com, durch '+' ersetzt werden. Hier wird dafür das + Zeichen genutzt.

Alternativ, bzw. bei nicht erfolgtem Senden einer Nachricht, wird diese Nachricht in eine Protokolldatei geschrieben. Zu Beginn des Skripts (nach der Konfiguration) ist ein RPC Aufruf zu finden, der nach Skriptstart die aktuelle Länge der Protokolldatei ermittelt. Dies wird an anderer Stelle zur Begrenzung der Protokolldateilänge genutzt.

Der administrative Anwender muss zuvor diese Protokolldatei mit einem Zeichen als Inhalt (bspw. ein Punkt) erstellen.

10.1 Konfiguration

Phone und APIKey sind zwei Schlüssel, die man beim Erstellen eines callmebot Account erhält und die hier einzutragen sind, wenn Nachrichten via callmebot.com gesendet werden sollen.

HttpCode ist der erste Teil des URL zum senden einer Messenger-Nachricht. Er setzt sich aus dem URL zu callmebot.com mit dem Messengerdienstnamen, den beiden o.a. Schlüsseln und dem Parameternamen "text" zusammen. Dieser Wert wird von der Funktion sendMsg genutzt, die noch die Quelle und die zu sendende Nachricht hinten anhängt.

Src beinhaltet die Quelle der Nachricht, hier "Bewässerung-2:+".

Msg beinhaltet den einleitenden Text, der zeigt, worum es sich handelt - hier "Inaktive+Skripte:".

LogId ist der Skript Identifier zur Protokolldatei (= nicht ausführbares Skript). Damit werden in diese persistente Datei die Nachrichten geschrieben, die nicht via callmebot.com gesendet werden (konnten). Der administrative Anwender mag hier entscheiden, ob er anstatt eines Nachrichtendienstes ausschließlich diese Protokolldatei nutzen will.

LogMax beinhaltet die maximale Länge der Protokolldatei. Diese Grenze wird beim protokollieren nicht überschritten.

10.2 Funktionen

store() schreibt eine valide Nachricht in die Protokolldatei. Hierfür ersetzt sie alle + Zeichen durch Leerzeichen, damit der Protokolltext leicht lesbar ist.

d2() liefert zu einer ein- oder zweistelligen Zahl einen immer zweistelligen Text. Dies wird für Datum und Zeitangaben in standartisiertem Format genutzt.

sendMsg() versucht, die importierte Nachricht via callmebot.com zu senden. Gelingt dies nicht, ruft sie store() auf, zwecks Speicherung der Nachricht in der Protokolldatei.

analyze() importiert die Liste an Skripten auf dem Zielgerät, einen Id oder ein Array mit allen Id der zu prüfenden Skripte, eine Basisnachricht und einen Wahrheitswert, der besagt, ob die nicht laufenden Skripte gestartet werden sollen. Zu jedem geprüften, nicht laufenden Skript wird die Nachricht entsprechend erweitert. Nur wenn mindestens eines der geprüften Skripte nicht läuft, wird eine Nachricht via sendMsg versucht zu senden bzw. die Nachricht in der Protokolldatei abzulegen.

checkLocalScripts() holt die auf demselben Gerät vorhandene Skriptliste und ruft damit analyze() auf.

checkRemoteScripts() importiert u.a. die IP-Adresse des anderen Shelly Gerätes, holt dessen Skriptliste und ruft damit analyze() auf. Scheitert die Kommunikation mit dem anderen Shelly, wird eine Nachricht über dessen Nichterreichbarkeit gesendet bzw. lokal in der Protokolldatei gespeichert.

checkScripts() ist die von außen aufzurufende Funktion zum Überprüfen von Skripten auf ihren Run-Status. Dies kann via Methode Script.Eval von irgendwoher geschehen. Eine zweckdienliche Nutzung erfolgt über mindestens einen Zeitplan, der diese Funktion periodisch und parametriert aufruft. Die importierten Parameter sind

  1. eine Zeichenkette mit der IP-Adresse des Ziel-Shelly oder "local", "localhost", "127.0.0.1" für das lokale Gerät als Ziel,
  2. ein einzelner Id-Wert oder ein Array aus Id-Werten, welche die zu prüfenden Skripte identifizieren,
  3. ein optionaler Zeitversatzwert, der festlegt, um wieviele Sekunden verzögert die Prüfung starten soll,
  4. ein optionaler Wahrheitswert, der mitteilt, ob die geprüften, nicht laufenden lokalen Skripte zu starten sind.

Einschränkung: Derzeit können ausschließlich lokal geprüfte Skripte gestartet werden. Versucht man dies mit remote Skripten, ist dies wirkungslos.

Weil checkScripts() die zur Nutzung der Observierung entscheidende Funktion ist, sollen Beispielaufrufe deren Einsatz etwas verdeutlichen.

  • checkScripts("local",1) lässt auf demselben Gerät, auf dem dieses Skript läuft (=lokal), das Skript 1 prüfen.
  • checkScripts("localhost",[2,3,7],0,true) lässt lokal die Skripte 2, 3 und 7 ohne Zeitversatz (0) prüfen und die nicht laufenden dieser Skripte starten. Der dritte Parameter 0 ist erforderlich, wenn der vierte genutzt wird.
  • checkScripts("192.168.33.56",1,5) lässt auf dem Gerät mit IP-Adresse 192.168.33.56 nach 5 Sekunden das Skript 1 prüfen.

10.3 Code

// 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-07-19_01
// This script may sends messages via callmebot.com.
// If this fails, a logfile will be used instead.
// This logfile has to be created by the user.

const
// For using callmebot.com, you must entry your data of phone key and API key.
  Phone = "xxxxxxxxxxxxxxxxxx",
  APIKey = "yyyyyy",
  HttpCode = "https://signal.callmebot.com/signal/send.php?phone="+Phone+"&apikey="+APIKey+"&text=",
  Src = "Bewässerung-2:+", // Entry your preferred source name!
  Msg = "Inaktive+Skripte:",
  LogId = 4, // the id of the logfile - only used if sending via callmebot fails
  LogMax = 10000; // maximum length of the logfile
  
let LogLen = null;

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

function store(msg) {
  if(msg==='') return;
  if(LogLen===null) {console.log("missing log script:"+LogId); return;}
  if(LogLen+msg.length>LogMax) {console.log("log script:"+LogId+" has reached maximum length"); return;}
  // String.replace don't work in this environment.
  msg = msg.split('+');
  let n = msg.length-1, m = "\n";
  for(let i=0; i<n; ++i)
    m += msg[i] + ' ';
  m += msg[n];
  //print(m);
  Shelly.call("Script.PutCode", {id:LogId, code:m, append:true},
    function(res, errc, errm) {
      if(errc) {console.log(errc, errm); return;}
      LogLen = res.len;
    });
}

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

// sendMsg tries to send a message via callmebot.com.
// Whithout success the message will be stored into a script with id = LogId.
function sendMsg(msg) {
  let d = new Date();
  let t = d.getFullYear()+"-"+d2(d.getMonth()+1)+"-"+d2(d.getDate())+"+"+d2(d.getHours())+":"+d2(d.getMinutes());
  msg = t+"+"+msg;
  Shelly.call("HTTP.Get", {url:HttpCode+Src+msg},
    function(res, errc, errm) {
      if(errc) {
        console.log(errc, errm);
        if(errc===-104 || errc===-114) {
          // store the message into log script
          console.log("store message into log script:"+LogId);
          store(msg);
        }
        return;
      }
      res = res.body;
      //print(res);
      let m = res.indexOf("APIKey is invalid.") > 0 ? "ungültiger API key" : "";
      if(m!=="") {console.log(m); return;}
      let ok = res.indexOf("sent to ") > 0;
      console.log((ok ? "" : "nicht ")+"gesendet"+(ok ? "" : ", vermutlich liegt ein Leerzeichen im Nachrichtentext"));
  });
}

// analyze the result of method Script.List
function analyze(res, id, msg, start){
  if(id.length===undefined) id = [id];
  let i, j, n = id.length, l = res.length, d = false;
  for(i=0; i<n; ++i) {
    for(j=0; j<l && res[j].id!==id[i]; ++j);
    if(j>=l) {
      console.log("missing script:"+id[i]);
      msg += (d ? ",+" : "+")+"missing+script:"+id[i];
      d = true;
    } else {
      //print(JSON.stringify(res[j]));
      if(!res[j].running) {
        if(start) {
          let tid = res[j].id;
          //print("start script", tid);
          Shelly.call("Script.Start", {id:tid}, function(res, errc, errm) {
            if(errc) {console.log(errc, errm); return;}
            console.log("script", tid, "started");
          });
        }
        msg += (d ? ",+" : "+") + res[j].name + (start ? "+gestartet" : "");
        d = true;
      }
    }
  }
  //print(msg);
  if(d) sendMsg(msg);
}

// checkLocalScripts sends a message if one or more local scripts in the id list are not running
// the parameter may be a single number or an array of numbers
// call examples: checkLocalScripts(2), checkLocalScripts([1,2])
function checkLocalScripts(id, start) {
  //print("local");
  Shelly.call("Script.List", {}, function(res, errc, errm) {
    if(errc) {console.log(errc, errm); return;}
    //print(JSON.stringify(res));
    analyze(res.scripts, id, Msg, start); // do the remainder
  });
}

// checkRemoteScript sends a message if one or more remote scripts in the id list are not running
// the id parameter may be a single number or an array of numbers
// call examples: checkRemoteScript("<IP address>", 2), checkRemoteScript("<IP address>", [1,2])
function checkRemoteScripts(ip, id, start) { // start has no effect yet
  //print("remote");
  Shelly.call("HTTP.Get", {url:"http://" + ip + "/rpc/Script.List",timeout:30}, function(res, errc, errm) {
    if(errc) {
      console.log(errc, errm);
      if(errc===-104) sendMsg("Host+"+ip+"+is+not+reachable.");
      return;
    }
    if(res.code!==200) return;
    //print(res.body);
    res = JSON.parse(res.body).scripts;
    //print(JSON.stringify(res));
    analyze(res, id, "Host+"+ip+"+"+Msg, false); // do the remainder
  });
}

// checkScripts is the most usable function in this script.
// Parameters:
// ip: IP address of the scripts host
// id: single id of the checked script or an array of such ids
// dt: time offset in seconds after the call - optional - if missing, the check will be proceed immediately
// call examples:
// checkScripts("local", 1, 5);
// checkScripts("172.16.3.23", 1, 3);
function checkScripts(ip, id, dt, start) {
  if(start==undefined || typeof start!=="boolean") start = false;
  let local = ip==="local" || ip==="localhost" || ip==="127.0.0.1";
  if(dt==undefined || dt<=0) local ? checkLocalScripts(id, start) : checkRemoteScripts(ip, id, start);
  else Timer.set(dt*1000, false, function() {
    local ? checkLocalScripts(id, start) : checkRemoteScripts(ip, id, start);
  });
}