Nachrichten von FHEM zu Matrix Synapse senden

Begonnen von DecaTec, 03 Mai 2021, 12:42:21

Vorheriges Thema - Nächstes Thema

Torxgewinde

Hallo,
Das bekannte Problem, wenn der Longpoll noch aktiv ist und man dann eine Nachricht senden möchte, ist nun gelöst - man braucht also nicht zwei Devices einrichten.

Es wird das Attribut set01IExpr ein wenig ungewöhnlich genutzt um vor dem Senden ein wenig Perlcode auszuführen. So wird dann der longpoll-Aufruf vorzeitig beendet und das set01 kann seine Arbeit verrichten ohne extra warten zu müssen:
attr MatrixBot set01IExpr #cancel an active longpoll\
if ($hash->{BUSY}\
    && $hash->{HttpUtils}\
    && $hash->{HttpUtils}->{url} =~ m|^https://[^/]+/_matrix/client/v3/sync\?timeout=\d+&filter=| ) {\
    Log(3, "$name: longpoll active, cutting it off now");;\
    HttpUtils_Close($hash->{HttpUtils});;\
}\
\
#just return $val unteraltered\
$val

Warum das Probleme machte kann ich nur eingrenzen auf den Abschnitt aus der 98_HTTPMOD.pm:
    if ($hash->{BUSY}) {            # still waiting for reply to last request
        if ($now > $last + max(15, AttrVal($name, "timeout", 2) *2)) {
            Log3 $name, 5, "$name: HandleSendQueue - still waiting for reply, timeout is over twice - this should never happen. Stop waiting";
            $hash->{BUSY} = 0;      # waited long enough, clear busy flag and continue
        }
Obwohl das Timeout auf 65 Sekunden steht und auch .LASTSEND auf sinnvollen Werten steht, springt HTTPMOD in das Fehlerhandling, das einfach nur busy auf 0 setzt, ohne den ggf. noch aktiven longpoll darüber zu informieren. Als Reaktion darauf kann es passieren, dass die ReadCallback-Funktion dann zweimal aufgerufen wird, die Ergebnisse aber bei dem falschem Reading landen. Wie auch immer, so wie oben umschiffe ich das Problem.

Hier das ganze Device zum einfachen Einrichten:
defmod MatrixBot HTTPMOD none 0
attr MatrixBot userattr MatrixRoomID MatrixServer MatrixUser
attr MatrixBot MatrixRoomID !12345678901234:nope.chat
attr MatrixBot MatrixServer nope.chat
attr MatrixBot MatrixUser DeinUsername
attr MatrixBot bodyDecode utf-8
attr MatrixBot bodyEncode utf-8
attr MatrixBot comment "\
Create a room for FHEM.\
\
The room must not use encryption, a room that has encryption\
enabled, cannot be converted to a non-encrypted room anymore \
\
The room-id can be found in Element-Web at:\
Room Settings --> Advanced --> Internal room ID\
\
To store the password in FHEM in obfuscated way:\
set MatrixBot storeKeyValue MatrixPassword yourPassword123\
\
###\
To send a text:\
set MatrixBot sendText Bla Bla Bla\
\
\
###\
To longpoll for messages once:\
# 1. send special filter to Matrix:\
set MatrixBot sendFilter\
\
# 2. start one longPoll (waits up to 60 seconds\
#                        or until data is available)\
get MatrixBot longpoll\
\
#alternatively, to keep on longPolling (this also sets filter):\
set MatrixBot longpollCmd startTimer\
#to stop the timers:\
set MatrixBot longpollCmd stopTimer\
\
#############################################################\
https://spec.matrix.org/v1.14/client-server-api/#syncing\
"
attr MatrixBot get02AlwaysNum 0
attr MatrixBot get02HeaderAuthorization Authorization: Bearer $sid
attr MatrixBot get02Name longpoll
attr MatrixBot get02Regex \"next_batch\":\s*\"(?<next_batch>[^\"]+)\"(?:.*?\"timeline\":\s*{\s*\"events\":\s*(?<messages>\[.*?\])\s*)?
attr MatrixBot get02TextArg 0
attr MatrixBot get02URL https://[$name:MatrixServer]/_matrix/client/v3/sync?timeout=60000&filter=[$name:filter_id]%%next_batch_param%%
attr MatrixBot icon message_info
attr MatrixBot parseFunction1 handleAuthErrors
attr MatrixBot reAuthAlways 0
attr MatrixBot reAuthRegex M_UNKNOWN_TOKEN
attr MatrixBot replacement01Mode expression
attr MatrixBot replacement01Regex \[([^:\s\[\"\']+):([^\]\s]+)\]
attr MatrixBot replacement01Value my $device = $name if ($1 eq "\$name") // $1;;\
ReadingsVal($device, $2, undef) or AttrVal($device, $2, "???");;
attr MatrixBot replacement02Mode expression
attr MatrixBot replacement02Regex %%uuid%%
attr MatrixBot replacement02Value join("-", unpack("A8 A4 A4 A4 A12", unpack("H*", join("", map { chr(int rand 256) } 0..15))))
attr MatrixBot replacement03Mode key
attr MatrixBot replacement03Regex %%MatrixPassword%%
attr MatrixBot replacement03Value MatrixPassword
attr MatrixBot replacement04Mode expression
attr MatrixBot replacement04Regex %%next_batch_param%%
attr MatrixBot replacement04Value #is there a reading 'next_batch'?\
my $val = ReadingsVal($name, 'next_batch', '???');;\
\
#return the GET parameter 'sync=value' for /sync Endpoint\
return "&since=$val" if ($val ne '???');;\
\
#return neither since-key nor value for it:\
return "";;
attr MatrixBot set01Data {\
  "msgtype": "m.text",\
  "body": "$val"\
}
attr MatrixBot set01HeaderAuthorization Authorization: Bearer $sid
attr MatrixBot set01HeaderContent-Type application/json
attr MatrixBot set01IExpr #cancel an active longpoll\
if ($hash->{BUSY}\
    && $hash->{HttpUtils}\
    && $hash->{HttpUtils}->{url} =~ m|^https://[^/]+/_matrix/client/v3/sync\?timeout=\d+&filter=| ) {\
    Log(3, "$name: longpoll active, cutting it off now");;\
    HttpUtils_Close($hash->{HttpUtils});;\
}\
\
#just return $val unteraltered\
$val
attr MatrixBot set01Method POST
attr MatrixBot set01Name sendText
attr MatrixBot set01ParseResponse 1
attr MatrixBot set01Regex {\"event_id\":\"(.*)\"}
attr MatrixBot set01TextArg 1
attr MatrixBot set01URL https://[$name:MatrixServer]/_matrix/client/v3/rooms/[$name:MatrixRoomID]/send/m.room.message?txnId=%%uuid%%
attr MatrixBot set02Data {\
  "room": {\
    "rooms": ["[$name:MatrixRoomID]"],\
    "timeline": {\
      "limit": 10,\
      "types": ["m.room.message"]\
    },\
    "include_leave": false,\
    "include_join": false,\
    "include_account_data": false,\
    "include_state": false,\
    "state": {\
      "types": []\
    },\
    "ephemeral": {\
      "types": []\
    },\
    "account_data": {\
      "types": []\
    }\
  },\
  "event_fields": [\
    "content.body",\
    "sender",\
    "origin_server_ts"\
  ],\
  "event_format": "client",\
  "presence": {\
    "types": [],\
    "not_types": ["*"]\
  },\
  "account_data": {\
    "types": [],\
    "not_types": ["*"]\
  }\
}
attr MatrixBot set02HeaderAuthorization Authorization: Bearer $sid
attr MatrixBot set02HeaderContent-Type application/json
attr MatrixBot set02Method POST
attr MatrixBot set02Name sendFilter
attr MatrixBot set02NoArg 1
attr MatrixBot set02ParseResponse 1
attr MatrixBot set02Regex \"filter_id\":\s*\"(?<filter_id>\d+)\"
attr MatrixBot set02URL https://[$name:MatrixServer]/_matrix/client/v3/user/@[$name:MatrixUser]:[$name:MatrixServer]/filter
attr MatrixBot set03Local 1
attr MatrixBot set03Name longpollCmd
attr MatrixBot set03TextArg 1
attr MatrixBot showError 1
attr MatrixBot sid01Data {\
  "type": "m.login.password",\
  "identifier": {\
    "type": "m.id.user",\
    "user": "[$name:MatrixUser]"\
  },\
  "password": "%%MatrixPassword%%"\
}
attr MatrixBot sid01HeaderContent-Type application/json
attr MatrixBot sid01IdRegex "access_token"\s*:\s*"([^"]+)"
attr MatrixBot sid01URL https://[$name:MatrixServer]/_matrix/client/v3/login
attr MatrixBot timeout 65
attr MatrixBot userReadings longpollTimer:(next_batch|longpollCmd|LAST_ERROR|sendText):.* {\
  my $longpollCmd = ReadingsVal($name, 'longpollCmd', '???');;\
  my $delay = 1;;\
  my $timeout = AttrVal($name, 'timeout', 61) + $delay + 5;;\
  \
  # stop our timers:\
  if ($longpollCmd eq "stopTimer") {\
    fhem("cancel ${name}_longpollTimer quiet");;\
    fhem("cancel ${name}_longpollTimer2 quiet");;\
    return "stopped";;\
  }\
  \
  if ($longpollCmd ne "startTimer") {\
    return "no timer set, longpollCmd is not set to 'startTimer'";;\
  }\
  \
  #if startTimer cmd was given now, set filter as well:\
  if (ReadingsAge($name, 'longpollCmd', 0) <= 1) {\
    $delay = 5;; #delay to allow for sendFilter to be answered\
    #Log(1, "🪲 $name: >>". InternalVal($name, 'httpbody', '???') ."<<");;\
    fhem("sleep 0.1 quiet;; set $name sendFilter");;\
  }\
  \
  #we handle an error reported by http-utils:\
  if (ReadingsAge($name, 'LAST_ERROR', 0) <= 1) {\
    my $last_error = ReadingsVal($name, 'LAST_ERROR', '???');;\
    $delay = 10;; #delay to allow for error reasons to improve\
    #Log(1, "🪲 $name: Dealing with error: >>$last_error<<");;\
  }\
  \
  # for testing this: { $defs{MatrixBot}{sid} = 'bla' }\
  if (!defined &HTTPMOD::handleAuthErrors) {\
    *HTTPMOD::handleAuthErrors = sub {\
      my ($hash, $header, $body, $request) = @_;;\
      my $name = $hash->{NAME};;\
      my $status;;\
      \
      if ($header =~ m{^HTTP/\d\.\d\s+(\d+)}m) {\
        $status = $1;;\
      }\
      \
      Log3($name, 4, "$name: HTTP status code is $status");;\
      \
      if ( $status == 401 || $status == 403 || $status == 500 ) {\
        Log3($name, 3, "$name: auth-error or servererror ($status), calling doAuth()");;\
        HTTPMOD::DoAuth($hash);;\
      }\
    };;\
  }\
  \
  #set timers, one regular and one fallback:\
  fhem("sleep $delay ${name}_longpollTimer quiet;; get $name longpoll");;\
  fhem("sleep $timeout ${name}_longpollTimer2 quiet;; set $name longpollCmd startTimer");;\
  \
  return strftime("next longpoll at %H:%M:%S", localtime( time()+$delay ));;\
},\
messages_list:messages:.* {\
  my $this = ReadingsVal($name, $reading, '');;\
  my @timestampArray = split("\n", $this);;\
  my $messages_ref = decode_json(ReadingsVal($name, 'messages', ''));;\
  my $length = 20;;\
  \
  my %seen_messages = map { $_ => 1 } @timestampArray;;\
  \
  foreach my $msg (@$messages_ref) {\
    my $val = $msg->{content}{body};;\
    $val = Encode::decode('utf-8', $val) unless Encode::is_utf8($val);;\
    $val =~ s/\n/ /g;; #replace newlines with spaces\
    \
    my $sender = $msg->{sender};;\
    my $ts = strftime("%Y-%m-%d %H:%M:%S", localtime($msg->{origin_server_ts} / 1000));;\
    my $new_entry = "$ts: $sender: $val";;\
    \
    next if $seen_messages{$new_entry};;\
    \
    my $inserted = 0;;\
    for (my $i = 0;; $i < @timestampArray;; $i++) {\
      my ($existing_ts) = $timestampArray[$i] =~ /^([^:]+):/;;\
      if ($ts lt $existing_ts) {\
        splice(@timestampArray, $i, 0, $new_entry);;\
        $inserted = 1;;\
        last;;\
      }\
    }\
    push(@timestampArray, encode('utf-8', $new_entry)) unless $inserted;;\
    shift(@timestampArray) while @timestampArray > $length;;\
  }\
  \
  return join("\n", @timestampArray);;\
},\
process_messages:messages:.* {\
  my $val = ReadingsVal($name, 'messages_list', '');;\
  my $this = ReadingsVal($name, $reading, '');;\
  my $latest_ts = $this;;\
  \
  foreach my $line (split(/\n/, $val)) {\
    if ($line =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}):\s*(.*)$/) {\
      my ($ts, $msg) = ($1, $2);;\
      \
      if ($ts gt $this) {\
        fhem("trigger $name msg: $line");;\
        $latest_ts = $ts if ($ts gt $latest_ts);;\
      }\
    }\
  }\
  return $latest_ts;;\
}
attr MatrixBot verbose 3
attr MatrixBot widgetOverride longpollCmd:uzsuSelectRadio,startTimer,stopTimer

Torxgewinde


riker1

#17
Hi
irgendwie bin ich zu blöd.
Matrix kenne ich auch wenig, mir scheint als ob ich laufend neue Access Token erhalte.

Wo muss ich denn im Device den Token angeben, ?

muss hier attr MatrixTRBot sid01IdRegex "access_token"\s*:\s*"([^"]+)"der token rein?

laufend die meldung falscher Token
der user muss doch mit @user:server eingegeben werden? richtig?

muss der Server als matrix.tchncs.de     oder nur   tchncs.de  eingegeben werden?

Danke für die Klarstellung
FHEM    5.26.1 Ubuntu 18, FHEM    5.26.1 RPI 3 , Actoren: IT ,Tasmota, ESPEasy,
MAX CUBE, MAX HT, MAX WT, Selbstbau nanoCULs, FS 20,Tasmota, Homematic, FTK, SW. DIM, Smoke,KODI,Squeezebox

Torxgewinde

Hi,
Den Token musst du garnicht selbst erstellen, das macht das Device für dich.

Nutzername und Server werden getrennt angegeben:
- attr MatrixUser FHEM_Nutzername_WieBeimServerOhneDoppelpunktUndOhneAt
- attr MatrixServer nope.chat (oder wo auch immer dein Matrix-Synapse-Server läuft)

Bezüglich des servers (ob mit Matrix. oder ohne) musste bitte einfach probieren. Ich meine es muss mit, da es auf eine andere IP verweist:

;; ANSWER SECTION:
matrix.tchncs.de. 110 IN CNAME h2.tchncs.de.
h2.tchncs.de. 423 IN A 135.181.232.169

;; ANSWER SECTION:
tchncs.de. 600 IN A 89.58.62.32