Eventcallback Schleife für Dbus - Implementierungsmöglichkeiten?

Begonnen von Adimarantis, 24 Januar 2021, 14:47:11

Vorheriges Thema - Nächstes Thema

Adimarantis

Hallo,

ich bin gerade dabei ein Modul für die Verwendung des Signal Messengers (Signalbot - ähnlich dem Telegrambot) zu entwickeln.
Dabei nutze ich das Programm "signal-cli" ( https://github.com/AsamK/signal-cli ) welches man als Daemon im dbus Modus starten kann.
Über die Library Net::DBus ( https://metacpan.org/release/Net-DBus ) kann ich dieses dann von Perl aus ansteuern.

Ich habe es schon geschafft, dass man Nachrichten und Attachments an Personen oder Gruppen schicken kann (die Registrierung muss über die Kommandozeile von signal-cli erfolgen - die Dbus Schnittstelle unterstützt da noch nicht alle Funktionen).

Jetzt wollte ich auch Nachrichten/Rückmeldungen empfangen. Dazu bietet Net::DBus die Möglichkeit callbacks auf Signale von DBus zu implementieren. Diese werden aber so weit ich das verstehe nur aufgerufen, wenn sich das Perl Programm in einem Warteaufruf befindet:
my $reactor = Net::DBus::Reactor->main();
$reactor->run();


In einem Standalone Beispiel klappt das auch wunderbarm aber FHEM würde da natürlich solange stehen.
Wäre es ok, hier mit den Blockingcalls zu arbeiten und einen fork zu machen in dem diese Schleife dauerhaft läuft?

Weiteres Problem: Die Callbacks bieten keine Möglichkeit $hash durchzureichen - was ist die eleganteste Möglichkeit da trotzdem dranzukommen?

Danke,
Jörg


Raspberry 4 + HM-MOD-RPI-PCB (pivCCU) + RfxTrx433XL + 2xRaspberry 1
Module: 50_Signalbot, 52_I2C_ADS1x1x , 58_RPI_1Wire, (50_SPI_MAX31865)

Dr. Boris Neubert

Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

Adimarantis

Danke!
Hab natürlich intensiv in den offiziellen Modulen gesucht, aber beim allgemeinen "googeln" ist "Signal" ein übler Suchbegriff.
Das schaue ich mir natürlich näher an bevor ich jetzt da Rad zum zweiten Mal erfinde. Hab schon einen Punkt entdeckt, wo man was von mir reinmergen könnte  :)
Raspberry 4 + HM-MOD-RPI-PCB (pivCCU) + RfxTrx433XL + 2xRaspberry 1
Module: 50_Signalbot, 52_I2C_ADS1x1x , 58_RPI_1Wire, (50_SPI_MAX31865)

Wzut

Zitat von: Adimarantis am 24 Januar 2021, 14:47:11
Wäre es ok, hier mit den Blockingcalls zu arbeiten und einen fork zu machen in dem diese Schleife dauerhaft läuft?
Mache ich in 73_MPD um die Nachrichten des MPD auszuwerten und fast genau so nochmal in 96_SIP wenn das Modul auf einen Anruf wartet.
Würde mich daher auch interessieren wenn es Argumente dagegen gibt und eventuelle Alternativen.
Maintainer der Module: MAX, MPD, UbiquitiMP, UbiquitiOut, SIP, BEOK, readingsWatcher

Adimarantis

Ich habe mir SiSi jetzt mal ein wenig angeschaut. Schaut so aus, als würde das Modul nicht sehr aktiv maintained zu werden.
Außerdem funktioniert genau mein Problem dort anscheinend auch nicht: Es werden keine Messages empfangen.

Scheint ein allgemeines Problem unter FHEM zu sein:

Ein blankes Perl Programm ala

my $bus = Net::DBus->system;
my $service = $bus->get_service("org.asamk.Signal");
my $object = $service->get_object("/org/asamk/Signal");

my $sig1 = $object->connect_to_signal("ReceiptReceived", \&rec_callback);
my $sig2 = $object->connect_to_signal("SyncMessageReceived", \&sync_callback);
my $sig3 = $object->connect_to_signal("MessageReceived", \&msg_callback);
my $reactor = Net::DBus::Reactor->main();
$reactor->run();

wartet brav in run() auf Rückmeldungen und ruft die Callbacks auf, so dass man Nachrichten empfangen kann.
Selbst wenn ich diesen Code ganz genau so in einem geforkten FHEM process laufen lassen, dann kommt run() sofort zurück und keine Callbacks werden aufgerufen. Das passiert auch direkt in FHEM ohne fork - hat also mit dem forken eher nichts zu tun.

Weiss irgendein Perl Profi was bei einem Perl Programm von der Kommandozeile hier anders läuft als wenn es in FHEM aufgerufen wird?
Oder vielleicht ist es auch ein Problem bei mir. Sieht jemand unter SiSi dass auch Nachrichten empfangen werden (verbose 5, dann sollte was im Log stehen)? (die Frage habe ich direkt im SiSi Thread gestellt - antworten auch gerne da, falls der Original Entwickler dort eher mitliest).

Gruß,
Jörg
Raspberry 4 + HM-MOD-RPI-PCB (pivCCU) + RfxTrx433XL + 2xRaspberry 1
Module: 50_Signalbot, 52_I2C_ADS1x1x , 58_RPI_1Wire, (50_SPI_MAX31865)

Dr. Boris Neubert

Hallo Jörg,

ich habe neulich jemandem SiSi installiert und dort wurden Nachrichten empfangen. Das war unter einem aktuelle Raspberry Pi OS.

Grüße
Boris
Globaler Moderator, Developer, aktives Mitglied des FHEM e.V. (Marketing, Verwaltung)
Bitte keine unaufgeforderten privaten Nachrichten!

Adimarantis

Hmm.. sehr seltsam.
Habe den Raspberry jetzt mal durchgestartet und das hat geholfen. Jetzt geht sowohl SiSi als auch meine Lösung.
Warum es unter ohne FHEM ging und mit nicht, ist mir ein Rätsel.
Mal sehen ob ich das weiterverfolge. Ich wollte die BlockingCall API nutzen während SiSi da was selbst gebaut hat - aber vielleicht hatte das ja seinen Grund.

Jörg
Raspberry 4 + HM-MOD-RPI-PCB (pivCCU) + RfxTrx433XL + 2xRaspberry 1
Module: 50_Signalbot, 52_I2C_ADS1x1x , 58_RPI_1Wire, (50_SPI_MAX31865)

Adimarantis

Dann komme ich nochmal auf die Problematik zurück die Device/$hash durchzureichen.

1. Aufruf von BlockingCall im Parent -> alles gut, kann den Devicenamen als Parameter übergeben
2. Callback für Net::DBus initialisieren -> hier kann ich meines Wissen keine Parameter übergeben
    meine Lösung: Globale Variable (die ich nur im Child verwende)
3. Im Net::DBus Callback -> Globale Variable auslesen, message auswerten und per BlockingInformParent() auch den Namen im return String übergeben.
4. Im Parent Callback, diesen String auswerten und über $defs den $hash wiederherstellen.

Mich stört jetzt die globale Variable zwischen (2) und (3) - geht das eleganter, oder ist das ok?
Im Child komme ich eigentlich auch komplett ohne $hash aus, das brauche ich erst wieder beim Auswerten der Daten im Parent.

Jörg

Raspberry 4 + HM-MOD-RPI-PCB (pivCCU) + RfxTrx433XL + 2xRaspberry 1
Module: 50_Signalbot, 52_I2C_ADS1x1x , 58_RPI_1Wire, (50_SPI_MAX31865)

rudolfkoenig

ZitatMich stört jetzt die globale Variable zwischen (2) und (3) - geht das eleganter, oder ist das ok?
Statt \&msg_callback koenntest Du es mit einer inplace-Funktion versuchen: sub(){ msg_callback($hash) }

herrmannj

Hi,

die Funktionalität wird in 32_SiSi.pm mAn doch umgesetzt und zwar via fork. BlockingCall ist auch nichts anderes. Wenn Du Dich da selbst verwirklichen willst bist Du sicher gut bedient Dich an 32_SiSi.pm wenigsten zu orientieren.

Das tiefer liegende "Problem" liegt jedoch darin, dass Net::DBus eine eigene Eventloop definiert. (reactor.pm #321ff, #349ff).

Vergleiche jetzt mal Reactor.pm #372f

  my ($ro, $wo, $eo);
  my $n = select($ro=$ri,$wo=$wi,$eo=$ei, (defined $timeout ? ($timeout ? $timeout/1000 : 0) : undef));

fhem.pl #704
$nfound = select($rout=$rin, $wout=$win, $eout=$ein, $timeout) if(!$nfound);

Es wäre also problemlos möglich (mAn "richtig") die Funktionalität von Reactor.pm auf die Eventloop von fhem zu adaptieren (im eigenen modul). Entsprechende Aufrufe der CPAN Module sind ja dort sichtbar.

Dann erspart man sich das ganze forken und die Interprozesskommunikation und alle damit verbundenen Probleme.

Adimarantis

Hallo Rudi,

klappt so nicht ganz, da der Callback vom Framework bereits feste Übergabeparameter bekommt.
Hat mich aber auf eine Idee gebracht (und SiSi macht das wohl ähnlich, hatte das nur nicht ganz verstanden, @hermannj: Ist auch ein wenig "Lernprojekt"):

sub blockingCall {

     my $device=<Name übergeben von BlockingCall()>;

     my sub callback {
          my $localdevice=$device;
          ....
         BlockingInformParent("...",$localdevice,0);
    }
    ...
    my $sig1 = $object->connect_to_signal("ReceiptReceived", \&callback);

   reactor->run(); #Eventloop
}
   


Dadurch ist es keine globale Variable, sondern eine der sub selbst.
Musste ich mit "my sub" definieren, sonst bekomme ich ein Warning

PERL WARNING: Variable "$device" will not stay shared at ....

Kann das "my sub" Probleme machen? Falls die Eventloop abbricht, werden die Callbacks auch nicht mehr verwendet und sollte daher solange existieren wie man sie braucht.

@hermannj: den select() an FHEM zu delegieren hätte sicher was. Dafür müsste ich mir den "Net::DBus" code mehr zu gemüte führen (wie kommt man an die filehandles? wie kommt man an die Daten für den Callback?). Wenn es dafür keine API gibt und man da tief eingreifen muss, dann ist das auch nicht so gut (Kompatibilität bei updates).

Gruß
Jörg
Raspberry 4 + HM-MOD-RPI-PCB (pivCCU) + RfxTrx433XL + 2xRaspberry 1
Module: 50_Signalbot, 52_I2C_ADS1x1x , 58_RPI_1Wire, (50_SPI_MAX31865)

rudolfkoenig

ZitatKann das "my sub" Probleme machen?
So ein Konstrukt hat bei mir zu einem Speicherloch gefuehrt.
Weiss nicht, ob das in deinem Fall auch zutrifft, bzw. ob das relevant ist.

Adimarantis

Die Unterroutine welche die "my sub" enthält, ist ja im geforkten Process und wird nur einmalig aufgerufen. Der Prozess beendet sich mit der Beendigung der Eventloop, womit das Betriebssystem den Speicher aufräumen sollte. Da sehe ich jetzt in diesem Anwendungsfall eher keine Gefahr.
Raspberry 4 + HM-MOD-RPI-PCB (pivCCU) + RfxTrx433XL + 2xRaspberry 1
Module: 50_Signalbot, 52_I2C_ADS1x1x , 58_RPI_1Wire, (50_SPI_MAX31865)

Adimarantis

Zitat von: herrmannj am 25 Januar 2021, 15:10:12
Es wäre also problemlos möglich (mAn "richtig") die Funktionalität von Reactor.pm auf die Eventloop von fhem zu adaptieren (im eigenen modul). Entsprechende Aufrufe der CPAN Module sind ja dort sichtbar.

Dann erspart man sich das ganze forken und die Interprozesskommunikation und alle damit verbundenen Probleme.

So klappt eine eigene Eventloop mit select:
$reactor->{running} = 1;
while ($reactor->{running}) {
print "starting select ".$reactor->{running}."\n";
my ($ri, $ric) = $reactor->_bits("read");
my ($ro);
my $n = select($ro=$ri,undef,undef,undef);
if ($n>0) { print "got something".$reactor->{running}."\n";}
$reactor->step();
}
$reactor->shutdown();


Unschön ist dass man dafür die interne "running" variable setzen muss (sonst kehrt step() ohne Funktion zurück und die interne Funktion _bits() aufrufen muss. D.h. der Autor kann das jederzeit ohne Rücksicht ändern.

Raspberry 4 + HM-MOD-RPI-PCB (pivCCU) + RfxTrx433XL + 2xRaspberry 1
Module: 50_Signalbot, 52_I2C_ADS1x1x , 58_RPI_1Wire, (50_SPI_MAX31865)

Adimarantis

So, hab jetzt eine Version die Nachrichten ohne fork empfangen kann.

Wermutstropfen dabei ist dieses Code-Fragment:
my $count = 0;
my $fds=undef;
    foreach (keys %{$reactor->{fds}->{read}}) {
next unless $reactor->{fds}->{read}->{$_}->{enabled};
$fds=$_;
$count++;
    }
return "Error getting Dbus filehandler" unless defined $fds;


Das holt die Filehandle direkt aus den internen Strukturen (und geht davon aus es gibt nur eine - bei != 1 schmeisse ich aktuell einen Fehler).

Zweite Unschönheit: Ich rufe $reactor->step() vorsichtshalber zweimal auf. Teilweise wurde beim ersten Aufruf kein Callback getriggert. Es könnten aber auch mehrere Nachrichten in der Queue stehen.
Leider hab ich aber keinen Weg gefunden nachzusehen wie viele Nachrichten noch anstehen (um z.B. eine while Schleife zu machen, bis alles gelesen ist).
Damit das select() in der step() Funktion nicht blockiert wird noch ein sehr kurzer $reactor->add_timeout() installiert (der selbst nichts macht).

Jetzt aber Gute Nacht :) Jörg
Raspberry 4 + HM-MOD-RPI-PCB (pivCCU) + RfxTrx433XL + 2xRaspberry 1
Module: 50_Signalbot, 52_I2C_ADS1x1x , 58_RPI_1Wire, (50_SPI_MAX31865)