ACK Verständnisfrage

Begonnen von KarlHeinz2000, 30 September 2020, 15:16:27

Vorheriges Thema - Nächstes Thema

KarlHeinz2000

Ich habe einen kleinen RS485 Aufbau mit GW und einem Node und teste das ACK, um meine Übertragung zuverlässiger zu machen.
Dabei ist mir aufgefallen, dass im Falle eines fehlenden ACK das heartbeat reading auf NACK gesetzt wird und nicht der state. Soll das so sein?
Heartbeat wird auch nur durch ein gesendetes Heartbeat wieder auf alive gesetzt. Ein erfolgreiches ACK bewirkt das nicht.
Wie wäre der angedachte Ablauf? Ich sende Daten zum Node mit ACK aktiv und warte dann, ob das entsprechende Reading innerhalb einer Zeit auf den neuen Wert geht? Wenn nicht-> nicht zugestellt. Oder wertet man das heartbeat reading bzgl NACK aus? Aber wie?

Beta-User

Also, die story war die, dass heartbeat ursprünglich mal in state stattgefunden hat, weil das (scheinbar) gar nicht genutzt wurde.
Irgendwie war das aber nicht der richtige Ort, daher ist es insgesamt eben in ein eigenes Reading "heartbeat" gewandert, was dann nicht ganz optimal ist, wenn man es mit "state"-settern zu tun hat (also z.B. einem Relay, das man per setCommands nach "state" geschoben hat). Sowas ist aber die ziemliche Ausnahme, was man schon daran sieht, dass du nach Jahren der erste bist, der darüber grübelt ;D ...

Im Prinzip könnte man "dead" etc. ja auch wieder zurücksetzen, wenn "irgendwas" von der Node kommt. Das war mal in der Diskussion, hätte aber eine deutliche Erhöhung der Systemlast zur Folge gehabt, daher hatte ich das damals nicht umgesetzt. Du hat jetzt aber m.E. berechtigt einen "Sonderfall" aufgezeigt, werde mal schauen, wie man Antworten auf "Ack"-Anforderungen an der Stelle anders behandeln kann.

(Btw.: eine heartbeat-Message sollte nach meinem Codeverständnis eigentlich ein NACK nicht zurücksetzen, sondern erst eine zeitgerecht bestätigte (andere) ACK-Anforderung. Wenn wir das aber in der Ack-Verarbeitung berücksichtigen, kann das m.E. auch so bleiben, alles andere wäre unnötiger overhead.
Wobei man von ACK im engeren Sinne eigentlich nur noch bei nRF24 sprechen sollte, im MyS-Node-Code nennt sich das seit einiger Zeit bei anderen Transport-Layern "echo").
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

Beta-User

Mags du mal zum testen diese Routine in 10_MYSENSORS_DEVICE.pm tauschen:

sub onSetMessage {
    my $hash = shift;
    my $msg  = shift // return;
    my $name = $hash->{NAME};
    if (defined $msg->{payload}) {
      eval {
        my ($reading,$value) = rawToMappedReading($hash,$msg->{subType},$msg->{childId},$msg->{payload});
        readingsBeginUpdate($hash);
        readingsBulkUpdate($hash, $reading, $value);
        if ( defined ($hash->{setcommands}->{$value}) && $hash->{setcommands}->{$value}->{var} eq $reading ) { #$msg->{childId}
           if ($hash->{SetExtensionsCommand} && AttrVal($name, "setExtensionsEvent", undef)) {
             readingsBulkUpdate($hash,"state",$hash->{SetExtensionsCommand}) ; 
           } else {
             readingsBulkUpdate($hash,"state","$value");
             SetExtensionsCancel($hash) if !$msg->{ack};
           }
         }
      };
      readingsBulkUpdate($hash,"heartbeat","alive") if $msg->{ack} && ReadingsVal($hash,"heartbeat","dead") eq "NACK" and @{$hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}} == 0;
      readingsEndUpdate( $hash, 1 );
      Log3 ($hash->{NAME}, 4, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message ".GP_Catch($@)) if $@;
    } else {
      Log3 ($hash->{NAME}, 5, "MYSENSORS_DEVICE $hash->{NAME}: ignoring C_SET-message without payload");
    }
    return;
}
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

KarlHeinz2000

Alles klar. Mache ich heute Abend.
Danke.

KarlHeinz2000

Habe das kurz getestet. Keine Änderung. :(
Nach einem fehlenden ACK bleibt das heartbeat reading auf NACK. Egal ob die ACKs später wieder kommen. Auch einreboot des Node ändert nichts. Nur ein gesendeter heartbeat setzt das reading auf alive. Der Übergang von dead auf NACK funktioniert.

PS:
das requestAck Attribut lässt sich nur mit deleteattr löschen. Das Ändern über das drop down Feld geht nicht (es wird immer 1 angezeigt)

Beta-User

#5
Grummel, da war ich wohl etwas verpeilt, was das Ziel der ReadingsVal-Abfrage anging...

So sollte der Teil besser sein:
sub onSetMessage {
    my $hash = shift;
    my $msg  = shift // return;
    my $name = $hash->{NAME};
    if (defined $msg->{payload}) {
      eval {
        my ($reading,$value) = rawToMappedReading($hash,$msg->{subType},$msg->{childId},$msg->{payload});
        readingsBeginUpdate($hash);
        readingsBulkUpdate($hash, $reading, $value);
        if ( defined ($hash->{setcommands}->{$value}) && $hash->{setcommands}->{$value}->{var} eq $reading ) { #$msg->{childId}
           if ($hash->{SetExtensionsCommand} && AttrVal($name, "setExtensionsEvent", undef)) {
             readingsBulkUpdate($hash,"state",$hash->{SetExtensionsCommand}) ; 
           } else {
             readingsBulkUpdate($hash,"state","$value");
             SetExtensionsCancel($hash) if !$msg->{ack};
           }
         }
      };
      readingsBulkUpdate($hash,"heartbeat","alive") if $msg->{ack} && ReadingsVal($name,"heartbeat","dead") eq "NACK" and @{$hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}} == 0;
      readingsEndUpdate( $hash, 1 );
      Log3 ($hash, 4, "MYSENSORS_DEVICE $name: ignoring C_SET-message ".GP_Catch($@)) if $@;
    } else {
      Log3 ($hash, 5, "MYSENSORS_DEVICE $name: ignoring C_SET-message without payload");
    }
    return;
}


Dafür finde ich es jetzt ganz und gar nicht mehr einleuchtend, dass eine heartbeat-Message das auf alive setzt, obwohl ggf. noch nicht alles zugestellt ist, was mit ACK-Anforderung versendet wurde (bzw. wiederholt dann auch versendet wird)... Da ist wohl auch noch was ändern ab Zeile 947:
    if ($type == I_HEARTBEAT_RESPONSE) {
        readingsSingleUpdate($hash, "heartbeat", "alive",1) if !ReadingsVal($name,"heartbeat","alive") eq "NACK";


Ad PS:
Das mit dem "Wahr/Falsch"-Attribut ist auch in vielen anderen Fällen so, dass man das nur (auf 1) Setzen oder Löschen kann.
EDIT sagt: Es gibt jedenfalls nach meinem Verständnis sogar komische Ergebnisse, wenn man das via Kommandozeile auf "0" setzt, bitte mal ein list ansehen... (Wie gesagt, mit diesem Verhalten dürfte MYSENSORS_DEVICE kein Einzelfall sein ;) .)
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

KarlHeinz2000

Jetzt funktioniert das ACK in der Art, dass das heartbeat reading nach Ablauf der timeoutAck Zeit auf NACK geht. Sobald die Nachricht zugestellt wurde geht es wieder auf alive. Passt, denke ich.
Allerdings ist das Alive jetzt ohne Funktion. Es gibt kein dead mehr. Das heartbeat reading bleibt immer auf alive. Nur wenn timeoutAlive geändert wird wird dead aktiv.

Beta-User

Hmm, vielleicht sollte man die Abfrage einfacher schreiben:
     readingsSingleUpdate($hash, "heartbeat", "alive",1) if ReadingsVal($name,"heartbeat","alive") ne "NACK";

Danke für's Testen und Mitdenken!
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

KarlHeinz2000

geht immer noch nicht. Das reading wechselt nicht von alive nach dead.

Beta-User

Vermutlich habe ich was noch nicht richtig verstanden. Was genau meinst du mit
Zitat von: KarlHeinz2000 am 01 Oktober 2020, 17:59:06
Allerdings ist das Alive jetzt ohne Funktion. Es gibt kein dead mehr. Das heartbeat reading bleibt immer auf alive. Nur wenn timeoutAlive geändert wird wird dead aktiv.

Grundsätzlich ist "dead" nur erreichbar, wenn timeoutAlive gesetzt ist. Du schreibst aber, dass das timeoutAlive geändert werden müßte. Dann war das gesetzt, allerdings muss der Timer wieder aktiviert werden, daher nochmaliges setzen.
(Dann hat das aber eher was mit der Änderung in onSetMessage() zu tun und nichts mit der  I_HEARTBEAT_RESPONSE-Geschichte? Das Verhalten müsste dann (fast) gleich sein, egal, was dort passiert... Grübel, werde mir das mit den Timern nochmal ansehen müssen, aber eine Klarstellung wäre ggf. hilfreich).
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

KarlHeinz2000

Ich versuche mal das Verhalten zu beschreiben:

1)
requestAck 0
timeoutAlive 3

dead/alive funktioniert. Nach Ablauf timeoutAlive geht reading auf dead
- gesendetes heartbeat vom node: dead -> alive (->dead)
- ändern der timeoutAlive: dead->alive(->dead)

2)
requestAck 1
timeoutAck 2
timeoutAlive 3

RS485 Verbindung händisch unterbrochen.

Senden-> Reading geht auf NACK nach Ablauf der timeoutAck.
Ist die Verbindung wieder hergestellt, kommt nach einigen Sek ein erneuter/automatischer Sendeversuch. Dieser gelingt. Das reading wird von NACK->alive gesetzt (retry von FHEM oder GW?).
Es bleibt dann alive, außer:
- Wenn ein heartbeat vom node kommt, läuft die Zeit erneut ab und dead wird gesetzt.
- Die timeoutAlive Zeit wird geändert
- Übertragung ist gestört ->NACK


Beta-User

OK, dann muss der Timer auch an der Stelle wieder in Gang gesetzt werden. Ergo sollte das hier helfen:

sub onSetMessage {
    my $hash = shift;
    my $msg  = shift // return;
    my $name = $hash->{NAME};
    if (defined $msg->{payload}) {
      readingsBeginUpdate($hash);
      eval {
        my ($reading,$value) = rawToMappedReading($hash,$msg->{subType},$msg->{childId},$msg->{payload});
     
        readingsBulkUpdate($hash, $reading, $value);
        if ( defined ($hash->{setcommands}->{$value}) && $hash->{setcommands}->{$value}->{var} eq $reading ) { #$msg->{childId}
           if ($hash->{SetExtensionsCommand} && AttrVal($name, "setExtensionsEvent", undef)) {
             readingsBulkUpdate($hash,"state",$hash->{SetExtensionsCommand}) ; 
           } else {
             readingsBulkUpdate($hash,"state","$value");
             SetExtensionsCancel($hash) if !$msg->{ack};
           }
         }
      };
      if ($msg->{ack} && ReadingsVal($name,"heartbeat","dead") eq "NACK" and @{$hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}} == 0) {
         readingsBulkUpdate($hash,"heartbeat","alive") ;
         refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive};
      }
      readingsEndUpdate( $hash, 1 );
      Log3 ($hash, 4, "MYSENSORS_DEVICE $name: ignoring C_SET-message ".GP_Catch($@)) if $@;
    } else {
      Log3 ($hash, 5, "MYSENSORS_DEVICE $name: ignoring C_SET-message without payload");
    }
    return;
}
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

KarlHeinz2000

Sieht gut aus!  :)
Soweit ich es testen konnte ist alles ok.

Ein eingehendes heartbeat vom Node generiert allerdings 2 alive events im monitor. Soll das so sein? War evtl auch vorher schon so...

Und jetzt geht es an die Umsetzung der eigentlichen Kommunikation mit ACK. Da habe ich auf FHEM Seite noch keine Idee.
Eine Sub schreiben, in der gesendet wird und gleichzeitig ein Timer läuft und auf das ACK wartet?
Wie kann ich sicherstellen, dass ein empfangenes ACK auch zu meiner gesendeten Nachricht gehört und nicht von einem anderen Node stammt? Fängt da Protokoll das ab?
Es gibt unterschiedliche events:

MYSENSORS_DEVICE MYSENSOR_100 var2 3
MYSENSORS_DEVICE MYSENSOR_100 var2: 3

Einmal mit und ohne ":"
"Ohne" ist das Senden mit angefordertem ACK
"Mit" ist das eingehende ACK
Bei timeout ACK kommt das NACK zwischendrin

2020-10-02 11:57:35 MYSENSORS_DEVICE MYSENSOR_100 var2 3
2020-10-02 11:57:37 MYSENSORS_DEVICE MYSENSOR_100 heartbeat: NACK
2020-10-02 11:57:49 MYSENSORS_DEVICE MYSENSOR_100 var2: 3


Wird ohne requestACK gesendet kommt nur:


MYSENSORS_DEVICE MYSENSOR_100 var2: 3


Macht ein Fifo Sinn, wenn eine Nachricht länger braucht und schon eine die nächste gesendet werden soll. Wo fängt man sowas ab?



Wenn es da Ideen gibt, bitte her damit  ;)

Beta-User

Wg. des 2. Events: Ist vermutlich eine "Nebenwirkung" der Timer-Erneuerung, kommentiere mal in onSetEvent den Reading-Update aus:
if ($msg->{ack} && ReadingsVal($name,"heartbeat","dead") eq "NACK" and @{$hash->{IODev}->{messagesForRadioId}->{$hash->{radioId}}->{messages}} == 0) {
         #readingsBulkUpdate($hash,"heartbeat","alive") ;
         refreshInternalMySTimer($hash,"Alive") if $hash->{timeoutAlive};
      }


Was die "Warteschlange" angeht: die wird vom Modul verwaltet und der Reihe nach abgearbeitet. MAn. no action required. Ob da irgendeine bessere Timing-Verarbeitung erforderlich wäre: keine Ahnung, ein Teil der Nachrichten wird nämlich auch (zusätzlich) vom GW gepuffert, das hat einen buffer, der afaik sowohl eingehende wie ausgehende Messages enthalten kann (dunkel: ~15). Von daher würde ich das "Freischießen der Leitung" nach Möglichkeit auf dem GW belassen. Wer da richtig viel Traffic hat, muss ggf. eine andere MCU einsetzen und den Nachrichtenpuffer dort vergrößern?

Die Events mit und ohne Doppelpunkt dürften normal sein, ist einmal das Setzen vom User/von FHEM aus und einmal die Antwort, ohne Ack wird so getan, als "wäre das so".
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

KarlHeinz2000

Die 2 alive sind immer noch im event monitor. Aber nur wenn das heartbeat vom Node kommt. Wenn ich nur den timeoutAlive ändere, kommt nur 1x alive.

Wie reagiere ich, wenn beim Senden etwas nicht ankommt. z.B: 3 Nachrichten sollen gesendet werden. Bei der 2. gibt es kein ACK. Wie bekomme ich das mit? Wird dann abgebrochen oder noch x-Mal probiert? Geht die 3. Nachricht noch raus?