• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

[Gelöst] Named Pipe zeitweise verstopft (stdout --> PHP)

T30

Newbie
Hallo,

ich habe mich in diesem Forum angemeldet, weil mein Problem offenbar mit Linux zu tun hat und deshalb in dem Forum, in dem ich sonst zu Hause bin (https://www.symcon.de/forum/) nicht gelöst werden kann.

Ich habe auf Raspis eine Anwesenheitserkennung mit Bluetooth-Dongels (BLE = Bluetooth low energy) in Betrieb (erscheint z.B. das Signal des Dongels nach Abwesenheit wieder, werden bestimmte Aktionen ausgeführt, z.B.: Garagentor auf, Licht ein usw.).
Das Pgr, das die empfangenen Bluetooth-Signale auswertet, liefert ca. 0 bis 4 Datensätze pro Sekunde, Ausgabe an stdout; dieser Kanal wird auf eine Datei umgeleitet und das Pgr. nach ca. 3 bis 4 Sekunden abgebrochen:
Code:
#!/bin/bash
# BLEscan.sh
sudo hcitool lescan > /tmp/Lescan.txt & sleep 3;
pkill --signal SIGINT hcitool;
sleep 1;

Anschließend wird die Datei in PHP ausgelesen und dem Inhalt entsprechend die oben beschriebenen Aktionen ausgelöst. Das ganz funktioniert sehr gut und zuverlässig, hat aber den Nachtel, dass es bis zu 4 Sekunden dauern kann, bis ein Signal erkannt wird.
Es ist halt blöd, mit dem Auto vor der Garage zu stehen und den Verkehr zu blockieren, weil es da zu einer Erkennungsverzögerung kommt.

Deshalb kam der Gedanke auf, von diesem "Stottermodus" in einem "Parallelmodus" überzugehen:
Das Empfangs-Programm wird durch eine Instanz gestartet und parallel dazu wertet eine andere Instanz (benutzt wird PHP) schritthaltend die Signale aus.

Ich habe zuerst versucht, die Datei zu lesen (und bin gescheitert, der Inhalt steht erst irgendwann zur Vfg.)
Auch an die Bildschirmausgabe, die angeblich im proc filesystem (/proc/<pid>/fd/1) erreichbar sein soll, bin ich nicht rangekommen.

Dann die Überlegung, dass eine "Named Pipe (fifo)" eigentlich das richtige für diesen Zweck ist:

A very useful Linux feature is named pipes which enable different processes to communicate.
The output of the command run on the first console shows up on the second console. Note that the order in which you run the commands doesn't matter.

Ich habe auch zur Kenntnis genommen, dass es zu Blockaden kommen kann (Auszug aus Linux Programmer's Manual, pipe - overview of pipes and FIFOs :

I/O on pipes and FIFOs
The only difference between pipes and FIFOs is the manner in which
they are created and opened. Once these tasks have been
accomplished, I/O on pipes and FIFOs has exactly the same semantics.

If a process attempts to read from an empty pipe, then read(2) will
block until data is available. If a process attempts to write to a
full pipe (see below), then write(2) blocks until sufficient data has
been read from the pipe to allow the write to complete. Nonblocking
I/O is possible by using the fcntl(2) F_SETFL operation to enable the
O_NONBLOCK open file status flag.

The communication channel provided by a pipe is a byte stream: there
is no concept of message boundaries.

If all file descriptors referring to the write end of a pipe have
been closed, then an attempt to read(2) from the pipe will see end-
of-file (read(2) will return 0). If all file descriptors referring
to the read end of a pipe have been closed, then a write(2) will
cause a SIGPIPE signal to be generated for the calling process. If
the calling process is ignoring this signal, then write(2) fails with
the error EPIPE. An application that uses pipe(2) and fork(2) should
use suitable close(2) calls to close unnecessary duplicate file
descriptors; this ensures that end-of-file and SIGPIPE/EPIPE are
delivered when appropriate.
Mein Problem ist jetzt folgendes:
Der BLE-Empfänger schreibt zwar offenbar kontinuierlich seine Daten in die Pipe
aber das Auswertprogramm kann sie nicht entnehmen.

Erst wenn der BLE-Empfänger beendet wird, kommen alle Daten in einem Zug beim Auswertprogramm an und das war ja nicht der Sinn der Übung, denn diese Methode dauert ja noch länger als der "Stottermodus".
Daher mein Betreff, dass die pipe zeitweise verstopft ist.

Kann mir jemand hier aus dem Forum mit einer fertigen Lösung helfen oder mal nachsehen, welchen Fehler ich mache?

Folgendes habe ich bereits realisiert:
Code:
#!/bin/bash
# BLEscan2.sh
cd /tmp;
sudo mkfifo /tmp/LePipe;
sudo hcitool lescan > /tmp/LePipe;

Damit wird in der 1. Instanz die Pipe erstellt und der BLE-Empfänger gestartet.
Gestoppt wird der durch folgendes Skript:
Code:
#!/bin/bash
# BLEstop.sh
sudo pkill --signal SIGINT hcitool;
sleep 1;
sudo rm /tmp/LePipe;

Hier das Skelett meines PHP-Skriptes, das die Daten auswerten soll (System-spezifische Befehle habe ich meist weggelassen):
Code:
$DatenDir = '/tmp/';
$LogFile = $DatenDir . 'Lescan.txt';    // wie in BLEscan2.sh
$StartTime = time ();

IPS_RunScript (GetObjectIDByIdentPath ('BLEscan/ACT2'));     // Starte schon mal BLEscan2.sh
IPS_Sleep (1000);   //  Wartezeit in Millisekunden ohne Abfrage als Vorsprung

$handle = fopen ($DatenDir . 'LePipe', 'r');				// ensures at least one writer (us) so will be non-blocking
// stream_set_blocking($handle, false); // prevent fread / fwrite blocking (ist ohne Bedeutung)

do {
    $line = trim (fgets ($handle, 64));        // Liest eine Zeile von der Position des Dateizeigers
    // das folgende hab ich auch schon ausprobiert:
//    $line = trim (fread ($handle,64)); // liest eine Zeile von $handle
//    $line = trim (stream_get_line ($handle,64));

if ($line){
//    var_dump( $line);
    print( (time ()) . " '" . $line . "'\n");
	}
} while ((time () - $StartTime ) < 50);   //  Wartezeit in Sekunden
Das Stop-Skript starte ich bei dieser Test-Version separat vor Ablauf der Warte-Zeit von 50 sek.

Für Rückfragen und weiter Erläuterungen stehe ich gerne zur Verfügung.

Es wäre schön, wenn mir jemand helfen könnte.

Viele Grüsse
Harald
 

abgdf

Guru
Vorweg: Ich hab' leider keine Bluetooth-Geräte und kann mir das nur vorstellen (aber es dürfte ja ähnlich sein wie nach einem USB-Gerät zu suchen).

Das mit hcitool, sleep und pkill finde ich nicht so toll, vor allem auch nicht die Kombination aus bash-Skript und PHP-Skript.
Kann sein, daß Dir schon was mit "ungeblocktem Lesen" hilft.

Hier:
http://www.elinux.org/RPi_Bluetooth_LE
http://www.elinux.org/RPi_Bluetooth_LE#Using_Bluetooth_LE_from_Python

wird ein Python-Modul "bluepy" genannt. Damit würde ich es probieren (weil Python meine bevorzugte Sprache ist).
Wahrscheinlich könnte man dann alles in einem (Python-)Skript schreiben.

Edit: Wäre FHEM was für Dich?

https://wiki.fhem.de/wiki/Hauptseite

Da gäbe es einen Daemon zur Anwesenheitserkennung in Perl:
https://wiki.fhem.de/wiki/Anwesenheitserkennung#Anwesenheitserkennung_Bluetooth_PebbleBee_mit_PRESENCE_Modul
https://github.com/mhop/fhem-mirror/blob/master/fhem/contrib/PRESENCE/lepresenced
 

MH1962

Member
Der BLE-Empfänger schreibt zwar offenbar kontinuierlich seine Daten in die Pipe
aber das Auswertprogramm kann sie nicht entnehmen.
Vermutlich schreibt er seine Daten eben NICHT kontinuierlich in die Pipe, sondern erst mal nur in einen Puffer.

In diesem Fall KÖNNTE stdbuf helfen. Einen Versuch wäre es wert.

Code:
sudo stdbuf -o 0 hcitool lescan > /tmp/LePipe
 

spoensche

Moderator
Teammitglied
Die Ausgabe wird per default gebuffert. Erst wenn der Schreiberling quasi einen Flush auf dem Filedescriptor bzw Stream auslöst, werden die Daten auch ausgegeben. Dies führt zu den Verzögerungen.

Siehe dazu auch
Code:
man 3 stdout

The stream stdout is line-buffered when it points to a terminal. Partial lines will not appear until fflush(3) or exit(3) is called, or a newline is printed.
 
OP
T

T30

Newbie
abgdf schrieb:
...
Das mit hcitool, sleep und pkill finde ich nicht so toll, vor allem auch nicht die Kombination aus bash-Skript und PHP-Skript.
...
Wäre FHEM was für Dich?
...

Ich verwende IPS und bin damit zufrieden. IPS kann man als "Middleware" bezeichnen, es bietet jede Menge Schnittstellen zu diversen Gateways, eine Web-Oberfläche und zur Programmierung PHP.

Die FHEM-Leute verwenden bei ihrer Bluetooth-Anwesenheitserkennung genau das von mir aufgeführte Skript, das Du "nicht so toll" findest; wahrscheinlich haben wir es von derselben Quelle adoptiert :D

Python ist mir ziemlich fremd, meine Perl-Programmierzeit auch schon einige Jahre abgelaufen, noch weiter ist C entfernt, aber weil die ganzen Sourcen vorliegen, hab ich schon überlegt, das ganze für meine Zwecke zu adaptieren; eine einfache Lösung wäre mir aber lieber.

Vielen Dank jedenfalls für die Vorschläge

Viele Grüsse
Harald
 
OP
T

T30

Newbie
spoensche schrieb:
Die Ausgabe wird per default gebuffert. Erst wenn der Schreiberling quasi einen Flush auf dem Filedescriptor bzw Stream auslöst, werden die Daten auch ausgegeben. Dies führt zu den Verzögerungen.
Diese Interpretation kann ich aber aus dem zitierten Text nicht ableiten!!!

The stream stdout is **line-buffered** when it points to a terminal. **Partial lines** will not appear until fflush(3) or exit(3) is called, or a **newline** is printed.

So erscheint der Output, wenn stdout nicht umgeleitet wird:
Code:
20:53:35[pi@RaspiB5:/tmp] $ sudo hciconfig hci0 up
21:01:58[pi@RaspiB5:/tmp] $ sudo hcitool lescan
LE Scan ...
7C:2F:80:99:C9:00 (unknown)
7C:2F:80:99:C9:00 Gigaset G-tag
7C:2F:80:91:68:9C (unknown)
7C:2F:80:91:68:9C Gigaset G-tag
7C:2F:80:99:C9:00 (unknown)
7C:2F:80:91:68:9C (unknown)
7C:2F:80:99:C9:00 (unknown)
7C:2F:80:99:C9:00 Gigaset G-tag
7C:2F:80:91:68:9C (unknown)
7C:2F:80:91:68:9C Gigaset G-tag
7C:2F:80:91:68:9C (unknown)
7C:2F:80:91:68:9C Gigaset G-tag
7C:2F:80:99:C9:00 (unknown)
7C:2F:80:99:C9:00 (unknown)
7C:2F:80:99:C9:00 Gigaset G-tag
^C

Und so will ich jede Zeile abgeschlossen durch **newline** zum Zeitpunkt der Ausgabe auswerten können.
Das, was Du "quasi einen Flush" nennst, wird ja zum Abschluß jeder Zeile als newline gesendet.
**Partial lines** möchte ich nicht auswerten.

Viele Grüsse
Harald
 
OP
T

T30

Newbie
MH1962 schrieb:
...
Vermutlich schreibt er seine Daten eben NICHT kontinuierlich in die Pipe, sondern erst mal nur in einen Puffer.

In diesem Fall KÖNNTE stdbuf helfen. Einen Versuch wäre es wert.

Code:
sudo stdbuf -o 0 hcitool lescan > /tmp/LePipe

Code:
22:03:35[pi@RaspiB5:/tmp] $ sudo mkfifo /tmp/LePipe
22:05:58[pi@RaspiB5:/tmp] $ ls -al
...
prw-r--r--  1 root root    0 Feb  6 22:05 LePipe
...
22:06:45[pi@RaspiB5:/tmp] $ sudo stdbuf -o 0 hcitool lescan > /tmp/LePipe
-bash: /tmp/LePipe: Keine Berechtigung
22:06:48[pi@RaspiB5:/tmp] $

Einen Versuch war es wert :/

Oder war der Versuch ungültig, weil im Terminal ausgeführt? Mein PHP-Testskript hat sich aufgehängt.

Viele Grüsse
Harald
 

abgdf

Guru
T30 schrieb:
Python ist mir ziemlich fremd
Ist relativ leicht zu lernen, wahrscheinlich leichter als PHP. Und es lohnt sich, weil gute Allroundsprache.
T30 schrieb:
Die FHEM-Leute verwenden bei ihrer Bluetooth-Anwesenheitserkennung genau das von mir aufgeführte Skript, das Du "nicht so toll" findest; wahrscheinlich haben wir es von derselben Quelle adoptiert :D
AFAICS kommt da ein Perl-Daemon lepresenced zur Anwendung.
 

MH1962

Member
T30 schrieb:
Code:
22:03:35[pi@RaspiB5:/tmp] $ sudo mkfifo /tmp/LePipe
22:05:58[pi@RaspiB5:/tmp] $ ls -al
...
prw-r--r--  1 root root    0 Feb  6 22:05 LePipe
...
22:06:45[pi@RaspiB5:/tmp] $ sudo stdbuf -o 0 hcitool lescan > /tmp/LePipe
-bash: /tmp/LePipe: Keine Berechtigung
22:06:48[pi@RaspiB5:/tmp] $

Einen Versuch war es wert :/

Oder war der Versuch ungültig, weil im Terminal ausgeführt? Mein PHP-Testskript hat sich aufgehängt.
Probier es mal umgekehrt:
Code:
stdbuf -o 0 sudo hcitool lescan > /tmp/LePipe
 
OP
T

T30

Newbie
MH1962 schrieb:
Probier es mal umgekehrt:
Code:
stdbuf -o 0 sudo hcitool lescan > /tmp/LePipe

Code:
14:05:52[pi@RaspiB5:/tmp] $ sudo hciconfig hci0 up
14:05:54[pi@RaspiB5:/tmp] $ sudo mkfifo /tmp/LePipe
14:06:08[pi@RaspiB5:/tmp] $ stdbuf -o 0 sudo hcitool lescan > /tmp/LePipe
-bash: /tmp/LePipe: Keine Berechtigung
14:06:23[pi@RaspiB5:/tmp] $
 
OP
T

T30

Newbie
Hallo,

vielen Dank für die Tipps und Anregungen, die ich hier im Forum von Euch bekommen habe und die es mir möglich machten, mein Problem zu lösen:

MH1962 für den Tipp mit stdbuf
spoensche für den Hinweis auf die Bufferung in Linux (die ich erst nicht glauben wollte, weil ich die Linux-Dokumentation für Pipes für wahr hielt: „A pipe has a read end and a write end. Data written to the write end of a pipe can be read from the read end of the pipe. The communication channel provided by a pipe is a byte stream: there is no concept of message boundaries.”)

Mit einigen anderen Hinweisen, die ich noch gegoogelt habe, hab ich mir dieses Skript entwickelt:

Code:
#!/bin/bash
# BLEscan2.sh
cd /tmp;
sudo mkfifo /tmp/LePipe;
sudo stdbuf -oL hcitool lescan 2>&1 | sudo tee /tmp/LePipe;

Wahrscheinlich kann die letzte Zeile schlanker gemacht werden, ich werde aber keine weitere Experimente machen, denn das Skript funktioniert für meine Zwecke, hier ist der Beweis:

Code:
Mi 08.02.17-20:25:32 'LE Scan ...'
Mi 08.02.17-20:25:32 '7C:2F:80:91:68:9C (unknown)'
Mi 08.02.17-20:25:32 '7C:2F:80:91:68:9C Gigaset G-tag'
Mi 08.02.17-20:25:32 '7C:2F:80:91:68:9C (unknown)'
Mi 08.02.17-20:25:33 '7C:2F:80:91:68:9C (unknown)'
Mi 08.02.17-20:25:33 '7C:2F:80:91:68:9C (unknown)'
Mi 08.02.17-20:25:33 '7C:2F:80:91:68:9C Gigaset G-tag'
Mi 08.02.17-20:25:34 '7C:2F:80:91:68:9C (unknown)'
Mi 08.02.17-20:25:35 '7C:2F:80:91:68:9C (unknown)'
Mi 08.02.17-20:25:35 '7C:2F:80:91:68:9C Gigaset G-tag'
Mi 08.02.17-20:25:36 '7C:2F:80:91:68:9C (unknown)'
Mi 08.02.17-20:25:36 '7C:2F:80:91:68:9C Gigaset G-tag'
Mi 08.02.17-20:25:37 '7C:2F:80:91:68:9C (unknown)'
Mi 08.02.17-20:25:37 '7C:2F:80:91:68:9C Gigaset G-tag'
Mi 08.02.17-20:25:38 '7C:2F:80:91:68:9C (unknown)'
Mi 08.02.17-20:25:38 '7C:2F:80:91:68:9C Gigaset G-tag'
Mi 08.02.17-20:25:39 '7C:2F:80:91:68:9C (unknown)'
Mi 08.02.17-20:25:40 '7C:2F:80:91:68:9C Gigaset G-tag'
Mi 08.02.17-20:25:41 '7C:2F:80:91:68:9C (unknown)'

Das Test-Skript (siehe mein erste Beitrag zu diesem Thema) lieferte entsprechend dem eingestellten Timer 10 Sekunden lang Output mit entsprechenden Real-Zeit-Marken.
18 Meldungen = 1,8 Meldungen /sek bei Vorhandensein eines Bluetooth-Tags. Wenn mehrere präsent sind, ist entspechend mehr los.

Ich werde das Thema mit [Gelöst] im Titel versehen!


Viele Grüsse
Harald
 
OP
T

T30

Newbie
Einen Nachtrag zur Lösung hab ich noch:

In meinem ersten Beitrag zu diesem Thread hatte ich aus dem "Linux Programmer's Manual, pipe - overview of pipes and FIFOs" zitiert. Danach gibt es bei der Verwendung von Named Pipes Probleme mit

Buffering: der Empfänger bekommt keine Daten obwohl die der Sender welche abgeschickt hat; erst wenn der Sender abgeschaltet wird oder es zu einem Buffer-Überlauf kommt, erhält der Empfänger die Daten in einem Rutsch, d.h. die sind nicht mehr aktuell.

Blocking: wenn der Sender nichts mehr liefert, bleibt der Empfänger an der Pipe hängen und kommt nicht mehr los, bis nachgeschoben wird.

Durch das in dem vorherigen Beitrag gezeigte Skript BLEscan2.sh wird nur das Problem des Buffering gelöst.

Mein PHP-Testkript lief aber nur, weil ständig Daten vom BLE-Empfänger in die Pipe geschrieben werden und ich den Loop nach 10 Sekunden abbreche. Ist das nicht der Fall, hängt das Skript.

Deshalb muß das Problem des Blocking im PHP-Skript gelöst werden:

Code:
$DatenDir = '/tmp/';
$LogFile = $DatenDir . 'Lescan.txt';    // wie in BLEscan2.sh
$StartTime = time ();

IPS_RunScript (GetObjectIDByIdentPath ('BLEscan/ACT2'));     // Starte schon mal BLEscan2.sh
IPS_Sleep (1000);   //  Wartezeit in Millisekunden ohne Abfrage als Vorsprung

$handle = @fopen ($DatenDir . 'LePipe', 'r');   // Pipe zum Lesen geöffnet, unbuffered durch BLEscan2.sh, aber noch blocked
// wait
stream_set_blocking ($handle, 0);   // Dadurch blockiert fgets nicht mehr, liefert Inhalt oder leeren String, kein EOF

do {
   $Line = trim (fgets ($handle, 128));
   if ($Line) {           // wenn noch was im Buffer ist
      // entweder verarbeiten (oder s.u. Maintenance)

      //  .... Verarbeitungsroutine

   } else {      // Nix im Buffer
      // oder Nichts zu verarbeiten daher Maintenance   

      // z.B. nachprüfen  ob für die einzelnen Tags die Zeit abgelaufen ist: dann auf abwesend setzen

   }
// Ende von Verarbeitung oder Maintenance
} while ((time () - $StartTime ) < 50);   //  Wartezeit in Sekunden

Viele Grüsse
Harald
 

abgdf

Guru
T30 schrieb:
Einen Nachtrag zur Lösung hab ich noch
Sorry, aber selbst wenn das alles schlecht und recht so funktionieren sollte, ist es doch programmiertechnisch grottig und bestimmt nicht zur Weitergabe geeignet.
Von einer allgemeingültigen "Lösung" kann keine Rede sein.
 
OP
T

T30

Newbie
abgdf schrieb:
Sorry, aber selbst wenn das alles schlecht und recht so funktionieren sollte, ist es doch programmiertechnisch grottig und bestimmt nicht zur Weitergabe geeignet.
Von einer allgemeingültigen "Lösung" kann keine Rede sein.

Dann mach Verbesserungsvorschläge oder bitte den Mod den ganzen Thread aus "Qualitätsgründen" zu löschen!
Nur rumzumaulen hilft niemandem.

Mir jedenfalls haben die diversen Hinweise geholfen und ich war deshalb auch gerne bereit die Komplett-"Lösung" für die Community zu posten.
 

abgdf

Guru
T30 schrieb:
Dann mach Verbesserungsvorschläge
Ich hätte Dir gern ein vernünftiges (Python-)Skript dazu geschrieben, aber leider habe ich kein Haus (mehr), schon gar kein intelligentes, und kann deshalb nichts testen.
T30 schrieb:
oder bitte den Mod den ganzen Thread aus "Qualitätsgründen" zu löschen!
Nur rumzumaulen hilft niemandem.

Mir jedenfalls haben die diversen Hinweise geholfen und ich war deshalb auch gerne bereit die Komplett-"Lösung" für die Community zu posten.
Löschen oder so braucht man nicht, die heutige Speicherkapazität läßt es zu, alles mögliche stehen zu lassen.
Ob sich allerdings außer Dir hier noch jemand für das Thema interessiert, wer weiß? Ich würde erstmal nicht davon ausgehen.
 
Oben