executeCommandLine für Curl

Einrichtung der openHAB Umgebung und allgemeine Konfigurationsthemen.

Moderatoren: seppy, udo1toni

Antworten
TheDallas
Beiträge: 7
Registriert: 28. Mär 2024 23:05
Answers: 0

executeCommandLine für Curl

Beitrag von TheDallas »

Hallo Forum,
ich stecke hier seit Tagen fest.

Ich habe einige Kameras von Hikvision, die eine API unterstützen.
Der OpenHab 3.4.4 läuft in einem Docker und steuert Steckdosen und AudioVideoSystem.
Er soll nun dafür sorgen, das die Kameras nur aufnehmen, wenn es eben dunkel wird, oder keiner im Haus ist, oder oder oder.
Die Typische Regelsteuerung eben. Dafür musste ich aber erstmal herausfinden, wie die API "tickt".

Mit CURL habe ich es jetzt immerhin schon hinbekommen, das die Camera die Linenüberwachung ein- und ausschaltet.
Das ist so gelöst:

Code: Alles auswählen

curl --digest -X PUT "http://<Name>:<Passw>@<IP>/ISAPI/Smart/linedetection" -d "<LineDetectionList xmlns=\"http://www.hikvision.com/ver20/XMLSchema\" version=\"2.0\"><LineDetection><id>1</id><enabled>true</enabled></LineDetection></LineDetectionList>"
Im Ergebnis schaltet die Kamera die LineCrossingDetection auf ON oder eben mit false wieder auf OFF.
Schonmal gut

Nun wollte ich dieses Stückchen in ein Script hängen, welches ich dann über meine Regeln und Items (Anwesenheit, Astro etc) steuern kann.
Die Idee war, die CURL Lösung in einen httpPutRequest umzubuchen. Das ist krachend gescheitert. Da bin ich einfach zu blöd für.
Die neue Idee ist, den CURL Befehl einfach direkt in ein Skript zu legen.
Gesagt, getan:

Code: Alles auswählen

#!/bin/bash 
var Exec = Java.type("org.openhab.core.model.script.actions.Exec");
Exec.executeCommandLine(Duration.ofSeconds(5),"/etc/openhab/scripts/lineDetectOn.sh")
Immerhin, nach einigem lesen und versuchen, gibts keine Fehler mehr , außer :

Code: Alles auswählen

 Failed to execute commandLine '[/etc/openhab/scripts/lineDetectOn.sh]'
Der genannte Ordner und das Script haben User und Group 9001 (das ist der OPENHAB User). Die Berechtigungen sind mit 0777 gesetzt.
Der User Openhab kann das Script direkt (als ohne SH vorn) ausführen.
Der Root vom HOST kann das auch.
Das Skript ist in die Whitelist eingetragen.
Trotzdem lässt es sich nicht ausführen.
Ich habe das auch über die PaperUI mit dem Exec Binding probiert, aber das Ergebnis ist das gleiche.

So. Jetzt bin ich am Ende.

Wo ist der Fehler ?
Kann mich jemand Step-by-Step führen ?

Ich danke euch !

Benutzeravatar
udo1toni
Beiträge: 13986
Registriert: 11. Apr 2018 18:05
Answers: 222
Wohnort: Darmstadt

Re: executeCommandLine für Curl

Beitrag von udo1toni »

Herzlich willkommen im openHAB Forum!

Zunächst einmal musst Du zwei Dinge auseinander halten, nämlich GNU/Linux Scripte und openHAB (Scripte).
Du kannst über das Exec Binding oder auch per Rule Befehl ein GNU/Linux Shell Script ausführen, immer gegeben, dass im Container alle Voraussetzungen vorhanden sind. Und da ist schon die erste ernsthafte Hürde.
Ich denke nämlich nicht, dass ein Paket wie curl im offiziellen openHAB Container eingebaut ist, das wäre im Zweifel nur unnötiger Ballast.
openHAB kann Scripte ausschließlich im eigenen Container laufen lassen, dort muss also auch curl installiert sein.
Du kannst testen, ob Du den curl Befehl erfolgreich von dort starten kannst (als User openhab, nicht als root), dazu musst Du Dich über docker mit dem Container verbinden, damit Du den fraglichen Befehl senden kannst.

Nur falls Du das Exec Binding verwendest, musst Du exakt den Befehl, den Du auch im Exec Thing hinterlegt hast, zusätzlich in der whitelist eintragen.

Aber es gibt noch ein paar weitere Punkte:
Dein gezeigtes Script

Code: Alles auswählen

#!/bin/bash 
var Exec = Java.type("org.openhab.core.model.script.actions.Exec");
Exec.executeCommandLine(Duration.ofSeconds(5),"/etc/openhab/scripts/lineDetectOn.sh")
ist Unsinn.

Zeile 1 weist darauf hin, dass /bin/bash zur Ausfürung verwendet werden soll. Das ist im Grunde genommen passend, aber leider steht /bin/bash im openHAB Container nicht zur Verfügung. Wenn, dann müsstest Du also /bin/sh verwenden.
Zeile 2 ist eine Definition, die sich auf JavaScript Scripting innerhalb openHAB bezieht. in einem Shell Script hat diese Zeile nichts zu suchen.
Zeile 3 könnte theoretisch auch in einem JavaScript vorkommen, ist dann allerdings fehlerhaft (es fehlt hinten ein Semikolon). Du könntest unter Umständen aber die Zeile so in einer DSL Rule verwenden.
Allerdings: Der Befehl bewirkt dann nur, dass ein Shell Script ausgeführt wird, welches im Verzeichnis /etc/openhab/scripts/ liegt und den Namen lineDetectOn.sh trägt.
ACHTUNG! Das Verzeichnis /etc/openhab/scripts/ ist NICHT für Shell Scripte gedacht. Hier dürfen streng genommen ausschließlich Dateien mit der Endung .script liegen, und diese Dateien dürfen ausschließlich jeweils ein einzelnes DSL Script enthalten, denn nur dafür ist dieses Verzeichnis da.

Also... zunächst musst Du sicherstellen, dass der Befehl curl überhaupt zur Verfügung steht, und zwar innerhalb des Containers. Ist das nicht der Fall, so scheidet der Weg über curl direkt aus.
Steht curl wider Erwarten im Container zur Verfügung, brauchst Du ein Script mit folgendem Inhalt:

Code: Alles auswählen

#!/bin/sh 
curl --digest -X PUT "http://<Name>:<Passw>@<IP>/ISAPI/Smart/linedetection" -d "<LineDetectionList xmlns=\"http://www.hikvision.com/ver20/XMLSchema\" version=\"2.0\"><LineDetection><id>1</id><enabled>true</enabled></LineDetection></LineDetectionList>"
Wo Du dieses Script hin legst, ist relativ egal, mein Tipp wäre aber der userdata Zweig (der sollte als Volume im Container gemountet sein). Der Name des Scripts wäre dann wie gehabt lineDetectOn.sh.
Nun musst Du dieses Script von openHAB aus starten, vorzugsweise über das exec Binding. Dazu legst Du zunächst ein exec Thing an (hier als Textdefinition, kannst Du aber auch über die UI erstellen):

Code: Alles auswählen

Thing exec:command:lineDetectOn "Linie einschalten" [
    command="/pfad/zur/datei/lineDetectOn.sh",
    interval=0,
    timeout=5,
    autorun=false
]
Exakt die command-Zeile trägst Du zusätzlich in die Datei /etc/openhab/misc/exec.whitelist als einzelne Zeile ein. Exakt heißt in diesem Falle, dass die Zeile identisch sein muss, kein Leerzeichen vorne dran, keines hinten dran, kein Kommentar oder sonst was. Also so:

Code: Alles auswählen

/pfad/zur/datei/lineDetectOn.sh
Natürlich musst Du /pfad/zur/datei/ jeweils mit dem echten Pfad ersetzen, und ja, es muss der absolute Pfad sein, so, wie er innerhalb des Containers gilt, zumindest, wenn Du willst, dass es funktioniert.
Hast Du mehrere exec Things, so legst Du für jedes command eine neue Zeile an.

Nun legst Du ein Switch Item an, welches mit dem run-channel verknüpft wird:

Code: Alles auswählen

Switch LineDetectArm "LineDetection scharf schalten" {channel="exec:command:lineDetectOn:run"}
Jedes Mal, wenn Du den Schalter auf ON schaltest, sollte nun das Script gestartet werden. Schau dazu in das events.log, dort sollte es einen entsprechenden Eintrag geben.

Zum Ausschalten müsstest Du nun ein zweites Script, ein zweites Thing und ein zweites Switch Item anlegen, was mir reichlich umständlich erscheint. Besser wäre also, das Script mit einem Parameter zu versehen:

Code: Alles auswählen

#!/bin/sh 
curl --digest -X PUT "http://<Name>:<Passw>@<IP>/ISAPI/Smart/linedetection" -d "<LineDetectionList xmlns=\"http://www.hikvision.com/ver20/XMLSchema\" version=\"2.0\"><LineDetection><id>1</id><enabled>$1</enabled></LineDetection></LineDetectionList>"
Dieses Script nennst Du nun linedetect.sh (ohne das On). Beim Aufruf wird $1 durch den ersten übergebenen Parameter substituiert.
Du musst dieses Script mit einem Parameter true oder false aufrufen:

Code: Alles auswählen

linedetect.sh true
linedetect.sh false
Entsprechend muss der Aufruf im exec command angepasst werden:

Code: Alles auswählen

Thing exec:command:lineDetect "Linie schalten" [
    command="/pfad/zur/datei/linedetect.sh %2$s",
    interval=0,
    timeout=5,
    autorun=true
]
die Whitelist nicht vergessen :)
Nun brauchst Du statt des Switch Items ein String Item:

Code: Alles auswählen

String commandInput "Command" {channel="exec:command:lineDetect:input"}
Und dieses Item muss entweder "true" oder "false" als Text enthalten, was am einfachsten über ein Sitemap Mapping erfolgen kann:

Code: Alles auswählen

Switch item=commandInput mappings=[true="ON",false="OFF"]
openHAB erstellt damit zwei Schaltflächen ON und OFF, welche dann jeweils true bzw. false in das Input Item schreiben, was wiederum das exec command auslöst.

Sollte der curl-Weg ausfallen (wie von mir befürchtet), dann wäre das http Binding die beste Option, denn mit dem http Binding kann man auch PUT Befehle senden. Das ist aber ein anderes Kapitel dieses Romans :)
openHAB4.1.2 stable in einem Debian-Container (bookworm) (Proxmox 8.1.5, LXC), mit openHABian eingerichtet

TheDallas
Beiträge: 7
Registriert: 28. Mär 2024 23:05
Answers: 0

Re: executeCommandLine für Curl

Beitrag von TheDallas »

Hi Udo, vielen Dank für die schnelle Rückmeldung.
Curl konnte ich schon vorher per BASH (nicht SH) als openhab ausführen. Das scheint der Container also irgendwie doch zu kennen.

Code: Alles auswählen

root@xxx:~# docker exec -ti openhab  /bin/bash
root@98282230dcef:/openhab# su openhab
openhab@98282230dcef:~$
...
openhab@98282230dcef:~/userdata/shell$ ./LineON.sh
<?xml version="1.0" encoding="UTF-8"?>
<ResponseStatus version="2.0" xmlns="http://www.hikvision.com/ver20/XMLSchema">
<requestURL>/ISAPI/Smart/linedetection</requestURL>
<statusCode>1</statusCode>
<statusString>OK</statusString>
<subStatusCode>ok</subStatusCode>
</ResponseStatus>
openhab@98282230dcef:~/userdata/shell$

Man sieht. Das klappt. Inhaltlich jetzt natürlich mit dem %1 drin Müll, aber man sieht, das CURL geht.

Das was ich aus deinen Infos für mich verständlich herausextrahieren konnte habe ich mal umgesetzt.

Also ich habe das Script erstmal umbenannt (LineOn.sh) und umgezogen in einen neuen Ordner "shell" unter userdaten (wie vorgeschlagen) überführt.
Außerdem jetzt Sh statt Bash, auch wenn Bash wie mal oben sieht hier auch geht.
Namen sind hier erstmal egal, das ist draft, weil ich das erstmal verstehen und nachbauen will, was Du mir versuchst zu erklären.
Ist aber nicht einfach :-)

Die Whitelist habe ich angepasst und über die Oberfläche das Thing angelegt.

Code: Alles auswählen

# For security reasons all commands that are used by the exec binding or transformation need to be whitelisted.
# Every command needs to be listed on a separate line below.
/userdata/shell/LineON.sh

Code: Alles auswählen

UID: exec:command:c970ce94ba
label: Befehl
thingTypeUID: exec:command
configuration:
  interval: 0
  autorun: true
  command: /userdata/shell/LineON.sh %2$s
  timeout: 15
Dann habe ich den Parameter $1 in das Curlscript gehängt und wieder 0777 vergeben.

Soweit alles gut.

Mit der Kommandozeile unter Textbasierten Anlage von Items, Things usw. hatte ich noch nicht das vergnügen und danach wird es bei mir zu hoch.
Das fängt schon bei Verständnis der Parameter an: Im Script haben wir $1 gesetzt. Beim Aufruf im Thing jetzt aber %2$s ?

Diese Parametrisierung von Skripten war der Grund, weswegen es letztlich auf TRUE und FALSE in 2 Scripten herausgelaufen wäre. Ich habe biser nicht verstanden wie diese Parametrisierung funktioniert.
Selbst aus den Dokus nicht.
ich lese deine Ausführung nun so:
Es gibt 2 Items. Das erste nimmt Parameter an (commandInput) und das andere ist ein Schalter, der jeweils einen Wert an diese CommandInput überträgt.

Mir fehlt aber da irgendwas um deinen Code in die PaperUI umzusetzen. Die Items per Code einzugeben klappt auch nicht, denn dann gibts einen Bad Request. Warum auch immer.

Daher hier mal die Items die angelegt sind als Code:
Das Item am Channel für die Ausführung:

Code: Alles auswählen

UID: exec:command:c970ce94ba
label: Befehl
thingTypeUID: exec:command
configuration:
  interval: 0
  autorun: true
  command: /userdata/shell/LineON.sh %2$s
  timeout: 15

Für den Schalter zeigt die PPUI leider keinen Code an.

Ich kann im Event sehen, das der Schalter schaltet, aber er führt nichts aus. Irgendwas ist noch nicht rund.

Benutzeravatar
udo1toni
Beiträge: 13986
Registriert: 11. Apr 2018 18:05
Answers: 222
Wohnort: Darmstadt

Re: executeCommandLine für Curl

Beitrag von udo1toni »

TheDallas hat geschrieben: 29. Mär 2024 04:10 Curl konnte ich schon vorher per BASH (nicht SH) als openhab ausführen. Das scheint der Container also irgendwie doch zu kennen.
Umso besser, dann sollte das auch kein Problem sein. Offensichtlich ist der Container weitaus allgemeiner konfiguriert, als das normalerweise üblich ist :)

Dein fehler ist schon im angegebnen Pfad im command, innerhalb des Containers liegt userdata im Verzeichnis /openhab/, also besser

Code: Alles auswählen

command: /openhab/userdata/shell/LineOn.sh %2$s
Parameter:

In einem GNU/Linux Shell Script kann man Variablen verwenden. Wenn man den Wert einer Variablen verwenden will, wird ein $ vorangestellt.. Also z.B.

Code: Alles auswählen

#!/bin/bash
MYVAR='Text'
echo $MYVAR
Es gibt einige vordefinierte Variablen, dazu gehört ein Satz Variablen, die den aufgerufenen Befehl enthalten. Ein Aufruf

Code: Alles auswählen

meinscript.sh meinWert
liefert innerhalb des Scripts meinscript.sh die beiden Variablen $0 und $1 aus, wobei $0 die Zeichenkette meinscript.sh und $1 die Zeichenkette meinWert enthält.
Entsprechend kann man damit leicht auf einen beim Aufruf mitgegebenen Parameter zugreifen. Es ist recht egal, an welcher Stelle man $0 oder $1 verwendet, die bash wird $0 bzw. $1 automatisch mit dem aktuellen Wert substituieren.

exec Binding:
Im exec Binding wird pro Command ein Thing definiert. Das Thing kennt dabei verschiedene Parameter, von denen lediglich command obligatorisch ist, alle anderen Parameter haben default Werte.
command gibt den exakten Aufruf an, der an die Standard Shell geschickt werden soll. Im Docker Container ist dies tatsächlich /bin/bash (wie ich jetzt selbst ausprobiert habe... hätte ich mir gestern einige Worte sparen können... egal...)
interval gibt die Zeitspanne zwischen zwei automatischen Aufrufen an. Default steht interval auf 60, also einmal pro Minute. Das ist aber nur sinnvoll, wenn das Thing zyklisch einen Wert abfragen soll, weil z.B. ein externes Script Daten eines Sensors ermittelt. In unserem Fall wäre ein zyklisches Ausführen des Befehls "tödlich".
timeout gibt die Zeitspanne in Sekunden an, die das exec Binding auf den erfolgreichen Abschluss des ausgeführten Befehls wartet (es kommt von der bash entweder 0 = Erfolg oder ein Wert ungleich 0 = Fehler zurück, sobald der Befehl abgearbeitet wurde).
autorun gibt an, ob der Befehl automatisch ausgeführt werden soll, wenn der input Channel einen Befehl empfangen hat.
Womit wir bei den Channels des Things angekommen sind. Es gibt exakt die Channel input, output, exit, run und lastexecution.
lastexecution liefert einen dateTime Wert, der angibt, wann das command zuletzt aufgerufen wurde.
run ist ein switch Channel und steht auf ON, solange das command läuft. Man kann mit dem Befehl ON das Command starten und mit OFF das Command abbrechen.
exit liefert den numerischen exit Wert (0 = Ok, ungleich 0 = Fehler)
output liefert die Ausgabe des Command. z.B. das command echo 'Hallo' würde als output Hallo liefern.
input schließlich setzt den zu übergebenden Parameter, damit man ein Script unterschiedlich nutzen kann. Ein Parameter ist dabei evtl. etwas irreführend, denn man übergibt hier ja eine Zeichenkette, die auch exakt so weitergegeben wird. Man kann also durch einfügen von Leerzeichen auch mehrere Parameter setzen, immer vorausgesetzt, man weiß was man tut ;)
Nun kann ein Parameter an unterschiedlichen Stellen eines Kommandos gebraucht werden. Entsprechend muss man einen Platzhalter im command Parameter setzen. Das exec Binding kennt dabei zwei Platzhalter %1 und %2.
Denke bitte daran, dass wir uns jetzt nicht in der Shell befinden, sondern in openHAB. Es ist also völlig logisch, dass die Platzhalter im exec Binding so gar nichts mit den Platzhaltern in der Shell zu tun haben.
%1 enthält den aktuellen Zeitstempel. Der Zeitstempel ist dabei der Standard Zeitstempel, man muss ihn also erst formatieren, um damit wirklich etwas anfangen zu können, z.B. %1$tY-%1$tm-%1$td ergibt für den heutigen Tag 2024-03-29, denn $t ist die Formatierung eines Zeitstempels, Y steht für das vierstellige Jahr, m für den zweistelligen Monat und d für den zweistelligen Tag im Monat.
%2 enthält den aktuellen Wert im input Channel. Auch diesen Wert muss man formatieren, in diesem Fall mit $s, also "als String".

Deshalb muss man den command Parameter mit %2$s ergänzen, um von openHAB aus über den input Channel einen Parameter an das aufgerufene Script zu senden.
Im Script selbst muss man aber $1 verwenden, denn das ist die lokale Variable, welche den ersten Parameter des Aufrufs enthält.

Eventuell siehst Du jetzt etwas klarer :)
openHAB4.1.2 stable in einem Debian-Container (bookworm) (Proxmox 8.1.5, LXC), mit openHABian eingerichtet

Benutzeravatar
udo1toni
Beiträge: 13986
Registriert: 11. Apr 2018 18:05
Answers: 222
Wohnort: Darmstadt

Re: executeCommandLine für Curl

Beitrag von udo1toni »

Achso ... Wenn Du openHAB3 verwendest, heißt die UI Main UI, nicht Paper UI.

Du brauchst mit der übergebenen Variablen (über den input Channel) nur ein String Item, welches mit dem Input Channel gekoppelt ist. Sendest Du den Befehl true, so wird das Script mit dem Paramter true aufgerufen, sendest Du den Befehl false, wird das Script mit dem Parameter false aufgerufen.

Wenn Du die Main UI verwenden willst, musst Du Dir einfach ein passendes Widget heraus suchen, mit dem Du jeweils die beiden Strings als Befehl absetzen kannst.
So auf die Schnelle... (ich arbeite bisher fast gar nicht mit der Main UI, weil ich mit der Sitemap alles habe, was ich brauche) wäre sowas möglich:

Code: Alles auswählen

component: oh-cell
config:
  header: Umschalten
  action: toggle
  actionItem: switchString
  actionCommand: "true"
  actionCommandAlt: "false"
Allerdings siehst Du so den Zustand nicht. das actionItem ist dann das String Item, welches mit dem input Channel verlinkt ist. Weil openHAB nach einemn gesendeten Kommando automatisch ein postUpdate hinterher schickt, nimmt das Item jeweils den Status "true" oder "false" an, welshalb der Toggle-Mechanismus auf der oh-cell direkt funktioniert.
openHAB4.1.2 stable in einem Debian-Container (bookworm) (Proxmox 8.1.5, LXC), mit openHABian eingerichtet

TheDallas
Beiträge: 7
Registriert: 28. Mär 2024 23:05
Answers: 0

Re: executeCommandLine für Curl

Beitrag von TheDallas »

Klasse. Das mit dem Pfad war sehr hilfreich. Daran lag es. Der Rest kam mit den Fehlermeldungen im Eventlog dann raus :D
Das schalten erfolgt direkt per Blocky indem ich dem Item des Parameterchannels ein true oder false mitgeben und dann den Ausführungsschalter einmal auf ON stelle. Dann läuft das Script los.
Ich wollte nochmal erwähnen (das kam oben nicht so ganz deutlich raus), das mal in der Whiteliste 1:1 die COMMAND Zeile aus dem Thing mitgibt.
Also inkl. der Parameter und dem vollständige Pfad.

Das war eine SUPERHILFE und vielen Dank für die ausführliche Erklärung. Quasi eine Grundlagenschulung "Parameterübergabe in Batch-Scripten". :D
Mal sehen, wie ich das in anderen Fällen für mich nutzen kann. Die Kamera kann ja auch Alarmmeldungen geben :-).. .. Aber das heb ich mir für ein anderes mal auf. . .

Benutzeravatar
udo1toni
Beiträge: 13986
Registriert: 11. Apr 2018 18:05
Answers: 222
Wohnort: Darmstadt

Re: executeCommandLine für Curl

Beitrag von udo1toni »

Prima, dass es jetzt funktioniert.

Den "Ausführungsschalter" solltest Du nicht benötigen, dafür gibt es ja den autorun Parameter im Thing.
TheDallas hat geschrieben: 29. Mär 2024 19:08 Ich wollte nochmal erwähnen (das kam oben nicht so ganz deutlich raus), das mal in der Whiteliste 1:1 die COMMAND Zeile aus dem Thing mitgibt.
Also inkl. der Parameter und dem vollständige Pfad.
Hmm...
udo1toni hat geschrieben: 29. Mär 2024 01:38 Exakt die command-Zeile trägst Du zusätzlich in die Datei /etc/openhab/misc/exec.whitelist als einzelne Zeile ein. Exakt heißt in diesem Falle, dass die Zeile identisch sein muss, kein Leerzeichen vorne dran, keines hinten dran, kein Kommentar oder sonst was. Also so:

Code: Alles auswählen

/pfad/zur/datei/lineDetectOn.sh
und weiter unten:
udo1toni hat geschrieben: 29. Mär 2024 01:38 Entsprechend muss der Aufruf im exec command angepasst werden:

Code: Alles auswählen

Thing exec:command:lineDetect "Linie schalten" [
    command="/pfad/zur/datei/linedetect.sh %2$s",
    interval=0,
    timeout=5,
    autorun=true
]
die Whitelist nicht vergessen
Identisch heißt tatsächlich identisch, nicht, "der Pfad kann weg gelassen werden" oder gar "den Parameterteil kann man weg lassen".
;)
Mag aber sein, dass dieser Teil meines Romans einfach untergegangen ist, das kann schon mal vorkommen...
openHAB4.1.2 stable in einem Debian-Container (bookworm) (Proxmox 8.1.5, LXC), mit openHABian eingerichtet

TheDallas
Beiträge: 7
Registriert: 28. Mär 2024 23:05
Answers: 0

Re: executeCommandLine für Curl

Beitrag von TheDallas »

Je genau. Da hab ich mehr bzw. weniger reininterpretiert. Danke.

Antworten