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. Im Raspbian Image (Lite vom November 2018) sind alle Voraussetzungen vorhanden, man muss nichts weiter installieren. In anderen Systemen kann es sein, dass man das debian Paket cifs-utils nachinstallieren muss.  Openmediavault hat z.B. bei aktiviertem Samba nicht automatisch cifs-utils installiert.
Für den praktischen Gebrauch muss man lediglich 3 Dinge konfigurieren, für einen schnellen Test  kann man es mit einem Zweizeiler machen (siehe weiter unten):

Pfad

Unter Linux braucht man einen Ort wo man ein Laufwerk "hin" mounten kann, dabei wird
\\Servername\Sharename auf den lokalen Pfad /DirName/DirName gemappt.
Man erzeugt einen Pfad, am Besten mit einer "Wurzel", einem Verzeichnis unter dem alle Netzlaufwerke eingehängt werden:
mp='/mnt/Sicherung'
sudo mkdir -p ${mp}
Die Option -p erzeugt alle notwendigen Ordner falls sie noch nicht existieren.

Benutzerkonto

Da ein Windows Server nicht ohne weiteres einen Zugriff ohne Login ermöglicht und wir username und password nicht immer eintippen wollen, werden die Informationen entsprechend abgelegt.
Dabei gibt es drei Möglichkeiten. Ja nach Vorliebe verwendet man einen Editor (nano) oder direkte Shellbefehle
1. persönliche Datei (notwendiger Inhalt siehe nächster Codeblock):
fcred='.smbcredentials'
nano $fcred
chmod 600 $fcred
Die Datei ist damit nur vom Eigentümer lesbar!

2. allgemeine Datei:
Wenn keine Domain verwendet wird, kann der Eintrag domain entfallen oder freibleiben.
fcred='.smbcredentials'
export datei="/usr/${fcred}" 
sudo bash -c "cat >${datei}" <<'EOF'
username=UserName
password=Userpassword
domain=
EOF
Man braucht pro Konto eine credentials Datei. Diese Datei muss von allen Usern lesbar sein, die mounten wollen. Die gerade angelegte Datei ist aber auch von "Jedem" lesbar, will man das eingrenzen, kann man das so machen:
sudo -s <<EOI
  addgroup cifs
  usermod -aG cifs fhem
  usermod -aG cifs pi
  chown root:cifs ${datei}
  chmod 640 ${datei}
EOI
Hinweis: Die Änderung der Gruppenmitgliedschaft wirkt erst nach Neuanmeldung!

3. anstatt extra Datei (credentials=Datei) schreibt man die Kontodaten direkt in die fstab Zeile, dort ohne Zeilentrennung!
username=ano,password=nym

fstab

Nun fehlt noch der mount Eintrag in der fstab. In meinem Beispiel wird nicht automatisch gemountet, alle User dürfen mounten und das Laufwerk ist beschreibbar.
sudo bash -c "echo //ServerName/daten ${mp} cifs noauto,users,credentials=/usr/.smbcredentials 0 0 >> /etc/fstab"

Verwendung

Jetzt können wir das Laufwerk einbinden, den erfolgreichen mount anzeigen und den Inhalt anzeigen. Das funktioniert ohne sudo, der Benutzer braucht Leserechte auf die credentials Datei.
mount ${mp}
df
ls -l ${mp}
umount ${mp}

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.

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 -p /mnt/daten
sudo mount -t cifs -o username=UserName,password=Passwort //ServerName/Freigabe /mnt/daten
# Bei smb1 Servern und ab debian stretch
sudo mount -t cifs -o username='UserName',password='Passwort',vers=1.0 //ServerName/Freigabe /mnt/daten

Bei Problemen kann man sich die verwendeten Dateisysteme bzw. die Unterstützung des Kernels  anzeigen lassen.
cat /proc/filesystems
ls /lib/modules/$(uname -r)/kernel/fs

Zum nachlesen: Ubuntu Wiki

SMB Protokoll

smb Version 1 wird mittlerweile als extrem unsicher eingestuft. Deswegen wird es aus modernen Systemen "entfernt".
Allerdings ist es in vielen Geräten nach wie Standard und eine Unterstützung von smb 2 oder 3 nicht implementiert (Fritzbox NAS, Sonos ...)
Windows hat ab Version 1709 das Protokoll entfernt, man muss es bei Bedarf mit "Windows Apps und Features" nachinstallieren (Neustart erforderlich)
debian stretch hat es offenbar als default aus der Aushandlung herausgenommen, mann muss es mit der cifs Option "vers=1.0" beim mount Befehl explizit hinzufügen, wenn der Server nur smb1 unterstützt.

Donnerstag, 8. Februar 2018

In FHEM externe Programme aufrufen

Es gibt viele Möglichkeiten ein externes Programm aufzurufen. Ich habe Einiges ausprobiert und hier sind meine Erkenntnisse. Es kann sein, dass sich Dinge ändern oder ich etwas nicht richtig verstanden habe. Meine Ausführungen sind mit Sicherheit nicht vollständig!
Dieser Beitrag endet mit konkreten Beispielen, die kann man exakt so übernehmen. Dabei sind die Anführungszeichen in den Codefenstern richtig und wichtig!
Empfehlung: Komplizierte und lange Codezeilen immer in ein Script oder in eine Perl Sub schreiben! Mit Sonderzeichen, doppelten und einfachen Anführungszeichen kommt man schnell in sehr unübersichtliche, beinahe unlösbare Situationen. Immer die mögliche Blockade von FHEM im Hinterkopf haben!

Aufruf

Es gibt drei FHEM Kommandotypen, sie unterscheiden sich in der Verwendung der "Klammer"
  • FHEM Befehl - keine Klammer
  • "Shell Befehl" - doppelte Anführungszeichen (blockiert FHEM nicht)
  • {Perl Befehl} - geschweifte Klammer (kann FHEM blockieren)
Für externe Programmaufrufe eignen sich die beiden letzeren.
Perl kennt in sich wieder drei Funktionsaufrufe für externe (nicht Perl) Programme.
  • system() - liefert in FHEM immer "-1" zurück, Ausgaben landen im Logfile
  • qx() - liefert die Ausgabe zurück, bei Aufruf in der FHEM Oberfläche landet diese im Browserfenster
  • exec() - in FHEM nicht sinnvoll! Beendet FHEM und das aufgerufene Programm wird ausgeführt.

Kurze Fingerübung

Der Code ist für die FHEM Kommandozeile!
"echo test"
Liefert nichts im Browserfenster, schreibt test ins Logfile.
Innerhalb von Perlcode kann man das auch so verwenden:
{fhem("\"echo test\"")}
Perl system() liefert -1 im Browserfenster und schreibt test ins Logfile.
{system("echo test")}
Variante von system() mit anderer Schreibweise: {system("programm","Arg1","Arg2")} und man bekommt das gleiche Ergebnis.
{system("echo","test")}
Perl qx() liefert test im Browserfenster und schreibt nichts ins Logfile.
{qx(echo test)}
Tipp: Diese Version verwende ich gern, um in der FHEM Oberfläche mal schnell etwas im System abzufragen. Man muss dazu nicht immer ins Terminal wechseln.

Achtung: Die Pipeline (> |) der Standardausgabe funktioniert nur bei {system("echo system > test.txt")} und {qx(echo qx > test.txt)}, bei "echo kommando > test.txt" versagt sie aber!

Zwischenspiel

Wie bekomme ich Text in eine Datei? Am Besten mit der Perlfunktion FileWrite
{FileWrite("test.txt","Inhalt")}
Das Ergebnis anschauen:
{qx(cat test.txt)}

Beispiel mit Wertübergabe von FHEM

Dieses mehrzeilige Beispiel ist für die Raw Definition!
Mit dem Code Block erzeugt man:
  • ein Shellprogramm (echo $(($1*$2))) welches beide Übergabewerte multipliziert.
  • ein Dummy mit dem Reading Wert,
  • ein notify welches den Wert und state an das Shell Programm übergibt und die Rückgabe wieder den Wert schreibt.
{FileWrite("ext_Script.sh","echo \$((\$1*\$2))")}

define 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

define ext_notify notify ext_dummy:[0-9] {\
 my $a = ReadingsVal($NAME,"Wert","2");;\
 my $b = qx(bash ext_Script.sh $EVENT $a);;\
 fhem("set $NAME Wert $b")\
}
attr ext_notify room extern
Diese Beispiel ist sicher etwas konstruiert, es zeigt aber verschiedenen Ebenen (FHEM, Perl, Shell) und auch die Übergabe von Werten über Variablen. Dabei gibt es einiges zu beachten:
Sonderzeichen in Textketten werden innerhalb einfacher und doppelter Anführungszeichen je nach Ebene unterschiedlich behandelt! Bei doppelten Anführungszeichen gilt:
  • Text der mit $ oder @ beginnt wird als Perl Variable/Array aufgelöst/ersetzt. Man kann/muss die Auflösung mit dem vorangestellten \ verhindern/schützen: "\$a , name\@domain.de". 
  • Ist das Anführungszeichen selbst nur Text: \".
  • Den \ muss man selbst schützen: \\.
  • Mit \ werden auch Steuerzeichen eingeleitet. z.B. \n ist ein Zeilenumbruch.
Immer dran denken: Den Befehlstrenner ; müssen wir in der FHEM Eingabezeile auch verdoppeln/schützen!

Beispiele zur Verwendung von Variablen

Zwei Perlzeilen und zwei Ebenen. Die Ausgabe ist gleich, die Variablen unterschiedlich.
  1. $a ist eine Perl Variable und der Inhalt wird von Perl zur Shell übergeben.
  2. $a ist eine Shell Variable und wird erst in der Shell erzeugt. Es wird nichts übergeben.
{my $a = "test";;qx "echo $a"}
{qx 'a=test;;echo $a'}
Der Syntax von qx ist sehr variabel, alle Zeilen bewirken das Gleiche: den letzten Fehlercode der Shell ausgeben
{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. Die Variable $a wird im zweiten Fall nicht aufgelöst, sondern als Shell Variable interpretiert.
{my $a = "Welt";;qx "echo 'Hallo $a'"}
{my $a = "Welt";;qx 'a=FHEM;;echo "Hallo $a"'}
Ausnahmen? Verwirrung?
Im folgenden Beispiel muss der Name der Variable im Shellscript auch geschützt werden.
Im Zweifelsfall hilft nur probieren!
{system('echo "wget --quiet - \"http://192.168.178.65/esp8266?max=Aussentemperatur+\$1+C\"" > ext_Script2.sh')}

Empfehlung: Befehle kapseln

Die Verwendung von Trennzeichen im String und die bisher genannten Interpretationen von Variablen Namen in den Umgebungen führt mich zu den unbedingten Empfehlung Befehle zu kapseln! Entweder in Stringvariablen in Perl oder in Scriptdateien für andere Sprachen. Es spart jede Menge Zeit und Nerven!

Verkettung / concatenation

Perl kennt noch die String Verkettung mit Punkten.
Im ersten Fall ist die Ausgabe identisch zur Version oben, im zweiten Fall wird der Inhalt der Variablen noch in Anführungszeichen ausgegeben:
{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 und damit das Mittel der Wahl.

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.
{FileWrite("ext_Pause.sh","sleep \$1\necho \$1 sec Pause")}
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.
Code Block

Donnerstag, 1. Februar 2018

Setup FHEM

Es gibt einen aktuellen Artikel. Detail werden aber weitestgehend hier erklärt.

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 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.
Nachtrag: Mittlerweile ist der Aufruf für play direkt mit Parametern im MplayerCall eingebaut. Das Script ist also überflüssig.
Aktuell 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

Man kann auch nur die Perl Module ermitteln, die im System installiert sind.
Aus diesem Beitrag mal abgeleitet könnte man alle Module ausgeben, die in einem System vollständig installiert sind:
dpkg -l |grep ^ii| awk '{ print $2 }'
Oder nur die Perl Module
dpkg -l |grep -i '\-perl'| awk '{ print $2 }'

Diese List könnte man von Zeilenumbrüchen befreien:
|tr -d "\r"|tr "\n" " "

...und in ein ähnliches Script wie oben einspeisen. Damit hätte man alle Pakete die nicht installiert sind. Das geht auch gleich remote von der "Quell Maschine". (Alle Pakete außer die mit "raspberry" in der Zeile)
bash software.sh $(ssh pi@remotehost "dpkg -l " |grep ^ii|grep -vi raspberry | awk '{ print $2 }' |tr -d "\r"|tr "\n" " ")
Das abgewandelte software.sh Script mit Loggingfunktion
LOG=DiffInstall.log
{
date
while [ $# -gt 0 ]
do
   if dpkg -s $1 &> /dev/null
     then
        echo $1" is installed!"
     else
        apt-get -y install $1
   fi
   shift
done
} >> $LOG 2>&1