udp multicast support in fhem

Begonnen von justme1968, 18 Februar 2022, 21:56:52

Vorheriges Thema - Nächstes Thema

justme1968

@rudi: ich bin gerade dabei mdns für fhem zu bauen. im ramen dessen sind wir auch auf multicast im allgemeinen zu sprechen gekommen. aktuell gibt es 5 module die alle IO::Socket::Multicast verwenden. das hat leider den nachteil das es ein externes perl modul ist das auch noch eine c abhängigkeit hat und sich nicht immer so einfach installieren lässt. bei mir geht es z.b. mit einem aktuellen mac os (mit oder ohne homebrew) nicht out of the box. ausserdem gibt es bei einem externen modul den nachteil das sich jeder selbst dum das zusammenspiel mit fhemfork kümmern muß.

ich habe vorhin im prinzip eine reine perl varante von udp multicast zum laufen bekommen. ich denke das wäre auf dieser basis ein gute möglichkeit udp multicast ohne externe abhängigkeiten direkt in fhem einzubauen.

ich würde TcpServerUtils.pm für den server teil (d.h. primär empfangen und eventuell antworten) und DevIO für den client teil (d.h. primär senden und eventuell die antwort darauf empfangen) als passend erachten.

spricht aus deiner sicht etwa dagegen? wenn nein: ich würde demnächst einen vorschlag machen.


ps: zum mdns modul: die routine die die mdns antworten parsed ist mehr oder weniger kompatibel zu routine die du für HttpUtils_gethostbyname gebaut hast (ist ja das gleiche message format :) ). ist aber allgemeiner (d.h. kommt mit mehr record typen und mit mehreren records pro message zurecht) wäre es eventuell sinnvoll nur eine solche routine zu haben? eigentlich gilt das auch für das zusammenbauen der query. eventuell könnte man auch einen dns cache gemeinsam nutzen.das hätte den nebeneffekt das man überall wo HttpUtils_gethostbyname verwendet wird auch .local adressen angeben könnte. das geht aktuell nicht. ich weiss aber nicht ob sich das vereinheitlichen lohnt.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

rudolfkoenig

Zitatspricht aus deiner sicht etwa dagegen?
Gar nichts.
Ich ueberlege, ob es nicht sinnvoll ist die beiden Hilfsmodule so zu erweitern, dass man weitere Protokolle nachladen kann.
Aber vielleicht fangen wir erstmal monolitisch an.

Ich habe auch nichts gegen einen universelleren noblocking-gethostbyname, gerne ausgeloest aus HttpUtils.pm.

erwin

Hi Rudi,
ich kann ein snipped anbieten zu noBlockning dns...
in define:
                if ($host !~ /$PAT_IP/ix) { # not an ip-address, lookup name
=pod
                        # blocking variant !
                        my $phost = inet_aton($host);
                        return "KNXIO_define: host name $host could not be resolved" if (! defined($phost));
                        $host = inet_ntoa($phost);
                        return "KNXIO_define: host name could not be resolved" if (! defined($host));
=cut
                        # do it non blocking! - use HttpUtils to resolve hostname
                        $hash->{PORT} = $port; # save port...
                        $hash->{timeout} = 5; # TO for DNS req.
                        $hash->{DNSWAIT} = 1;
                        my $KNXIO_DnsQ = HttpUtils_gethostbyname($hash,$host,1,\&KNXIO_gethostbyname_Cb);
                }

                else {
                        $hash->{DeviceName} = $host . q{:} . $port; # for DevIo
                }

der callback:
### called from define-HttpUtils_gethostbynam when hostname needs to be resolved
### process callback from HttpUtils_gethostbyname
sub KNXIO_gethostbyname_Cb {
        my $hash  = shift;
        my $error = shift;
        my $dhost = shift;

        my $name  = $hash->{NAME};
        delete $hash->{timeout};
        delete $hash->{DNSWAIT};
        if ($error) {
                delete $hash->{DeviceName};
                delete $hash->{PORT};
                Log3($name, 1, "KNXIO_define: hostname could not be resolved: $error");
                return  "KNXIO_define: hostname could not be resolved: $error";
        }
        my $host = ip2str($dhost);
        Log3($name, 3, "KNXIO_define: DNS query result= $host");
        $hash->{DeviceName} = $host . q{:} . $hash->{PORT};
        delete $hash->{PORT};
        return;
}


und dann etwas später:
### called from define - after init_complete
### return undef on success
sub KNXIO_openDev {
              ..........
               if (exists $hash->{DNSWAIT}) {
                $hash->{DNSWAIT} += 1;
                if ($hash->{DNSWAIT} > 5) {
                        Log3 ($name, 2, "KNXIO_openDev: $name - DNS failed, check ip/hostname");
                        return; #  "KNXIO_openDev: $name - DNS failed, check ip/hostname";
                }
                InternalTimer(gettimeofday() + 1,\&KNXIO_openDev,$hash);
                Log3 ($name, 2, "KNXIO_openDev: waiting for DNS");
                return; # waiting for DNS
        }

Das müsste man natürlich "generisch/universell" machen....

Ad. Multicast Client: das hab ich dzt. via DevIo: FHEM:DEVIO:$devname:$port gelöst, allerdings unter Verwendung von IO:Socket:Multicast....
l.g. erwin
FHEM aktuell auf RaspberryPI Mdl 1-4
Maintainer: 00_KNXIO.pm 10_KNX.pm
User: CUNO2 (868 SLOWRF) - HMS100xx, FS20, FHT, 1-Wire  - 2401(iButton), 18x20, 2406, 2413 (AVR), 2450,..,MQTT2, KNX, SONOFF, mySENSORS,....
Hardware:  Busware ROT, Weinzierl IP731, 1-Wire GW,...

rudolfkoenig

@erwin: ich sehe den Codestueck als Beispielaufruf fuer HttpUtils_gethostbyname, und nicht als Erweiterungsvorschlag. Irre ich mich?

justme1968

#4
der angehängte patch erweitern TcpServerUtils.pm um die möglichkeit udp multiucast zu empfangen. bitte noch nicht einchecken :). der code für ipv6 ist zwar schon da, aber noch ungetestet.


@erwin: vielleicht magst du mal schauen ob das in dieser art zu deinem modul passen würde.

verwendet wird es so:

  • das socket mit gesetztem optionalen vierten paramter von TcpServer_Open erzeugen:
    d.h. im define oder sonst wo mit my $ret = TcpServer_Open($hash, '5353', '0.0.0.0', 1); initialisieren. statt der 0.0.0.0 kann man auch die multicast adresse angeben, ich bin mir nicht sicher was richtiger ist und ob es einen unterschied macht. funktionieren tut bei mir beides.

  • TcpServer_Open schaltet automatisch den loopback mode aus.
    d.h. man empfängt seine eigenen daten nicht. ich vermute das ist der normalfall.
    mit TcpServer_SetLoopbackMode($hash,[0|1]); kann man das ändern wenn man es braucht.

  • mit TcpServer_MCastAdd die multicast adresse zum socket explizit hinzufügen:
    z.b.: TcpServer_MCastAdd($hash,'224.0.0.251');
    erst ab jetzt bekommt man auch tatsächlich daten.

  • im (device) hash wird für jedes udp packet die ReadFn aufgerufen.
    dort kann man mit TcpServer_MCastRecv dann die empfangenen daten abholen:
    my($peer_host, $peer_port) = TcpServer_MCastRecv($hash,$data,$length);

    alternativ kann man auch die low leven routinen direkt aufrufen:
    • my $sockaddr = $hash->{SERVERSOCKET}->recv($data, 4096);
      der empfang geht über SERVERSOCKET, nicht wie bei tcp über CD, es wird auch kein accept verwendet
    • aus $sockaddr kann man sich die gegenstelle zum udp packet holen falls man die braucht:
      my ($peer_port, $addr) = Socket::sockaddr_in($sockaddr);
      my $peer_host = inet_ntoa($addr);



  • eigene daten sendet man mit TcpServer_MCastSend:
    TcpServer_MCastSend($hash,$data[,$host[,$port]]);
    hier bei wird der port aus dem TcpServer_Open und die adresse aus TcpServer_MCastAdd verwendet. alternativ kann man beides auch beim aufruf mitgeben.

  • mit TcpServer_MCastRemove kann man die multicast adresse auch wieder vom socket entfernen:
    z.b.: TcpServer_MCastRemove($hash,'224.0.0.251');
    das socket empfängt dann keine daten für diese adresse mehr.

  • mit wechselweisem TcpServer_MCastAdd und TcpServer_MCastRemove kann man auch zeitweise zwischen empfangen und ignorieren hin und her wechseln. z.b. als reaktion auf disable.

  • mit TcpServer_Close($hash); das ganze am ende z.b. in der UndefFn wieder zu machen.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

erwin

HI,

@Rudi:
...Ist ein Implementations-Beispiel, da hab ich vermutlich deinen Satz falsch verstanden...
ZitatIch habe auch nichts gegen einen universelleren noblocking-gethostbyname, gerne ausgeloest aus HttpUtils.pm.
@Andre:
mache ich gerne, wird allerdings (urlaubsbedingt) ein paar Tage dauern...
l.g. erwin
FHEM aktuell auf RaspberryPI Mdl 1-4
Maintainer: 00_KNXIO.pm 10_KNX.pm
User: CUNO2 (868 SLOWRF) - HMS100xx, FS20, FHT, 1-Wire  - 2401(iButton), 18x20, 2406, 2413 (AVR), 2450,..,MQTT2, KNX, SONOFF, mySENSORS,....
Hardware:  Busware ROT, Weinzierl IP731, 1-Wire GW,...

erwin

Hi Andre!
Erstes Feedback:
Multicast - read: funktioniert !
Multicast - write: ich muss im TcpServer_Open die richtige MC-addresse (nicht '0.0.0.0') angeben, sonst funktioniert das $hash->{SERVERSOCKET}->send($msg) NICHT, auch nicht mit $hash->{SERVERSOCKET}->send($completemsg,0,'244.0.x.y')
Ich muss jetzt allerdings noch error-recovery testen, und etliches an code-cleanup machen....
...ich hab das perl-multicast modul noch nicht ausgebaut, hoffentlich spielt das mir keinen Streich....
Schaut gut aus, Danke!
erwin
FHEM aktuell auf RaspberryPI Mdl 1-4
Maintainer: 00_KNXIO.pm 10_KNX.pm
User: CUNO2 (868 SLOWRF) - HMS100xx, FS20, FHT, 1-Wire  - 2401(iButton), 18x20, 2406, 2413 (AVR), 2450,..,MQTT2, KNX, SONOFF, mySENSORS,....
Hardware:  Busware ROT, Weinzierl IP731, 1-Wire GW,...

justme1968

du hast recht. das multicast senden hatte ich noch garnicht probiert. damit wäre dann auch der unterschied beim open geklärt :).

für mdns hatte ich das ganze schon tagelang laufen und es hat stabil funktioniert. wenn das bei dir auch weiterhin stabil funktioniert und niemand mehr eine idee für änderungen hat könnte rudi diesen teil dann eigentlich übernehmen.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

erwin

Hi Andre,
eine Kleinigkeit hab ich noch:
in sub TcpServer_MCastRemove das readingsSingleUpdate($hash, "state", "Multicast listen", 0); auf
readingsSingleUpdate($hash, "state", "Multicast disabled", 0); oder ähnlich ändern.
Warum die ganzen readingsSingleUpdate ohne event definiert sind erschließt sich mir nicht, aber Rudi hatte da wohl seine Gründe.
Noch was (für mich) kurioses:

               $ret = TcpServer_Open($hash, $port, '224.0.23.12', 1); # funktioniert
               $ret = TcpServer_Open($hash, $port, $host, 1); # funktioniert NICHT !!!
               $ret = TcpServer_Open($hash, $port, "$host", 1); # FUNKTIONIERT !!!
               .....
                $ret = TcpServer_MCastAdd($hash,$host);
               ....
# und zwar kommt der Fehler erst beim Versuch zu schreiben:
              $ret = $hash->{SERVERSOCKET}->send($completemsg);
# und zwar: send: Cannot determine peer address at ./FHEM/00_KNXIO2.pm line ....
# es hilft auch nicht die Addresse im send mitzugeben....

Allerdings kommts mir auf ein paar Hochkommas mehr nicht an  :)
ich möchte auch noch das IO::Socket::Multicast ausbauen und testen, ob es dann noch immer funktioniert....
l.g.erwin
FHEM aktuell auf RaspberryPI Mdl 1-4
Maintainer: 00_KNXIO.pm 10_KNX.pm
User: CUNO2 (868 SLOWRF) - HMS100xx, FS20, FHT, 1-Wire  - 2401(iButton), 18x20, 2406, 2413 (AVR), 2450,..,MQTT2, KNX, SONOFF, mySENSORS,....
Hardware:  Busware ROT, Weinzierl IP731, 1-Wire GW,...

justme1968

ich hab es auf "Multicast removed" geändert. disabled ist in fhem ja normalerweise etwas anderes.

das es mit $host ohne anführungszeichen nicht geht liegt vielleicht daran das perl versucht die adresse als zahl zu interpretieren. schau mal ob es mit "".$host geht. dann würde ich das direkt in die routine einbauen.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

erwin

Das mit den Anführungszeichen ist offensichtlich eine Fehlinterpretation von mir!!!
Es funktioniert auch ohne !!!!

Was mich in die Irre geführt hat: Es gibt unmittelbar nach einem shutdown/restart ein Zeitfenster (etliche Sekunden), wo ein write schiefgeht. - mit der besagten Message- und FHEM damit abschmiert!
Ob es ein wirklich ein Zeitfenster ist oder ob es damit zu tun hat, dass zuerst ein Receive passieren muss, weiss ich noch nicht... (irgendwo hab ich gegoogld, das send auf die peeraddr des letzten receive geht...)
2022.03.03 19:24:47.718 0: Server started with 14 defined entities (fhem.pl:25644/2022-02-06 perl:5.032001 os:linux user:fhem pid:21068)
2022.03.03 19:24:47.805 3: KNXIO_openDev: device myKNXIO_R opened
2022.03.03 19:24:47.843 5: KNXIO_openDev: M, 224.0.23.12, 3671, reopen=0
2022.03.03 19:24:47.843 2: KNXIO_openDev: myKNXIOTest 224.0.23.12 3671
2022.03.03 19:24:47.844 3: myKNXIOTest: port 3671 opened
2022.03.03 19:25:23.247 5: KNXIO_write: started
2022.03.03 19:25:23.247 5: KNXIO_write: sending w0a00900931917
2022.03.03 19:25:23.248 5: KNXIO_Write: str/size/acpi/src/dst= 34/4/128/323533/3230343839
send: Cannot determine peer address at ./FHEM/00_KNXIO2.pm line 556.
2022.03.03 19:25:23.750 1: Including fhem.cfg
.....

In diesem Beispiel sind es ca. 30 sec nach dem port open....
l.g. erwin
FHEM aktuell auf RaspberryPI Mdl 1-4
Maintainer: 00_KNXIO.pm 10_KNX.pm
User: CUNO2 (868 SLOWRF) - HMS100xx, FS20, FHT, 1-Wire  - 2401(iButton), 18x20, 2406, 2413 (AVR), 2450,..,MQTT2, KNX, SONOFF, mySENSORS,....
Hardware:  Busware ROT, Weinzierl IP731, 1-Wire GW,...

justme1968

ich bin noch nicht dazu gekommen das senden selber zu testen.

ich bin aber sicher das du immer die version mit drei parametern, also $hash->{SERVERSOCKET}->send($data,0,'244.0.x.y') verwenden musst.

die variante ohne adresse geht wenn überhaupt nur ab dem zweiten mal und verwendet automatisch die adresse aus dem vorherigen send mit adressangabe.

die zeitabhängigkeit die du siehst kann ich mir nicht erklären, ich vermute das ist eher ein seiteneffekt von irgendetwas anderem.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

erwin

Hi Andre,
das mit der Zeitabhängigkeit ist FALSCH!
Richtig ist, das zuerst ein receive kommen muss, damit die peeraddr. definiert ist! Und das war unmittelbar nach einem FHEM restart nicht der Fall, daher die Verwirrung!
Ich kann mich auch nicht darauf verlassen, dass vom KNX-Bus irgendwann eine msg (receive) kommt, der KNX-Router/deamon ist in diesem Fall der Server und FHEM der Client.
Allerdings funktioniert dein Vorschlag auch nicht, das hatte ich schon probiert...
$hash->{SERVERSOCKET}->send($data,0,'244.0.x.y')
hab etliches versucht, die peeraddr vor dem send zu setzen, zb. $iaddr = inet_aton('224.0.23.12');
$hash->{SERVERSOCKET}->bind($iaddr);
$hash->{SERVERSOCKET}->send($data,0,$iaddr)

allerdings ohne Erfolg....
FHEM aktuell auf RaspberryPI Mdl 1-4
Maintainer: 00_KNXIO.pm 10_KNX.pm
User: CUNO2 (868 SLOWRF) - HMS100xx, FS20, FHT, 1-Wire  - 2401(iButton), 18x20, 2406, 2413 (AVR), 2450,..,MQTT2, KNX, SONOFF, mySENSORS,....
Hardware:  Busware ROT, Weinzierl IP731, 1-Wire GW,...

erwin

Hi Andre,

scheint, dass ich das write gelöst habe:
my $paddr = Socket::sockaddr_in($port, inet_aton($host));
$ret = $hash->{SERVERSOCKET}->send($completemsg,0,$paddr);

so funktioniert das write, das hat etwas gedauert, bis ich durchschaut hab, wie $paddr aussehen muss.... ;D

Aus meiner Sicht spricht nichts dagegen, die TcpServerUtils einzuchecken!
l.g. & Danke erwin
FHEM aktuell auf RaspberryPI Mdl 1-4
Maintainer: 00_KNXIO.pm 10_KNX.pm
User: CUNO2 (868 SLOWRF) - HMS100xx, FS20, FHT, 1-Wire  - 2401(iButton), 18x20, 2406, 2413 (AVR), 2450,..,MQTT2, KNX, SONOFF, mySENSORS,....
Hardware:  Busware ROT, Weinzierl IP731, 1-Wire GW,...

justme1968

ah. das klingt gut.

ich denke ich baue noch so etwas wie TcpServer_MCastSend ein und mache dann den kompletten patch mit doku für rudi fertig.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968