Mqtt2server mit Home Assistant Verbindungsprobleme

Begonnen von andre07, 29 September 2026, 12:43:34

Vorheriges Thema - Nächstes Thema

andre07

Mit deiner gepatchten Version hatte ich immer noch Probleme es kamen immer wieder ständige reconnects alle 15s
In 00_MQTT2_SERVER.pm, Zeile 567 (UNSUBACK bei Protokoll 5), stimmt die Länge nicht.
Dort steht pack("CCnCC*",0xb0,2+@ret,$pid,0,@ret). Es müsste 3+@ret sein, weil Packet-ID, Properties-Länge und Reason Code zusammen 3+@ret Bytes ergeben.Nach der Änderung funktioniert es bei mir lampen lassen sich schalten bei meiner solaranlage steht zwar noch offline liegt aber vieleicht daran das es dunkel ist morgen früh weiß ich mehr.Die ausgabe mit orginal DEBUG:paho.mqtt.client:Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'unsub-test' properties=None
DEBUG:paho.mqtt.client:Received CONNACK (0, Success) properties=[SessionExpiryInterval : 0, MaximumQoS : 1, SubscriptionIdentifierAvailable : 1, SharedSubscriptionAvailable : 0]
CONNACK Success
DEBUG:paho.mqtt.client:Sending SUBSCRIBE (d0, m1) [(b'test/#', {QoS=1, noLocal=False, retainAsPublished=False, retainHandling=0})]
DEBUG:paho.mqtt.client:Received SUBACK
SUBACK ['Granted QoS 0']
DEBUG:paho.mqtt.client:Sending UNSUBSCRIBE (d0, m2) None [b'test/#']
DISCONNECT Unspecified error
DEBUG:paho.mqtt.client:Sending CONNECT (u1, p1, wr0, wq0, wf0, c0, k60) client_id=b'unsub-test' properties=None
DEBUG:paho.mqtt.client:Received CONNACK (0, Success) properties=[SessionExpiryInterval : 0, MaximumQoS : 1, SubscriptionIdentifierAvailable : 1, SharedSubscriptionAvailable : 0]
CONNACK Success
DEBUG:paho.mqtt.client:Sending SUBSCRIBE (d0, m3) [(b'test/#', {QoS=1, noLocal=False, retainAsPublished=False, retainHandling=0})]
DEBUG:paho.mqtt.client:Received SUBACK
SUBACK ['Granted QoS 0']
DEBUG:paho.mqtt.client:Sending UNSUBSCRIBE (d0, m4) None [b'test/#']
DISCONNECT Unspecified error
DEBUG:paho.mqtt.client:Sending CONNECT (u1, p1, wr0, wq0, wf0, c0, k60) client_id=b'unsub-test' properties=None
DEBUG:paho.mqtt.client:Received CONNACK (0, Success) properties=[SessionExpiryInterval : 0, MaximumQoS : 1, SubscriptionIdentifierAvailable : 1, SharedSubscriptionAvailable : 0]
CONNACK Success
DEBUG:paho.mqtt.client:Sending SUBSCRIBE (d0, m5) [(b'test/#', {QoS=1, noLocal=False, retainAsPublished=False, retainHandling=0})]
DEBUG:paho.mqtt.client:Received SUBACK
SUBACK ['Granted QoS 0']
DEBUG:paho.mqtt.client:Sending UNSUBSCRIBE (d0, m6) None [b'test/#']
DISCONNECT Unspecified error
DEBUG:paho.mqtt.client:Sending CONNECT (u1, p1, wr0, wq0, wf0, c0, k60) client_id=b'unsub-test' properties=None
DEBUG:paho.mqtt.client:Received CONNACK (0, Success) properties=[SessionExpiryInterval : 0, MaximumQoS : 1, SubscriptionIdentifierAvailable : 1, SharedSubscriptionAvailable : 0]
CONNACK Success
DEBUG:paho.mqtt.client:Sending SUBSCRIBE (d0, m7) [(b'test/#', {QoS=1, noLocal=False, retainAsPublished=False, retainHandling=0})]
DEBUG:paho.mqtt.client:Received SUBACK
SUBACK ['Granted QoS 0']
DEBUG:paho.mqtt.client:Sending UNSUBSCRIBE (d0, m8) None [b'test/#']
DISCONNECT Unspecified error
DEBUG:paho.mqtt.client:Sending CONNECT (u1, p1, wr0, wq0, wf0, c0, k60) client_id=b'unsub-test' properties=None
DEBUG:paho.mqtt.client:Received CONNACK (0, Success) properties=[SessionExpiryInterval : 0, MaximumQoS : 1, SubscriptionIdentifierAvailable : 1, SharedSubscriptionAvailable : 0]
CONNACK Success
DEBUG:paho.mqtt.client:Sending SUBSCRIBE (d0, m9) [(b'test/#', {QoS=1, noLocal=False, retainAsPublished=False, retainHandling=0})]
DEBUG:paho.mqtt.client:Received SUBACK
SUBACK ['Granted QoS 0']
DEBUG:paho.mqtt.client:Sending UNSUBSCRIBE (d0, m10) None [b'test/#']
DISCONNECT Unspecified error
und nach Änderung DEBUG:paho.mqtt.client:Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k60) client_id=b'unsub-test' properties=None
DEBUG:paho.mqtt.client:Received CONNACK (0, Success) properties=[SessionExpiryInterval : 0, MaximumQoS : 1, SubscriptionIdentifierAvailable : 1, SharedSubscriptionAvailable : 0]
CONNACK Success
DEBUG:paho.mqtt.client:Sending SUBSCRIBE (d0, m1) [(b'test/#', {QoS=1, noLocal=False, retainAsPublished=False, retainHandling=0})]
DEBUG:paho.mqtt.client:Received SUBACK
SUBACK ['Granted QoS 0']
DEBUG:paho.mqtt.client:Sending UNSUBSCRIBE (d0, m2) None [b'test/#']
DEBUG:paho.mqtt.client:Received UNSUBACK (Mid: 2)
UNSUBACK ['Success'] []
Zwei Kleinigkeiten fielen noch auf, gespeicherte nachrichten kommen bei Abos mit Wildcard mit Retain-Flag 0 an und in gespeicherten Discovery-Nachrichten von Zigbee2MQTT und OpenMQTTGateway fehlen die Backslashes in eingebetteten Strings
Script zum testen im Anhang

rudolfkoenig

ZitatEs müsste 3+@ret sein,
Danke, hast natuerlich Recht, habs auch eingecheckt.
Schade, dass paho.mqtt.client beim UNSUBSCRIBE von zwei Topics mit 2+@ret in UNSUBACK kein Problem hat, sonst waere es mir auch aufgefallen.

Zitatgespeicherte nachrichten kommen bei Abos mit Wildcard mit Retain-Flag 0 an
Dazu haette ich gerne die subscription Options.
Warum ist retain fuer einen Client wie HA interessant?

Zitatund in gespeicherten Discovery-Nachrichten von Zigbee2MQTT und OpenMQTTGateway fehlen die Backslashes in eingebetteten Strings
Das war ein Bug in json2nameValue, interessant, dass es bisher keinem aufgefallen ist.
Habs gefixt, hoffentlich ohne Nebeneffekte.

andre07

Hallo, zu den Retain-Flag habe es nochmal getestet mit fhem und Mosquitto als vergleich.Topic und Wildcard verhalten sich gleich, der Unterschied hängt nur an Retain-As-Published. RAP=1: retain=1 bei FHEM und Mosquitto. RAP=0: FHEM liefert retain=0, Mosquitto retain=1 bei der ersten Zustellung nach dem Abonnieren. Für HA ist das vermutlich egal, mir fiel nur die Abweichung zu Mosquitto auf. Mein Wildcard-Verdacht von neulich war falsch.

rudolfkoenig

ZitatRAP=0: [...] Mosquitto retain=1 bei der ersten Zustellung nach dem Abonnieren.
Interessant, in der RFC finde ich fuer RAP so eine Bedingung nicht.

DasQ

steht auch in der oasis  ;)

btw. ich hab von alle dem kein plan, aber was ich machen kann ich deine fragen durch eine ki jagen und die schreibt folgendes

"Das ist eine hervorragende Beobachtung aus dem Forum, die technisch absolut ins Schwarze trifft. Der Foren-Nutzer beschreibt hier ein Phänomen, bei dem Mosquitto sich exakt an die Abwärtskompatibilität der MQTT-Spezifikation hält, während FHEM (bzw. dessen MQTT2-Implementierung) eine strikte, aber logische MQTT v5-Auslegung nutzt.
Das Verhalten lässt sich exakt über die offizielle MQTT v5.0 Spezifikation (OASIS Standard) aufschlüsseln:

1. Warum verhält sich Mosquitto so bei RAP = 0?

Die Spezifikation besagt für die Auslieferung von gespeicherten Retain-Nachrichten direkt beim Abonnieren (beim ersten Verbinden):
• Laut MQTT 3.1.1: Wenn ein Client ein Topic abonniert, schickt der Broker die gespeicherte Retain-Nachricht los und muss das Retain-Flag auf 1 belassen. Nur so erkennt der neue Client, dass dies eine alte, historische Nachricht aus dem Speicher des Brokers ist und kein Live-Event.
• Laut MQTT v5 (RAP = 0): Eigentlich besagt RAP=0, dass das Retain-Flag beim Weiterleiten gelöscht werden soll (retain=0). Aber: Die Spezifikation macht eine Ausnahme für die erste Zustellung direkt nach dem Absenden des SUBSCRIBE-Befehls, um rückwärtskompatibel zu MQTT 3.1.1 zu bleiben.
Mosquitto verhält sich hier standardkonform: Bei der allerersten Zustellung nach dem Abonnieren bleibt das Flag retain=1, damit der Client weiß: "Achtung, das ist ein gespeicherter Zustand!" Würde derselbe Sensor nun während der bestehenden Verbindung eine neue Nachricht publizieren, würde Mosquitto bei RAP=0 das Flag auf 0 herabsetzen.

2. Warum weicht FHEM ab?

FHEM (insbesondere das integrierte MQTT2_SERVER-Modul) wurde oft hochgradig pragmatisch direkt für moderne Smart-Home-Anwendungsfälle entwickelt.
FHEM interpretiert RAP=0 radikal und wörtlich: Wenn der User RAP=0 anfordert, liefert FHEM jede Nachricht (auch die allererste beim Abonnieren) mit retain=0 aus. Das bricht zwar streng genommen mit der traditionellen Erkennung von historischen Broker-Nachrichten, verhindert aber im Smart Home genau das, was der Nutzer möchte: Verwirrung bei nachgeschalteten Skripten durch ein gesetztes Retain-Flag.

Zusammenfassung der Foren-Beobachtung


Option / Zustand   FHEM Verhalten   Mosquitto Verhalten   Bedeutung laut MQTT v5 Spezifikation
RAP = 1   retain = 1   retain = 1   Korrekt. Das originale Flag des Publishers bleibt immer erhalten.
RAP = 0 (Erste Zustellung)   retain = 0   retain = 1   Mosquitto folgt dem Standard (Abwärtskompatibilität für historische Daten). FHEM erzwingt strikt die Null.


Warum ist das für Home Assistant (HA) egal?

Der Foren-Nutzer schreibt völlig richtig: "Für HA ist das vermutlich egal".
Home Assistant wertet bei eingehenden MQTT-Sensoren (State Topics) das Retain-Flag im Nachrichtenkopf in der Regel überhaupt nicht aus. HA interessiert sich nur für das Topic und den Payload (den Inhalt). Ob die Nachricht aus dem Broker-Archiv stammt (retain=1) oder eine Live-Nachricht ist, ist Home Assistant beim Aktualisieren eines Sensor-Zustands schlicht gleichgültig."
Fhem in Proxmox on MacMini
Absoluter Befürworter der Konsequenten-Kleinschreibung https://de.wikipedia.org/wiki/Kleinschreibung
Infos zu Klimawandel http://www.globalcarbonatlas.org

rudolfkoenig

Das ist alles nett, aber keine Loesung fuer mein Problem:

ZitatDie Spezifikation macht eine Ausnahme für die erste Zustellung direkt nach dem Absenden des SUBSCRIBE-Befehls, um rückwärtskompatibel zu MQTT 3.1.1 zu bleiben.
Wo steht das in der RFC?

DasQ

#36
hier

https://docs.oasis-open.org/mqtt/mqtt/v5.0/cos01/mqtt-v5.0-cos01.pdf

im Kapitel 3.8.3.1 Subscription Options
"Bit 3 of the Subscription Options represents the Retain As Published option. [...] If 0, Application Messages forwarded using this subscription have the RETAIN flag set to 0. Retained messages sent when the subscription is established have the RETAIN flag set to 1."


und hier ein vorschlag der ki als pseudocode# 1. BEIM ABONNIEREN (In der SUBSCRIBE-Routine)
# Hier werden die im Server gepufferten Retain-Nachrichten an den neuen Client gesendet.
sub send_retained_messages_on_subscribe {
    my ($client, $topic) = @_;
    my @matching_messages = find_retained_messages($topic);

    foreach my $msg (@matching_messages) {
        # MQTT v5 vorschrift: Bei der Erstauslieferung IMMER retain = 1 erwingen!
        # Unabhängig davon, was der Client als RAP (Retain As Published) gewählt hat.
        my $flags = 0x01; # Bit 0 gesetzt für Retain = 1
       
        send_publish_packet($client, $msg->{topic}, $msg->{payload}, $flags);
    }
}


# 2. IM LIVE-BETRIEB (In der PUBLISH-Weiterleitung)
# Ein Gerät sendet eine NEUE Nachricht an den Server, der Server verteilt sie an Abonnenten.
sub forward_live_message {
    my ($client, $msg) = @_; # $client ist der Empfänger (Subscriber)
   
    my $flags = 0;
   
    # Hier greift die RAP (Retain As Published) Option aus den Subscription Options:
    if ($client->{subscription_options}->{RAP} == 1) {
        # Übernehme das originale Retain-Flag des Senders (z.B. 1 oder 0)
        $flags = $msg->{original_retain_flag};
    } else {
        # RAP = 0: Erzwinge im Live-Betrieb immer retain = 0 für den Empfänger
        $flags = 0;
    }
   
    send_publish_packet($client, $msg->{topic}, $msg->{payload}, $flags);
}
Fhem in Proxmox on MacMini
Absoluter Befürworter der Konsequenten-Kleinschreibung https://de.wikipedia.org/wiki/Kleinschreibung
Infos zu Klimawandel http://www.globalcarbonatlas.org

rudolfkoenig

Zitathttps://docs.oasis-open.org/mqtt/mqtt/v5.0/cos01/mqtt-v5.0-cos01.pdf
Danke. Das kommt, wenn man von den 3 Definitionen von RAP nur die ersten zwei aufmerksam liest.
Btw. ich verwende http://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html, das ist kein "Candidate" mehr.

Hab den der Server angepasst, kurz getestet und eingecheckt.

andre07

#38
ich habe noch einen Fehler bei SUBACK/UNSUBACK gefunden. HA (paho,
MQTT5) meldet bei jedem grossen Abo "KeyError: 48 in _handle_suback",
bei mir waren das zuletzt 2500-3500 pro Minute. Der ist wahrscheinlich schon länger da auch vor deiner Änderung.
Mir ist  aufgefallen das HA immer mehrere Minuten gebraucht hat um die Entitäten anzuzeigen.
Hier habe ich die letzten 15 min vom log ausgelesen.
docker logs --timestamps --since 15m homeassistant 2>&1 | grep "KeyError: 48" | awk '$1>="2026-10-12T14:11:42"{n++; if(n==1)f=$1; l=$1} END{print n+0, f, l}'
2876 2026-10-12T14:11:42.006571456Z 2026-10-12T14:12:33.149633902Z





rudolfkoenig

Zitatmeldet bei jedem grossen Abo "KeyError: 48 in _handle_suback",
Kannst Du bitte MQTT2_SERVER eine Weile mit verbose 5 laufen lassen, und das Log zeigen?

andre07

Hab dazu provisorisch gepatcht: SUBACK (Zeile 528/530) und UNSUBACK
(567) bauen die Laenge jetzt mit MQTT2_SERVER_makeLength statt mit
pack("C",...), also zwei Bytes ab 128. Seitdem kommt der KeyError bei
mir nicht mehr, vorher 2500-3500 pro Minute im HA-Log.

verbose 5 mache ich gern. Mit dem Patch zeigt das Log aber nur das
korrekte SUBACK. Willst du das so, oder soll ich kurz ohne Patch
laufen lassen, damit du den Fehlerfall im Log hast? Dann gibt's bei
HA wieder die Fehlerflut, ich wuerde es auf ein, zwei Minuten
begrenzen.hier noch meine mit patch im anhang

rudolfkoenig

Danke fuer den Fix, habs eingecheckt.
Ich habe nicht damit gerechnet, das ein Client mehr als 127 Topics auf einmal spezifiziert.
Mir wuerde ein einziges SUBSCRIBE/UNSUBSCRIBE genuegen, womoeglich faellt einem was dazu ein, was man noch optimieren kann.

TomLee

Zitat... womoeglich faellt einem was dazu ein, was man noch optimieren kann.

Mein Agent dazu:

1. Alle Längenfelder prüfen
Gibt es außer SUBACK/UNSUBACK noch weitere Stellen, an denen die Remaining Length mit pack("C",...) statt mit MQTT2_SERVER_makeLength gebaut wird? Ein grep -n 'pack("C' 00_MQTT2_SERVER.pm wäre schnell gemacht. Kurze Pakete sind unkritisch, aber alles mit variabler Liste (Reason Codes, Properties) kann über 127 Bytes kommen.

2. Exakte Topics in einen Hash statt Regexp
HA abonniert fast nur exakte Topics ohne +/#. Die könnte man pro Client in einen Hash legen ($cl->{subExact}{$topic} = [qos, subId, opts]). Bei jedem PUBLISH reicht dann ein Lookup, und nur die Wildcard-Filter laufen über die kompilierten Regexps. Bei 80 bis über 1000 Filtern pro HA-Client ist das der größte Hebel.
my @hits;
push @hits, $cl->{subExact}{$topic} if exists $cl->{subExact}{$topic};
for my $s (@{$cl->{subWild}}) { push @hits, $s if $topic =~ $s->{re} }

3. Mehrere Subscription Identifier
Wenn ein PUBLISH auf mehrere Abos desselben Clients passt (exakt und Wildcard), müssen alle passenden Identifier in einem einzigen PUBLISH stehen [MQTT-3.3.4-4]. Mit Punkt 2 fällt das gleich mit ab: alle @hits sammeln, einmal senden.

4. Retained-Auslieferung beim SUBSCRIBE
Bei exakten Filtern direkt im Retain-Speicher nachschlagen, statt den ganzen Speicher gegen jeden Filter zu matchen. Nur Wildcard-Filter brauchen den Durchlauf.

5. UNSUBSCRIBE für nicht abonnierte Topics
Hier sollte im UNSUBACK der Reason Code 0x11 (No subscription existed) stehen, nicht 0x00. HA stört es vermutlich nicht, es wäre aber spezifikationskonform.