Sonntag, 25. Februar 2018

Windows Server Freigabe auf dem Raspberry verbinden

Ich will hier kurz zeigen wie der Zugriff auf eine Windows (Samba) Freigabe unter Raspbian eingerichtet wird. Unter Linux braucht man einen Ort wo man ein Laufwerk "hin" mounten kann, wir erzeugen also einen Pfad mit entsprechenden Berechtigungen:
sudo mkdir /media/daten
sudo chmod 777 /media/daten
Da ein Windows Server nicht ohne weiteres einen Zugriff ohne Login ermöglicht und wir die Konto und Passwort nicht immer eintippen wollen, werden die Informationen entsprechend abgelegt. Der erste Befehl öffnet den Editor und man schreibt 3 Zeilen in diese Datei
nano ~/.smcredentials
username=UserName
password=Userpassword
domain=ServerName
Nun fehlt noch der mount Eintrag in der fstab, es wird nicht automatisch gemountet, alle User dürfen mounten und es ist beschreibbar:
sudo nano /etc/fstab
//ServerName/daten /media/daten cifs noauto,users,credentials=/home/pi/.smcredentials 0 0
Jetzt können wir das Laufwerk einbinden und den Inhalt anzeigen. Das funktioniert ohne sudo, allerdings darf nur der User Pi in unserem Fall das hinterlegte Konto verwenden.
mount /media/daten
ls -l /media/daten
umount /media/daten
Hinweis
Das Windows Laufwerk wird auf diese Art nicht automatisch beim Start angebunden. Das Netzwerk ist zu diesem Zeitpunkt noch nicht verfügbar und der automount über die fstab würde nicht funktionieren. Ist für mich kein Nachteil, weil es jederzeit in einem Script per mount Befehl angebunden werden kann.

Sicherheit

Wir haben oben das Konto für den Zugriff auf smb im User Home Directory abgelegt, man kann es auch frei verfügbar für alle Benutzer machen. Eventuell z.B. unter /usr/.smcredentials
sudo nano /usr/.smcredentials
Der Inhalt der Datei ist wie oben. Die Daten in der fstab müssen natürlich angepasst werden.

Um den Inhalt der Datei im User Home Directory vor fremden Zugriffen zu schützen, kann man sie zusätzlich absichern.
chmod 600 ~/.smcredentials

Nachtrag

Man kann auch jederzeit  mit einem simplen "Zweizeiler" ein Windows Laufwerk als sudo mounten, password= kann man weglassen, dann wird es abgefragt.

sudo mkdir /media/daten
sudo mount -t cifs -o username=UserName,password=Passwort //ServerName/Freigabe /media/daten

Zum nachlesen: Ubuntu Wiki

Donnerstag, 8. Februar 2018

In FHEM externe Programme aufrufen

Man will ja immer mal in FHEM ein "externes" Programm aufrufen. Dabei gibt es verschiedenste Eigenheiten und Situationen, die ich hier mal versuche zu erfassen.
Alle Einzelbefehle sind direkt in der FHEM Kommandozeile nachvollziehbar. Dabei bitte den Code exakt so übernehmen wie ich ihn in den Codefenstern bereitstelle.

Ich habe die folgende Schritte ausprobiert und es sind meine Erkenntnisse. Es kann sein, dass sich Dinge ändern oder ich etwas nicht richtig verstanden habe. Meine Ausführungen sind nicht vollständig!

Empfehlung für externe Programme

Ich würde die Shell Code Zeile immer so kurz wie möglich halten und komplizierte Konstrukte immer in ein Script schreiben! Mit der unterschiedlichen Interpretation von bestimmten Zeichen innerhalb von "" oder ' ' kommt man leicht in scheinbar unlösbare Situationen.

  • Der FHEM "programm" Aufruf schreibt die Ausgabe immer ins Logfile, will man das nicht muss man die Ausgabe auf Systemebene verhindern, eine Umleitung beim Aufruf funktioniert nicht. Der Befehl wird immer im Hintergrund ausgeführt. Die Übergabe von FHEM System Variablen ist möglich, die Mischung mit Perl Code ist schwierig.
  • Beim system() Aufruf kann man die Ausgabe im Logfile durch eine Umleitung beim Aufruf mit >/dev/null verhindern. Der Befehl kann "nicht blockierend" ausgeführt werden.
  • Bei qx() kann man die Ausgabe flexibel auswerten oder verwerfen. Der Befehl wird immer blockierend ausgeführt.

Die FHEM Methode "Programmzeile"

Das aufgerufene Programm wird im Hintergrund (also non blocking) ausgeführt. Ausgaben landen im Logfile. Es gibt keine Rückgabe. Pipe wird nicht unterstützt, Dateien werden aber leer angelegt.
Der Befehl
"echo echo test > text.txt"
erzeugt eine leere Datei text.txt im FHEM Homedir (meistens /opt/fhem) und den Eintrag echo test in der Logdatei.
Will man diese Methode innerhalb eines Perl Blockes mittels {fhem("")} verwenden, muss man beides kombinieren aber mit einfachen Anführungszeichen schreiben:
{fhem('"echo echo test > text.txt"')}

Perl

Perl kennt mindestens drei Methoden (system, qx und exec) um einfach externe Programme aufzurufen, davon ist exec normalerweise unbrauchbar!

Diese beiden Befehlszeilen liefern das gleiche Ergebnis wie in der linux Shell, eine Datei text.txt im Homedir mit dem Inhalt echo test
{qx /echo echo test > text.txt/}
{system("echo echo test > text.txt")}
Fehlerausgaben würden ins Logfile geschrieben. Rückgabewerte werden ins Browserfenster geschrieben. Da es vom Programm hier keine Rückgabe gibt, liefert qx() in dem Fall keine Rückgabe, system() liefert aber in FHEM immer den Wert -1 zurück. Eigentlich sollte es den Fehlercode (0 kein Fehler, ungleich 0 ist Fehler) zurückliefern.

Die folgende, andere Variante liefert für qx() nichts im Log aber die Ausschrift test im Browserfenster zurück.
{qx /echo test/}
Für system() liefert diese Variante test im Logfile und eine -1 im Browserfenster zurück.
{system("echo test")}
Der system() Aufruf muss nicht aus einem String bestehen, man kann das Programm und die Argumente in getrennten Strings übergeben.
{system("programm","Arg1","Arg2")}

Ein Aufruf mit exec() beendet den laufenden Prozess und startet einen Neuen. Er kehrt nicht zurück. Würde man diesen Befehl in FHEM verwenden, wird FHEM beendet und das neue Programm ausgeführt.

Praktisches Beispiel mit Übergabe von Werten

Der folgende Code ist für die Raw Definition und nicht für andere Eingaben!
Nicht wundern, auf Grund des letzten Befehls, der die Script Datei erzeugt, bekommt man als Quittung nach Execute Commands eine -1.
Mit dem Code Block erzeugt man
  • ein Dummy für Werte, 
  • ein notify welches die Werte Änderung an ein Shell Programm übergibt und wieder Werte setzt. 
  • ein Shellprogramm welches beide Übergabewerte multipliziert.

defmod ext_dummy dummy
attr ext_dummy readingList Wert
attr ext_dummy room extern
attr ext_dummy setList 0 1 2 3 4 5 6 7 8 9
setreading ext_dummy Wert 1

defmod ext_notify notify ext_dummy:[0-9] {\
 my $a = ReadingsVal("ext_dummy","Wert","2");;\
 my $b = qx/bash ext_Script.sh $EVENT $a/;; \
 fhem("set ext_dummy Wert $b")\
}
attr ext_notify room extern

{system("echo 'echo \$((\$1*\$2))' > ext_Script.sh")}

Erklärung

Der Dummy hat ein zusätzliches Reading Wert mit dem Anfangswert 1. Über setList kann man den Status auf Werte von 0-9 setzen. Das attr readingList ermöglicht das setzen des Readings mit einfachem set Befehl.

Das notify reagiert nur auf Events vom Dummy mit einstelligen Zahlen von 0-9. Es liest den Wert des Readings Wert vom Dummy aus. Als nächstes wird das Shell Script ausgeführt, als Parameter wird der Status des Dummy ($EVENT) und der ausgelesene Wert in $a übergeben. Das Script multipliziert beide Werte und das Ergebnis wird in $b geschrieben. Das Reading Wert im Dummy wird mit diesem Wert überschrieben.

In dem system() Befehl, der uns das Shellscript erzeugt, müssen die Dollarzeichen so \$ geschützt werden, sonst würden sie als Perl Variablen interpretiert und erzeugen eine Fehlermeldung. Die \ erscheinen nicht im Script.

3 Syntax Ebenen

  • Ab der { befinden wir uns auf Perl Ebene, die Verwendung von set magic [Geraet:Reading] ist hier nicht möglich, man muss ReadingsVal usw. verwenden.
  • Nach qx/ befinden wir uns auf System Ebene, es werden Shell Befehle ausgeführt.
  • Ab fhem(" befinden wir uns wieder auf FHEM Ebene, es werden FHEM Befehle ausgeführt. set magic wäre innerhalb dieser Ebene möglich.
Das notify Beispiel oben ist als Beispiel konstruiert. Die eigentliche Aufgabe könnte ohne externes Script mit diesem qx() Aufruf erledigt werden.
qx/echo \$(($EVENT * $a))/

Auflösung von Variablen Namen

Etwas vereinfacht gilt in Perl: Zwischen "" werden bestimmte Zeichen interpretiert zwischen ' ' erfolgt diese Sonderbehandlung nicht. Zur Erläuterung nur paar Beispiele:

  • Text der mit $ oder @ beginnt wird als Perl Variable/Array aufgelöst/ersetzt. Will man die Auflösung verhindern, muss man Zeichen wie $ oder @ "schützen" und so schreiben "\$a , name\@domain.de". 
  • Damit " nicht als Textende erkannt wird: \" . 
  • Den \ muss man selbst schützen: \\.
  • Mit \ werden auch Steuerzeichen eingeleitet. z.B. \n ist ein Zweilenumbruch.
  • Immer dran denken: Den Befehlstrenner ;  müssen wir in der FHEM Eingabezeile auch verdoppeln/schützen.


Die folgenden beiden Codezeilen bewirken die gleiche Ausgabe.
{my $a = "test";;qx "echo $a"}
{qx 'a=test;;echo $a'}
Beim Ersten ist die Variable $a eine Perl Variable und der Inhalt wird von Perl zur Shell übergeben.
Beim Zweiten ist die Variable $a eine Shell Variable und wird erst in der Shell erzeugt. Es wird nichts übergeben.
Folgende Zeilen geben alle den letzten Fehlercode der Shell aus -> $?, der sollte 0 sein. Alle Zeilen sind äquivalent verwendbar, sie verwenden eine Shellvariable.
{qx "echo \$?"}
{qx 'echo $?'}
{qx /echo \$?/}
{qx (echo \$?)}
{`echo \$?`}
Mit einer Ausnahme könnte auch eine Perl Variablen übergeben werden (anstatt \$? $a).
Achtung: Die Verwendung von qx("") führt zu einer Fehlermeldung, da ist offenbar ein String Terminator zu viel.

Damit es nicht zu leicht wird, kann man die String Terminatoren natürlich auch schachteln. Für die Auflösung von Perl Variablen ist dabei die äußere Klammer bestimmend. Das erste Beispiel gibt Hallo Welt aus, das zweite nur Hallo.
{my $a = "Welt";;qx "echo 'Hallo $a'"}
{my $a = "Welt";;qx 'echo "Hallo $a"'}
Die Variable $a wird im zweiten Fall nicht aufgelöst, sondern als Shell Variable interpretiert.

Eine komplexere Zeile zeigt, dass es offenbar auch anders sein. Dieses Beispiel erzeugt ein Shell Script mit URL und Übergabevariable in der FHEM Kommandozeile.
{system('echo "wget --quiet - \"http://192.168.178.65/esp8266?max=Aussentemperatur+\$1+C\"" > ext_Script2.sh')}
Im Zweifelsfall hilft nur probieren!

Verkettung / concatenation

Perl kennt noch die String Verkettung mit Punkten. Ich nehme dazu das Beispiel von oben.
Im ersten Fall ist die Ausgabe identisch zur Version oben, im zweiten Fall wird der Inhalt der Variablen noch in Anführungszeichen ausgegeben soll:
{my $a = "test";;qx "echo ".$a}
{my $a = "test";;qx "echo ".'"'.$a.'"'}
Welche Methode man verwendet ist häufig Geschmacksache, manchmal aber auch vom Inhalt der Strings und dem gewünschten Ergebnis abhängig.

Achtung blockierend

Die Ausführung von externen Programmen mit Perl Aufrufen wird FHEM für die Laufzeit dieses Programmes unterbrechen/anhalten. Das kann unvorhersehbare Folgen haben.
Der kurzer Perl Test erzeugt für 5 sec "Warten" und dann eine Ausschrift.
{sleep 5;; return "Das war eine Pause"}
{qx "echo Das war;;sleep 5;;echo eine Pause"}
Der zweite Test führt auf Shell Ebene ein sleep aus, beide echo Befehle werden aber erst am Ende über qx an den Browser zurück gegeben.
Für einen weiteren Test erzeugen wir uns wieder ein Shell Script.
{system("echo 'sleep \$1' > ext_Pause.sh;;echo 'echo \$1 sec Pause' >> ext_Pause.sh")}
Dies erwartet eine Zahl und macht dann eine entsprechende Pause. Mit einem qx() Aufruf können wir das Script testen.
{qx "echo Das war;;bash ext_Pause.sh 4"}
Nach meinen Informationen, lässt sich das Verhalten mit qx() nicht ändern. Die Befehle werden immer seriell ausgeführt, Perl wartet auf die Rückgabe!
Der system() Aufruf hat andere Möglichkeiten. Bei ihm kann man mit dem & Zeichen am Ende (für die Shell typisch) die Ausführung im Hintergrund starten. Die Ausgabe im Script landet damit nach der Pause im Logfile von FHEM.
{system ("bash ext_Pause.sh 5 &")}

Aufräumen

Um die hier beim Testen erzeugten Geräte und Dateien wieder zu entfernen, kann man die beiden Zeilen einzeln in der Kommandozeile oder zusammen in der Raw Definition ausführen.
delete ext_.*
{system ("rm ext_*.sh >/dev/null")}

Ganz zum Schluss

Mal noch als "Merker" der Tipp aus dem Forum. Den baue ich bei Gelegenheit mal noch aus.

Donnerstag, 1. Februar 2018

Setup FHEM

FHEM installieren ist eigentlich ziemlich einfach. Bevor meiner folgenden Anleitung gefolgt wird, sollte man sich unbedingt hier über den aktuellen Stand informieren. Mit minimalem Aufwand kann man das Setup von hier durchführen.
Alle meine Setup Schritte erfordern erhöhte Rechte. Also bitte alles in einer "sudo su" Session ausführen!
Und so setze ich diese Aufgabe praktisch in einem Script um:
# von debian.fhem.de installieren - siehe aktuelle Anleitung dort https://debian.fhem.de/
wget -qO - http://debian.fhem.de/archive.key | apt-key add -
echo "deb http://debian.fhem.de/nightly/ /" >> /etc/apt/sources.list
apt-get update
apt-get -y install fhem

Den zusätzlichen Eintrag in der sources.list entfernt das Setup Programm von FHEM. Alle benötigten und sinnvollen debian Pakete werden automatisch mit installiert. Ist ein systemd System vorhanden wird auch der Dienst für FHEM entsprechend installiert.

Damit ist man grundlegend fertig! Aber ein paar Dinge fehlen noch!

Das aus meiner Sicht wichtigste aktuelle Feintuning des frischen FHEM ist die Deaktivierung der USB Stick Erkennung. Dieser kleine direkte Patch auf diese Art ist nur unmittelbar nach der Installation zulässig! Später muss man diese Attribute bitte über die FHEM Oberfläche setzen.
# Den USB Check besser abschalten
/opt/fhem/fhem.pl 7072 "attr initialUsbCheck disable 1"
/opt/fhem/fhem.pl 7072 "save"

Weitere Attribute die man irgendwie braucht, kann man einfach über die Raw definition setzen. Die findet man in jeder Detail Ansicht von Definitionen ganz unten in der Mitte als Link.
attr global backup_before_update 1
attr global commandref modular
attr global title FHEM-Name
attr global sendStatistics onUpdate
attr global latitude 51.xxxxxxxxxxxxx
attr global longitude 12.xxxxxxxxxxxxx
attr global language de
attr WEB JavaScripts codemirror/fhem_codemirror.js
attr WEB codemirrorParam { "theme":"blackboard", "lineNumbers":true }
attr WEB plotfork 1
attr WEB longpoll websocket

Fast fertig - allowed

ToDo

Speziell - Mplayer Ersatz

Ich arbeite viel mit Ansagen unter anderem mit Text2Speech. Aktuelle verwende ich dieses Script um mplayer zu ersetzen. In der Quelle gibt es auch noch eine Variante mit setzen des AUDIODEV.
#!/bin/sh
# die Kommentarezeichen vor echo  fuer den Test entfernen
# echo Parameteranzahl $# > /tmp/mplay.txt

# falls volume nicht vorhanden = 1
volume=1
# Alsa Device, Volume und Dateinamen ermitteln
while [ $# -gt 0 ]
do
# echo $1 >> /tmp/mplay.txt
 if [ $1 = -ao ]
 then
  shift
#   echo $1 >> /tmp/mplay.txt
  device=$(echo $1|cut -d= -f2)
#   echo $device >> /tmp/mplay.txt
 elif [ $1 = -volume ]
 then
  shift
#  echo $1 >> /tmp/mplay.txt
   if [ $1 -lt 100 ]
   then
    volume=0.$(($1))
   fi
 elif [ -e $1 ]
 then
  file=$1
 fi
 shift
done
#echo $volume $file >> /tmp/mplay.txt
play -q -v $volume $file
exit 0

Dieses Script muss für FHEM ausführbar sein. Wenn man es im aktuellen Homdedir liegen hat (User pi) dann kann man es wie folgt als User fhem an Ort und Stelle kopieren. Automatisch mit den richtigen Rechten und Besitz.
sudo -u fhem cp mplayer.sh /opt/fhem/
chmod +x /opt/fhem/mplayer.sh
Zur Vorbereitung sind zwei Pakete erforderlich.
sudo apt-get install libdigest-sha-perl mp3wrap
In Text2Speech kann man einfach den Aufruf durch dieses Script ersetzen. Minimal kann man Text2Speech so definieren:
define MyTTS Text2Speech default
attr MyTTS TTS_MplayerCall /opt/fhem/mplayer.sh

Weitere Software Pakete installieren

Viele der Module in FHEM erfordern zusätzliche Software auf System Ebene.
Aus meiner Liste sind bisher folgende debian Pakete noch nicht installiert
sudo apt-get install avrdude libcrypt-cbc-perl libcrypt-ecb-perl libcrypt-rijndael-perl libdigest-md5-perl libimage-info-perl liblwp-useragent-determined-perl libmime-base64-perl libnet-telnet-perl libnet-upnp-perl libsoap-lite-perl libxml-parser-lite-perl libxml-parser-perl sendemail
Diese Pakete müssen mit cpan installiert werden, es gibt kein debian Paket.
sudo cpan install Crypt::Rijndael_PP

Das folgende Script ermittelt ob die Pakete schon installiert sind und gibt eine Liste der noch nicht installierten Pakete aus. Der Aufruf erfolgt mit Übergabe der zu ermittelnden Pakete.
bash software.sh paket1 paket2 paket3
Script software.sh
# Eine von beiden Zeilen auskommentieren
#echo -n "cpan install " >notinstalled.sh
echo -n "apt-get install " >notinstalled.sh
while [ $# -gt 0 ]
do
# eine von beiden Zeilen auskommentieren
#perl -M$1 -e '' 2>/dev/null
dpkg -s $1 &> /dev/null

if [ $? -eq 0 ]; then
    echo $1" is installed!"
else
    echo $1" is not installed!"
    echo -n $1" " >>notinstalled.sh
fi
shift
done
Ich habe mal noch eine Variante gebaut um das Script für den mplayer Ersatz direkt aus FHEM heraus zu erzeugen. Für die Raw Definition:
{my $file = "mplayer.sh";;\
my @content=('#!/bin/sh',\
'volume=1','while [ $# -gt 0 ]',\
'do',' if [ $1 = -ao ]',\
" then\n  shift",\
'  device=$(echo $1|cut -d= -f2)',\
' elif [ $1 = -volume ]',\
" then\n  shift",\
'   if [ $1 -lt 100 ]',\
'   then','    volume=0.$(($1))',\
'   fi',\
' elif [ -e $1 ]',\
' then','  file=$1',\
" fi\n shift\ndone",\
'play -q -v $volume $file',\
'exit 0'\
);;FileWrite($file, @content);;\
qx "chmod +x $file"}