MQTT2 für Worx Landroid Mähroboter

Begonnen von Otto123, 09 Juni 2020, 13:55:43

Vorheriges Thema - Nächstes Thema

frober

Erste Stufe geschafft...

Ich habe einen Token  ;D
Raspi 3b mit Raspbian Buster und relativ aktuellem Fhem,  FS20, LGW, PCA301, Zigbee, MQTT, MySensors mit RS485(CAN-Receiver) und RFM69, etc.,
einiges umgesetzt, vieles in Planung, smile

********************************************
...man wächst mit der Herausforderung...

remo

Erstmal danke für eure Bemühungen.
Aber kann mir vielleicht bitte jemand erklären wo genau das Problem liegt?
Im ioBroker läuft es (wieder) (selbst getestet).
In OpenHAB soll es wohl auch (wieder) funktionieren (hab ich nur gelesen).

frober

Einfach gesagt, die Authentifizierung wurde von Passwort auf Token und Websocket Security umgestellt.
Dazu kommt, dass der Token nur 1h gültig ist.

Ich bin auf einem guten Weg, habe aber leider nicht täglich Zeit.



Raspi 3b mit Raspbian Buster und relativ aktuellem Fhem,  FS20, LGW, PCA301, Zigbee, MQTT, MySensors mit RS485(CAN-Receiver) und RFM69, etc.,
einiges umgesetzt, vieles in Planung, smile

********************************************
...man wächst mit der Herausforderung...

frober

#438
Aktuell komme ich nicht weiter  :(

Den Token bekomme ich, habe ihn nach Vorgabe manipuliert und den Websocket-Header erstellt.

@Rudi, kann ich die Serverantwort irgendwie besser debuggen?

Nachtrag: laut Inet wäre bei MQTT over WS der PORT 9001, damit bekomme ich keine Fehler- und auch keine Rückmeldung

erstens zur Info:
Über myUtils hole ich den Token, erstelle daraus den Header und übergebe ihn nach deinem ersten VersuchsBsp.
$defs{MQTT2_Worx}{header} =  { "x-amz-customauthorizer-name"=>"com-worxlandroid-customer", "x-amz-customauthorizer-signature"=>$token[2], jwt=>$token[0].".".$token[1]};
return fhem('set MQTT2_Worx connect');


Im Log bekomme ich dann:
2023.03.14 18:53:20 5: HttpUtils url=https://iot.eu-west-1.worxlandroid.com:8883/ NonBlocking via https
2023.03.14 18:53:21 4: IP: iot.eu-west-1.worxlandroid.com -> 52.31.129.120
2023.03.14 18:53:21 5: HttpUtils request header:
GET / HTTP/1.1
Host: iot.eu-west-1.worxlandroid.com:8883
User-Agent: fhem
Accept-Encoding: gzip,deflate
Sec-WebSocket-Version: 13
x-amz-customauthorizer-name: com-worxlandroid-customer
x-amz-customauthorizer-signature: JcZu1JbB2tra9KOOnl57NRKjf+xYbELYSvWamxbKf5oKt/NGjK5BifhQmSMv5m4FiM3IhOPljzu3Ao+gGoaMN4GRN5l+9VKRX0nT93DmCcGE
Upgrade: websocket
Sec-WebSocket-Protocol: mqtt
Connection: Upgrade
jwt: JKV1QiLCJhbGciOiJSUzI1NiJ9.zBhMmEiLCJqdGkiOiIzNTJjOWE3YzJiODA5YWYzYjVkYjdiMjM5NjY0ZmFiZWFlYzZkNWFlZTM4YmQwNDk5YThmZTBiZjM4ODIwMDEmlhdCI6MTY3ODgxNj
Sec-WebSocket-Key: l/Ds6EzsXVh9gaBoO/wp4g==


Den Token habe ich natürlich verfälscht/gekürzt  ;D

Mit dieser Meldung komme ich nicht weiter...

P.S. für den MQTT_Connect wird vom ioBroker das aws-sdk verwendet...keine Ahnung, ob es da Besonderheiten gibt (protocol: "wss-custom-auth",)
Raspi 3b mit Raspbian Buster und relativ aktuellem Fhem,  FS20, LGW, PCA301, Zigbee, MQTT, MySensors mit RS485(CAN-Receiver) und RFM69, etc.,
einiges umgesetzt, vieles in Planung, smile

********************************************
...man wächst mit der Herausforderung...

frober

Wieder ein Stück weiter...

PORT 443 für MQTT over WS bei AWS  ::)
https://docs.aws.amazon.com/iot/latest/developerguide/protocols.html

Nun bekomme ich auch ein Request...aber noch kein connect.
Raspi 3b mit Raspbian Buster und relativ aktuellem Fhem,  FS20, LGW, PCA301, Zigbee, MQTT, MySensors mit RS485(CAN-Receiver) und RFM69, etc.,
einiges umgesetzt, vieles in Planung, smile

********************************************
...man wächst mit der Herausforderung...

frober

was fehlt mir hier??

Zitat2023.03.14 21:56:29 4: https://iot.eu-west-1.worxlandroid.com:443/: HTTP response code 101
2023.03.14 21:56:29 5: HttpUtils https://iot.eu-west-1.worxlandroid.com:443/: Got data, length: 0
2023.03.14 21:56:29 5: HttpUtils response header:
HTTP/1.1 101 Switching Protocols
content-length: 0
upgrade: websocket
connection: upgrade
sec-websocket-accept: p+olasrgKWIYZL0yYCT/ctS/leA=
sec-websocket-protocol: mqtt
2023.03.14 21:56:29 5: MQTT2_Worx: sending CONNECT (16)9(0)(4)MQTT(4)(2)(3)(232)(0)-android-blabla(10)
2023.03.14 21:56:29 5: DevIo_SimpleWrite MQTT2_Worx: 02155103946406460044d51545404020
2023.03.14 21:56:29 1: wss:iot.eu-west-1.worxlandroid.com:443 reappeared (MQTT2_Worx)
2023.03.14 21:56:29 5: Websocket msg: OP:8 LEN:0 MASK:0 FIN:1
2023.03.14 21:56:29 5: Websocket close, reason:
Raspi 3b mit Raspbian Buster und relativ aktuellem Fhem,  FS20, LGW, PCA301, Zigbee, MQTT, MySensors mit RS485(CAN-Receiver) und RFM69, etc.,
einiges umgesetzt, vieles in Planung, smile

********************************************
...man wächst mit der Herausforderung...

rudolfkoenig

101 ist nichts Schlimmes, sagt nur, dass ab sofort auf dieser Leitung mit websocket weitergeht, das hat man ja vorher auch beantragt.
Was schlimm ist, dass die Gegenseite nach dem MQTT CONNECT die Leitung ohne Begruendung zuklappt, es gibt doch eine hoefliche Version mit CONNACK und Begruendung. Auch beim websocket close darf man eine Begruendung angeben, leider ist das auch hier optional.

Die Voreinstellung in MQTT2_CLIENT ist mqttVersion 3.1, womoeglich besteht die Gegenseite auf 3.1.1.
Muss man evtl. beim MQTT CONNECT Benutzername/Passwort angeben?

frober

Zitat von: rudolfkoenig am 15 März 2023, 10:45:06
Die Voreinstellung in MQTT2_CLIENT ist mqttVersion 3.1, womoeglich besteht die Gegenseite auf 3.1.1.
Muss man evtl. beim MQTT CONNECT Benutzername/Passwort angeben?

Zur Verwendung von Benutzername/Passwort habe ich bisher im ioBroker-Code keine Hinweise gefunden.

Aber mqttVersion 3.1.1 bzw. 5.0 werden, bzw. können beide verwendet werden.
Und "Clients müssen die TLS-Erweiterung Server Name Indication (SNI) in der Verbindungsanforderung senden. Verbindungsversuche, die das SNI nicht enthalten, werden abgelehnt."


Raspi 3b mit Raspbian Buster und relativ aktuellem Fhem,  FS20, LGW, PCA301, Zigbee, MQTT, MySensors mit RS485(CAN-Receiver) und RFM69, etc.,
einiges umgesetzt, vieles in Planung, smile

********************************************
...man wächst mit der Herausforderung...

rudolfkoenig

Fuer SNI muss man SSL_hostname in den sslargs Pararameter setzen, sagt https://metacpan.org/pod/IO::Socket::SSL.
Das wird aber von HttpUtils automatisch auf dem aus der Anfrage abgeleiteten Hostname gesetzt, falls die Bibliothek SNI unterstuetzt.

Wenn das Problem an SNI liegen wuerde, dann duerfte mAn kein OK auf das mit Token gesicherte websocket Anfrage kommen.
Wenn das kommt, dann ist man bei dem richtigen Server.


Ralli

Zitat von: frober am 15 März 2023, 15:43:52
Zur Verwendung von Benutzername/Passwort habe ich bisher im ioBroker-Code keine Hinweise gefunden.

Benutzername/Passwort nicht, aber wenn ich den Code richtig interpretiere der Token (Zeile 882, wird aus 731 aufgerufen):


    createWebsocketHeader() {
        const accessTokenParts = this.session.access_token.replace(/_/g, "/").replace(/-/g, "+").split(".");
        const headers = {
            "x-amz-customauthorizer-name": "com-worxlandroid-customer",
            "x-amz-customauthorizer-signature": accessTokenParts[2],
            jwt: `${accessTokenParts[0]}.${accessTokenParts[1]}`,
        };
        return headers;
Gruß,
Ralli

Proxmox 8.2 Cluster mit HP ED800G2i7, Intel NUC11TNHi7+NUC7i5BNH, virtualisiertes fhem 6.3 dev, virtualisierte RaspberryMatic (3.75.7.20240420) mit HB-RF-ETH 1.3.0 / RPI-RF-MOD, HM-LAN-GW (1.1.5) und HMW-GW, FRITZBOX 7490 (07.57), FBDECT, Siri und Alexa

rudolfkoenig

Die wurden doch angegeben, steht oben im Log.
Websocket Verbindung wurde daraufhin erfolgreich hergestellt (siehe 101, Switching Protocols), Probleme gibts bei MQTT CONNECT.

Aber: "return fhem('set MQTT2_Worx connect');" verwirrt mich, das sollte nicht notwendig sein.
Koennte ich den kompletten Code (myUtils.pm + MQTT-Definition) sehen?

Ralli

Das ist korrekt, aber muss das nicht im eigentlichen MQTT-Connect auch mit rein?


this.mqttC = awsIot.device({
                clientId: `${this.clouds[this.config.server].mqttPrefix}/USER/${this.userData.id}/iobroker/${uuid}`,
                username: "iobroker",
                protocol: "wss-custom-auth",
                host: mqttEndpoint,
                region: region,
                customAuthHeaders: headers,
                baseReconnectTimeMs: 5000,
            });


Die Variable headers wird auch hier mitgegeben.
Gruß,
Ralli

Proxmox 8.2 Cluster mit HP ED800G2i7, Intel NUC11TNHi7+NUC7i5BNH, virtualisiertes fhem 6.3 dev, virtualisierte RaspberryMatic (3.75.7.20240420) mit HB-RF-ETH 1.3.0 / RPI-RF-MOD, HM-LAN-GW (1.1.5) und HMW-GW, FRITZBOX 7490 (07.57), FBDECT, Siri und Alexa

frober

#447
Zitat von: rudolfkoenig am 15 März 2023, 17:28:44
Aber: "return fhem('set MQTT2_Worx connect');" verwirrt mich, das sollte nicht notwendig sein.
Koennte ich den kompletten Code (myUtils.pm + MQTT-Definition) sehen?
na klar, wenn es Sinn macht, sende sie dir auch mit meinen User/Passwort etc. zu

Vorweg 3 Fragen:
1. das hier gibt eine ziemliche Bastelei -> Dummy um Sub aufzurufen, dann ein einmaliges at um den Token jede Stunde zu erneuern und min. ein notify um nach Neustart/disconnect ein reconnect auszulösen.
Wäre es machbar, ein weiteres Attribut "perlConnectSub" einzuführen, wenn dieses gesetzt ist wird dann bei einem connect nicht die Url sondern die Sub aufgerufen?

2. die Übersetzung von Javascript `$a.$b` zu Perl $a.".".$b ist korrekt?

3. maxNrConnects  wird nur bei einem erfolgreichen connect zurückgesetzt?
Mit maxNrConnects 1 konnte ich nur einen Fehlversuch starten, danach musste ich die Anzahl erhöhen.
Wenn ich maxNrConnects lösche und der Token ist ungültig, wird wieder das Log zugemüllt.

Def:
defmod MQTT2_Worx MQTT2_CLIENT wss:iot.eu-west-1.worxlandroid.com:443
attr MQTT2_Worx SSL 1
attr MQTT2_Worx autocreate simple
attr MQTT2_Worx clientId android-xxxxxxxxx\

attr MQTT2_Worx disable 0
attr MQTT2_Worx group MQTT2
attr MQTT2_Worx keepaliveTimeout 1000
attr MQTT2_Worx maxNrConnects 6
attr MQTT2_Worx mqttVersion 3.1.1
attr MQTT2_Worx msgAfterConnect PRM100/FCxxxxxxxxBC/commandIn {}
attr MQTT2_Worx room System->Steuerung
attr MQTT2_Worx subscriptions PRM100/FCxxxxxxxBC/commandOut
attr MQTT2_Worx verbose 5


myUtils (mein debug habe ich auskommentiert):
package main;

use strict;
use warnings;
use HttpUtils;

sub
myUtils_Landroid_Initialize {
  my $hash = shift;
  return;
}

sub connectWorx
{
    #my $file = 'Landroid.log';

    my $hash = 'WorxTest';
    #my $def = shift;
    #my $name = $hash->{NAME};
    my $param = {
                    url        => "https://id.eu.worx.com/oauth/token",
                    timeout    => 5,
                    hash       => $hash,
                    method     => "POST",
                    header     => "User-Agent: TeleHeater/2.2.3\r\nAccept: application/json\r\ncontent-type: application/json\r\naccept-language: de-de",
    data       => '{"client_id": "150da4d2-bb44-433b-9429-3773adc70a2a","username": "xxx@xxx.de","password": "xxx","scop": "*","grant_type": "password"}',
                    callback   => \&Worxtoken
                };
    #debuglog("data: ".$param->{data}, $file);

    HttpUtils_NonblockingGet($param);
}

sub Worxtoken($)
{
    my $param = shift;
    my $err = shift;
    my $data = shift;
    my $hash = $param->{hash};
    my $name = 'Landroid';

    #my $file = 'Landroid.log';

    if($err ne "")                                                                                                       # wenn ein Fehler bei der HTTP Abfrage aufgetreten ist
    {
#debuglog("error while requesting ".$param->{url}." - $err", $file);
        Log3 $name, 3, "error while requesting ".$param->{url}." - $err";                                               # Eintrag fürs Log
       
    }

    elsif($data ne "")                                                                                                   # wenn die Abfrage erfolgreich war ($data enth? die Ergebnisdaten des HTTP Aufrufes)
    {
#debuglog("url ".$param->{url}." returned: $data", $file);
        Log3 $name, 3, "url ".$param->{url}." returned: $data";                                                         # Eintrag fürs Log

        my @array = ();
push @array, $data =~ /token_type.:.([^"]+).*expires_in.:([^"]+),.*access_token.:.([^"]+).*refresh_token.:.([^"]+)/gis;  # Token etc. aus Json auslesen

#debuglog("\ntokentyp: $array[0] \nAblauf: $array[1] \naccess: $array[2] \nrefresh: $array[3]", $file);

$array[2] =~ s/[_]/\//g; # im Token _ durch / ersetzen
$array[2] =~ s/[-]/+/g; # im Token - durch + ersetzen
my @token = split(/\./,$array[2]); # Token beim . splitten

#debuglog("\nTeil 1: $token[0] \nTeil 2: $token[1] \nTeil 3: $token[2]", $file);

$defs{MQTT2_Worx}{header} =  { "x-amz-customauthorizer-name"=>"com-worxlandroid-customer", "x-amz-customauthorizer-signature"=>$token[2], jwt=>$token[0].".".$token[1]}; # websocket header erstellen für MQTT2_CLIENT

#debuglog("$defs{MQTT2_Worx}{header}", $file);
# return fhem ("attr MQTT2_Worx HttpHeader $header; set MQTT2_Worx connect; defmod at_reconnect at $array[1]-100 {refreshToken()}");
return fhem('set MQTT2_Worx connect');

       
    }
return;   
}
Raspi 3b mit Raspbian Buster und relativ aktuellem Fhem,  FS20, LGW, PCA301, Zigbee, MQTT, MySensors mit RS485(CAN-Receiver) und RFM69, etc.,
einiges umgesetzt, vieles in Planung, smile

********************************************
...man wächst mit der Herausforderung...

frober

Zitat von: Ralli am 15 März 2023, 18:34:40
Das ist korrekt, aber muss das nicht im eigentlichen MQTT-Connect auch mit rein?


this.mqttC = awsIot.device({
                clientId: `${this.clouds[this.config.server].mqttPrefix}/USER/${this.userData.id}/iobroker/${uuid}`,
                username: "iobroker",
                protocol: "wss-custom-auth",
                host: mqttEndpoint,
                region: region,
                customAuthHeaders: headers,
                baseReconnectTimeMs: 5000,
            });


Die Variable headers wird auch hier mitgegeben.


schaue dir meine Code an...
wie Rudi schon geschrieben hat, websocket funktioniert
Raspi 3b mit Raspbian Buster und relativ aktuellem Fhem,  FS20, LGW, PCA301, Zigbee, MQTT, MySensors mit RS485(CAN-Receiver) und RFM69, etc.,
einiges umgesetzt, vieles in Planung, smile

********************************************
...man wächst mit der Herausforderung...

rudolfkoenig

Zu 1: ueber den optimalen Weg denken wir nach, wenn der Rest funktioniert. Immerhin verstehe ich anhand des Codes, was Du meinst :)
Zu 2: nicht ganz, JavaScript kennt kein $a. Richtig waere `${a}.${b}`, JS Template ersetzt im Backquote ${...} mit dem Ergebnis von eval(...) aus.
Zu 3: nein, das wird z.Zt nur bei define oder modify auf 0 gesetzt.