Matrix: Unterschied zwischen den Versionen

Aus FHEMWiki
 
(4 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 6: Zeile 6:
== Matrix und FHEM ==
== Matrix und FHEM ==
Um mit '''FHEM''' Nachrichten über das Matrix-Protokoll zu senden und zu empfangen, stehen mehrere Möglichkeiten zur Verfügung. Eine einfache und pragmatische Lösung ist die Nutzung eines '''HTTPMOD-Devices'''. Damit lassen sich Nachrichten über die Matrix-API versenden oder empfangen.
Um mit '''FHEM''' Nachrichten über das Matrix-Protokoll zu senden und zu empfangen, stehen mehrere Möglichkeiten zur Verfügung. Eine einfache und pragmatische Lösung ist die Nutzung eines '''HTTPMOD-Devices'''. Damit lassen sich Nachrichten über die Matrix-API versenden oder empfangen.
=== Matrix per Device ===
Es gibt ein Device für Matrix unter https://git.cooltux.net/FHEM/mod-matrix bzw https://github.com/Man-fred/matrix
==== Einrichten ====
Das Device ist nicht im FHEM Umfang enthalten und wird deswegen mit den folgenden Befehlen hinzugefügt:
<code>update add <nowiki>https://git.cooltux.net/FHEM/mod-matrix/raw/branch/dev/controls_Matrix.txt</nowiki></code>
<code>update</code>
<code>shutdown restart</code>
==== Weiteres ====
https://forum.fhem.de/index.php?topic=129978.0


=== Matrix per HTTPMOD Device ===
=== Matrix per HTTPMOD Device ===
[[Datei:Screenshot des Matrix HTTPMOD Device.png|alternativtext=Screenshot des Matrix HTTPMOD Device|ohne|mini|Screenshot des Matrix HTTPMOD Device]]
Folgendes Device kann Matrix Nachrichten senden und empfangen, es hat wenige Abhängigkeiten und sollte in den meisten FHEM Installationen unkompliziert eingerichtet sein. Es wird die Datenübertragung von FHEM zum Server mit HTTPS verschlüsselt, E2EE wird nicht verwendet.
Folgendes Device kann Matrix Nachrichten senden und empfangen, es hat wenige Abhängigkeiten und sollte in den meisten FHEM Installationen unkompliziert eingerichtet sein. Es wird die Datenübertragung von FHEM zum Server mit HTTPS verschlüsselt, E2EE wird nicht verwendet.


Zeile 16: Zeile 32:


<code>attr MatrixBot MatrixUser FHEM-Matrix-Username</code>
<code>attr MatrixBot MatrixUser FHEM-Matrix-Username</code>
Dann richtet man einen nicht E2E verschlüsselten Raum ein in dem der Matrix-FHEM-Account eingeladen ist. Von dem Raum benötigt man die MatrixRoomID, die man mit Element in den Raumdetails finden kann (Room Settings --> Advanced --> Internal room ID).


Das Passwort des Matrix-FHEM-Users setzt man mit dem Befehl:
Das Passwort des Matrix-FHEM-Users setzt man mit dem Befehl:


<code>set MatrixBot storeKeyValue MatrixPassword yourPassword123</code>
<code>set MatrixBot storeKeyValue MatrixPassword yourPassword123</code>
Dann richtet man einen, '''nicht E2E verschlüsselten Raum''' ein in dem der Matrix-FHEM-Account eingeladen ist. Von dem Raum benötigt man die MatrixRoomID, die man mit Element in den Raumdetails finden kann (Room Settings --> Advanced --> Internal room ID).
<code>attr MatrixBot MatrixRoomID !12345678901234:example.com</code>


==== Verwendung ====
==== Verwendung ====

Aktuelle Version vom 24. Mai 2025, 19:07 Uhr

Allgemeines

Matrix ist ein offenes Kommunikationsprotokoll für dezentrale Echtzeitkommunikation. Es wird von zahlreichen Clients unterstützt, darunter Element, Element-X, SchildiChat Next, FluffyChat und viele weitere.

Viele Open-Source-Projekte betreiben eigene Chaträume auf föderierten Matrix-Servern. Ein Beispiel dafür ist der Gadgetbridge-Chat, einer der größeren und aktiveren Räume im Matrix-Ökosystem.

Matrix und FHEM

Um mit FHEM Nachrichten über das Matrix-Protokoll zu senden und zu empfangen, stehen mehrere Möglichkeiten zur Verfügung. Eine einfache und pragmatische Lösung ist die Nutzung eines HTTPMOD-Devices. Damit lassen sich Nachrichten über die Matrix-API versenden oder empfangen.

Matrix per Device

Es gibt ein Device für Matrix unter https://git.cooltux.net/FHEM/mod-matrix bzw https://github.com/Man-fred/matrix

Einrichten

Das Device ist nicht im FHEM Umfang enthalten und wird deswegen mit den folgenden Befehlen hinzugefügt:

update add https://git.cooltux.net/FHEM/mod-matrix/raw/branch/dev/controls_Matrix.txt

update

shutdown restart

Weiteres

https://forum.fhem.de/index.php?topic=129978.0

Matrix per HTTPMOD Device

Screenshot des Matrix HTTPMOD Device
Screenshot des Matrix HTTPMOD Device

Folgendes Device kann Matrix Nachrichten senden und empfangen, es hat wenige Abhängigkeiten und sollte in den meisten FHEM Installationen unkompliziert eingerichtet sein. Es wird die Datenübertragung von FHEM zum Server mit HTTPS verschlüsselt, E2EE wird nicht verwendet.

Einrichtung

Es sind die Attribute MatrixServer und MatrixUser zu setzen:

attr MatrixBot MatrixServer Dein-Servername

attr MatrixBot MatrixUser FHEM-Matrix-Username

Das Passwort des Matrix-FHEM-Users setzt man mit dem Befehl:

set MatrixBot storeKeyValue MatrixPassword yourPassword123

Dann richtet man einen, nicht E2E verschlüsselten Raum ein in dem der Matrix-FHEM-Account eingeladen ist. Von dem Raum benötigt man die MatrixRoomID, die man mit Element in den Raumdetails finden kann (Room Settings --> Advanced --> Internal room ID).

attr MatrixBot MatrixRoomID !12345678901234:example.com

Verwendung

Nachrichten senden geht mit:

set MatrixBot sendText Bla Bla Bla

Longpoll von Nachrichten starten und stoppen mit:

set MatrixBot longpollCmd startTimer

set MatrixBot longpollCmd stopTimer

Weiteres

Forumlink

https://forum.fhem.de/index.php?topic=120834.msg1339487#msg1339487

Demo-Device

Das folgende DOIF-Device lauscht auf eine eingehende Nachricht die als Inhalt "ping" oder "Ping" ist und antwortet mit "Pong!". Es dient als Beispiel und kann natürlich um weitere Befehle ergänzt/ersetzt werden um FHEM damit zu steuern:

defmod MatrixPing.doif DOIF (["MatrixBot:msg: .*: @.*: [Pp]ing$"])\
  (set MatrixBot sendText Pong!)
attr MatrixPing.doif alias Matrix Ping/Pong
attr MatrixPing.doif do always
attr MatrixPing.doif group Meldungen
attr MatrixPing.doif icon homeConnect

Device-Definition

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