MsgDialog: Unterschied zwischen den Versionen

Aus FHEMWiki
K (→‎TelegramBot: Typo..)
 
(62 dazwischenliegende Versionen von 6 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
{{Infobox Modul
{{Infobox Modul
|ModPurpose=Dialoge für Sofortnachrichten über TelegramBot, Jabber und WhatsApp
|ModPurpose=Dialoge für Sofortnachrichten über TelegramBot, Jabber und WhatsApp
|ModType=d
|ModType=h
|ModForumArea=Frontends
|ModForumArea=Frontends
|ModTechName=76_msgDialog.pm
|ModTechName=76_msgDialog.pm
|ModOwner={{Link2FU|4106|igami}}
|ModOwner=Beta-User ({{Link2FU|9229|Forum}}/[[Benutzer Diskussion:Beta-User|Wiki]])
}}
}}


== Zielsetzung ==
== Zielsetzung ==
Eine zentrale Anforderung einer modernen Haus-Automation ist die Steuerung von unterwegs. Mittlerweile nutzt fast jeder einen oder mehrere Messenger Dienste. Da liegt es doch nahe, diese Messenger Dienste für die Kommunikation mit FHEM zu nutzen.  
Ein zentrale Anforderung an die Haus-Automation ist die Kommunikation mit den Anwendern. Fast jeder Anwender möchte über bestimmte Ereignisse informiert werden und Einstellungen vornehmen. FHEM bietet schon seit längerem die Unterstützung für verschiedene Messaging Dienste an.
Mit msgDialog ist es jetzt möglich, Dialoge für Sofortnachrichten über TelegramBot, Jabber und WhatsApp (yowsup) zu definieren. Dabei können die einzelnen Dialoge für unterschiedliche Nutzer berechtigt werden.
Mit dem msgDialog Modul können vordefinierte Dialoge erstellt werden. Die Kommunikation erfolgt dabei über den [[msg]] Befehl welcher zurzeit die bidirektionale Kommunikation über [[TelegramBot]], [[Jabber]] und [[yowsup]] (WhatsApp) unterstützt. Eingehende Nachrichten werden einem ROMMATE oder GUEST zugeordnet. Darüber werden auch die Berechtigungen für die einzelnen Dialoge festgelegt.
Die grundlegende Bedienung wird in diesem Video am Beispiel von TelegramBot gezeigt:
Die grundlegende Bedienung wird in diesem '''[https://youtu.be/yiCOTeR1YVQ Video]''' am Beispiel von TelegramBot gezeigt.
https://youtu.be/yiCOTeR1YVQ


== Einbindung in FHEM ==
== Konfiguration ==


=== Voraussetzungen ===
=== Voraussetzungen ===
Zeile 21: Zeile 20:
</pre>
</pre>


=== Hinweise ===
=== Hinweis zu den Code Beispielen ===
Es handelt sich bei den Beispielen um eine Raw definition, siehe auch [[Import_von_Code_Snippets]]


'''Telegram:''' Es kann notwendig sein, dass im TelegramBot das Attribut "utf8specials" auf "1" gesetzt wird, damit Nachrichten mit Umlauten gesendert werden. Bei dem msg-Befehl kann der TelegramBot_MTYPE angegeben werden. Die Vorgabe ist "message". Durch den Wert "queryInline" lässt sich ein inline Keyboard erzeugen (siehe weiter unten).
=== msgConfig ===


'''Jabber:''' Bei dem msg Befehl kann der Jabber_MTYPE angegeben werden. Die Vorgabe ist leer. Durch den Wert otr lässt sich eine OTR Nachricht versenden.
Als erstes benötigt man ein definiertes msgConfig device:


'''WhatsApp (yowsup):''' Bisher noch keine Erfahungen.
<pre>
defmod myMsgConfig msgConfig
attr myMsgConfig userattr msgDialog_evalSpecials:textField-long msgDialog_msgCommand:textField
attr myMsgConfig msgContactPush <Name des TelegramBot-device>
attr myMsgConfig msgDialog_evalSpecials me=<Aktivierungswort bzw. -Nachricht>\
TelegramBot=<Name des TelegramBot-device>
</pre>


=== msgDialog und ROOMATE ===
Falls bereits vorhanden, müssen natürlich nur die fehlenden Attribute gesetzt werden.


Für jeden Dialog kann festgelegt werden, welche Person(en) dazu berechtigt ist. Dazu sind Geräte vom Typ ROOMMATE oder GUEST mit definiertem msgContactPush Attribut erforderlich. Es ist darauf zu achten, dass das Reading "fhemMsgRcvPush" ein Event erzeugt.
;msgDialog_evalSpecials
:key1=value1 key2=value2 ... Leerzeichen getrennte Liste von Name=Wert Paaren. Wert kann Leerzeichen enthalten, falls es in "" oder {} eingeschlossen :ist. Wert wird als perl-Ausdruck ausgewertet, falls es in {} eingeschlossen ist. In der DEF werden %Name% Zeichenketten durch den zugehörigen Wert :ersetzt. Wenn der selbe Name im msgConfig und msgDialog definiert wurde, wird der Wert aus msgDialog verwendet.


=== Definition des msgConfig-Device  ===
;msgDialog_msgCommand <command>
:Befehl zum Versenden einer Nachricht. Die Vorgabe ist "msg push \@$recipients $message". Wenn nicht gesetzt, wird die Vorgabe verwendet.


Als nächstes benötigt man ein definiertes msgConfig device:
=== ROOMMATE / GUEST ===
Für jeden Dialog kann festgelegt werden welche Person dazu berechtigt ist. Dazu sind Geräte vom Typ ROOMMATE oder GUEST mit definiertem msgContactPush Attribut erforderlich. Es ist darauf zu achten, dass das Reading fhemMsgRcvPush ein Event erzeugt. Siehe auch Attribut "allowed" in msgDialog.


Der Inhalt vom Attribut msgContactPush muss folgendem Muster entsprechen:
<pre>
<pre>
defmod myMsgConfig msgConfig
<NAME des TelegramBot Device>:@<zum ROOMMATE/GUEST gehörende Telegram ID>
attr myMsgConfig msgDialog_evalSpecials me=<Aktivierungswort bzw. -Nachricht>
TelegramBot=<Name des TelegramBot-device>
attr myMsgConfig msgContactPush TelegramBot
attr myMsgConfig room msg
</pre>
</pre>


Falls bereits vorhanden müssen, natürlich nur die fehlenden Attribute gesetzt werden.
=== Define ===
 
=== Definition des metaDialogs ===  


Definition des metaDialogs:
Zur Auflistung aller berechtigten Dialoge dient - was sonst - ein Dialog!
Zur Auflistung aller berechtigten Dialoge dient - was sonst - ein Dialog!


Zeile 55: Zeile 60:
   "%me%": {\
   "%me%": {\
     "match": "\/?(start|%me%)",\
     "match": "\/?(start|%me%)",\
     "commands": [\
     "commands": "deletereading TYPE=msgDialog $recipient_history",\
  "deletereading TYPE=msgDialog $recipient_history",\
  "deletereading %TelegramBot% $recipient_sentMsgId"\
    ],\
     "message": [\
     "message": [\
       "{return('(' . join(') (', sort(split('\n', fhem('get TYPE=msgDialog:FILTER=NAME!=$SELF:FILTER=allowed=.*($recipient|everyone).* trigger')))) . ') ')}",\
       "{return('(' . join(') (', sort{lc($a) cmp lc($b)} (split('\n', fhem('get TYPE=msgDialog:FILTER=NAME!=$SELF:FILTER=allowed=.*($recipient|everyone).* trigger', 1)))) . ') (abbrechen) ')}",\
      "(abbrechen) ",\
       "Ich kann folgendes für dich tun:"\
       "Ich kann folgendes für dich tun:"\
     ]\
     ]\
  },\
  "zurück": {\
    "commands": "set $recipient_history=.+ say @$recipient {(ReadingsVal($DEV, '$recipient_history', '') =~ m/((.+)\\|.+$)/;;;; return($2 || $1);;;;)}"\
   },\
   },\
   "abbrechen": {\
   "abbrechen": {\
     "match": "\/?abbrechen",\
     "match": "\/?abbrechen",\
     "commands": [\
     "commands": "deletereading TYPE=msgDialog $recipient_history",\
  "deletereading TYPE=msgDialog $recipient_history",\
  "deletereading %TelegramBot% $recipient_sentMsgId"\
    ],\
     "message": [\
     "message": [\
       "(%me%) ",\
       "TelegramBot_MTYPE=queryInline (%me%) ",\
       "Dialog abgebrochen."\
       "Dialog abgebrochen."\
     ]   \
     ]\
   },\
   },\
   "beenden": {\
   "beenden": {\
     "match": "\/?beenden",\
     "match": "\/?beenden",\
     "commands": [\
     "commands": "deletereading TYPE=msgDialog $recipient_history",\
  "deletereading TYPE=msgDialog $recipient_history",\
  "deletereading %TelegramBot% $recipient_sentMsgId"\
    ],\
     "message": [\
     "message": [\
       "(%me%) ",\
       "TelegramBot_MTYPE=queryInline (%me%) ",\
       "Dialog beendet."\
       "Dialog beendet."\
     ]\
     ]\
Zeile 89: Zeile 87:
}
}


attr meta_Dialog DbLogExclude .*
attr meta_Dialog allowed everyone
attr meta_Dialog allowed everyone
attr meta_Dialog room msg
</pre>
</pre>
=== Definition beliebiger Dialoge ===


Aufgrund der Komplexität eines Dialogs mit JSON ist es am praktikabelsten, zunächst einen leeren Dialog zu definieren:
Aufgrund der Komplexität eines Dialogs mit JSON ist es am praktikabelsten, zunächst einen leeren Dialog zu definieren:
Zeile 101: Zeile 95:
</pre>
</pre>


Anschließend kann die DEF in der Detail-Ansicht des Dialog-Device bearbeitet werden. Jeder Dialog basiert auf der folgenden Struktur von einfach bis komplex.
Anschließend kann die '''DEF''' in der Detail-Ansicht des Dialog-Device bearbeitet werden. Jeder Dialog basiert auf der folgenden Struktur von einfach bis komplex.


<pre>
<pre>
Zeile 125: Zeile 119:
Um den JSON-Teil der Dialoge zu testen, empfiehlt sich ein Besuch auf https://jsonlint.com/
Um den JSON-Teil der Dialoge zu testen, empfiehlt sich ein Besuch auf https://jsonlint.com/
Hier kann der selbst geschriebene JSON-Code validiert werden.
Hier kann der selbst geschriebene JSON-Code validiert werden.
;TRIGGER
:Kann ein beliebiger Text sein. Es wird geprüft ob die eingehende Nachricht damit übereinstimmt. Falls ja, wird der Dialog an dieser Stelle fortgesetzt.
;match
:Wenn nicht nur genau eine Nachricht zugelassen werden soll, kann noch eine regex angegeben werden. Die regex muss auf die gesamte eingehnde Nachricht :zutreffen.
;setOnly
:Kann optional auf true oder false gestellt werden. In beiden fällen wird der TRIGGER dann nicht bei "get <name> trigger" zurück gegeben.
:Wenn setOnly auf "true" gestellt wird, kann der Dialog an dieser Stelle nicht durch eingehende Nachrichten ausgelöst werden, sondern nur über "get <name> say TRIGGER". Dies kann dazu genutzt werden um einen Dialog aus :FHEM heraus zu initiieren.
;commands
:Kann einen einzelnen oder mehrere Befehle enthalten:
<pre>
"commands": "single command"
</pre>
<pre>
"commands": [
"command 1",
"command 2",
"{perl command}"
]
</pre>
;message
:Kann einen einzelnen oder mehrere Texte enthalten die mit einen Zeilenumbruch verbunden werden:
<pre>
"message": [
  "text 1",
  "text 2",
  "{return from perl command}"
]
</pre>
;Dialogfelder
:Um Dialogfelder im Keyboard anzuzeigen müssen diese innerhalb des message-Blocks in runden Klammern stehen(). Wichtig ist das nach der schließenden Klammer ein Leerzeichen folgt. Damit das Keyboard angezeigt wird, muss auch ein normaler Text im bessage-Block enthalten sein.
<pre>
"message": [
  "(Dialogfeld 1) ",
  "(Dialogfeld 2) ",
  "Bitte wähle ein Dialogfeld aus"
]
</pre>
Bei mehrstufigen Dialogen wird diese Struktur ineinander verschachtelt angegeben.
Es werden Variablen und Platzhalter, welche unter dem Attribut evalSpecials definiert werden, ausgewertet.
=== Variablen ===
*$SELF: Eigenname des msgDialog
*$message: eingegangene Nachricht
*$recipient: Name des Dialogpartners
=== Attribute ===
*allowed: Liste mit allen RESIDENTS und ROOMMATE die für diesen Dialog berechtigt sind.
*disable: [0|1] Dialog ist aktiviert|deaktiviert.
*disabledForIntervals: HH:MM-HH:MM HH:MM-HH-MM ...
*evalSpecials: key1=value1 key2=value2 ...Leerzeichen getrennte Liste von Name=Wert Paaren. Wert kann Leerzeichen enthalten, falls es in "" oder {} eingeschlossen ist. Wert wird als perl-Ausdruck ausgewertet, falls es in {} eingeschlossen ist. In der DEF werden %Name% Zeichenketten durch den zugehörigen Wert ersetzt. Dieses Attribut ist als "msgDialog_evalSpecials" im msgConfig Gerät vorhanden. Wenn der selbe Name im msgConfig und msgDialog definiert wurde, wird der Wert aus msgDialog verwendet.
*msgCommand <command>: Befehl der zum Versenden einer Nachricht verwendet wird. Die Vorgabe ist "msg push \@$recipients $message" Dieses Attribut ist als "msgDialog_msgCommand" im msgConfig Gerät vorhanden.
=== set ===
*reset: Setzt den Dialog für alle Benutzer zurück.
*say [@<recipient1>[,<recipient2>,...]] <TRIGGER>[|<NEXT TRIGGER>|...]: Der Dialog wird für alle angegeben Empänger an der angegeben Stelle fortgeführt. Sind keine Empfänger angegeben wird der Dialog für alle unter dem Attribut allowed angegebenen Empfänger fortgeführt.
*updateAllowed: Aktualisiert die Auswahl für das Attribut allowed.
=== get ===
*trigger: Listet alle TRIGGER der ersten Ebene auf bei denen nicht setOnly angegeben ist.
=== READINGS ===
*$recipient_history: Durch | getrennte Liste von TRIGGER um den aktuellen Zustand des Dialogs zu sichern. Für jeden Dialogpartner wird ein Reading angelegt. Wenn der Dialog beendet ist wird das Reading zurückgesetzt.
== Hilfe bei der Fehlersuche ==
Hier entsteht eine Sammlung von Tipps bei der Fehlersuche. Die meisten dieser "Stolpersteine"
entstehen häufig bei der Erstellung eines ersten eigenen Dialoges.
=== Das Attribut allowed fehlt oder ist nicht korrekt gesetzt ===
Das allowed-Attribut dient dazu, einzelne Dialoge für alle  oder nur bestimmte Benutzer zu berechtigen.
Fehlt es, wird der Dialog dem aufrufenden Benutzer nicht angezeigt.
<pre>
attr Testdialog allowed everyone
</pre>
=== Das Attribut msgDialog_evalSpecials fehlt oder ist nicht korrekt gesetzt ===
Dieses Attribut gehört dem msgConfig device und muss wie in 2.3 zu lesen
korrekt befüllt werden.
<pre>
attr myMsgConfig msgDialog_evalSpecials me=<Aktivierungswort bzw. -Nachricht>
TelegramBot=<Name des TelegramBot-device>
</pre>
=== Das Attribut utf8Special fehlt beim TelegramBot device ===
Dieses Attribut ist für die korrekte UTF-8 Kodierung zuständig und im Standard deaktiviert.
Der Satz "Was kann ich für dich tun?" aus dem Meta-Dialog enthält bereits einen Umlaut!
Folgende FHEM-Log-Fehlermeldung kann auf das fehlende Attribut hindeuten:
<pre>
<Datum> <Zeit> 3: TelegramBot_Callback <TelegramBot-Device>: resulted in NonBlockingGet timed out on read from <hidden> after 30s from SendIt
</pre>
=== JSON-Syntax Fehler ===
Das Modul prüft vor dem Abspeichern auf eine korrekte JSON-Syntax.
Meistens wird auch eine sprechende Meldung angezeigt:
<pre>
Usage: define <name> msgDialog {JSON}
, or ] expected while parsing array, at character offset 73 (before "}\n}") at ./FHEM/76_msgDialog.pm line 93.
</pre>
Eine gute Anlaufstelle zur Überprüfung von JSON-Code ist hier: https://jsonlint.com/
=== Es werden keine Dialoge angezeigt ===
Ohne die Angabe einer normalen Nachricht wird bei Keyboards kein Dialog angezeigt:
<pre>
"message": [
"(Lampen einschalten) ",
"(Lampen ausschalten) ",
"(zurück:%me%) ",
],
</pre>
Erst mit der Nachricht "Möchtest Du Lampen ein oder ausschalten?" erscheint der Dialog:
<pre>
"message": [
"(Lampen einschalten) ",
"(Lampen ausschalten) ",
"(zurück:%me%) ",
"Möchtest Du Lampen ein oder ausschalten?"
],
</pre>
=== Innerhalb eines Dialogs fehlen einzelne Menüeinträge ===
Das liegt das meistens an einem fehlenden Leerzeichen:
<pre>
"message": [
"(Lampen einschalten)",
"(Lampen ausschalten) ",
"(zurück:%me%) ",
],
</pre>
Bei "Lampen ausschalten" ist dagegen korrekt ein Leerzeichen zu sehen und somit auch der Menüeintrag im Dialog.
=== ":" bei Inline Keyboards ===
Bei Inline Keyboards wird der letzte : als Trennzeichen zwischen Nachricht und Befehl ausgewertet.
Beispiel:
<pre>
"(Temperatur: [heizung:desiredTemperature]°C:setheiz) "
</pre>
oder 
<pre>
"([test:state]:weiter) " wenn [test:state] = "Status: ok"
</pre>


== Beispiele ==
== Beispiele ==
Zeile 239: Zeile 381:
</pre>
</pre>


== TelegramBot: Inline Keyboard verwenden==
== TelegramBot ==


In Telegram gibt es die Möglichkeit die Art des Keyboards zu ändern. Links das normale Keyboard, rechts das Inline Keyboard.
In [[TelegramBot|Telegram]] gibt es die Möglichkeit die Art des Keyboards zu ändern. Links das normale Keyboard, rechts das Inline Keyboard.


[[Datei:76msgDialog menu.png]]  [[Datei:76msgDialog menu inline.PNG]]
[[Datei:76msgDialog menu.png|Normales Keyboard]]  [[Datei:76msgDialog menu inline.PNG|Inline Keyboard]]


In diesem [https://youtu.be/oRDy2918mVI Video] wird Telegram mit einem Inline Keyboard verwendet.
In diesem '''[https://youtu.be/oRDy2918mVI Video]''' wird Telegram mit einem Inline Keyboard verwendet.


Um eine Nachricht bei Telegram zu verändern, gibt es den Befehl "queryEditInline".
Um eine Nachricht bei Telegram zu verändern, gibt es den Befehl "queryEditInline".
Zeile 252: Zeile 394:
<pre>
<pre>
defmod sentMsgIdByPeerId notify .+:sentMsgId.+ {\
defmod sentMsgIdByPeerId notify .+:sentMsgId.+ {\
   return unless($TYPE eq "TelegramBot");;\
   return if $TYPE ne 'TelegramBot';;\
   \
   \
  my $dev_hash = $defs{$NAME};;\
   my $sentMsgId = ReadingsVal($NAME, 'sentMsgId', '');;\
   my $sentMsgId = ReadingsVal($NAME, "sentMsgId", "");;\
   my $sentMsgPeerId = ReadingsVal($NAME, 'sentMsgPeerId', '');;\
   my $sentMsgPeerId = ReadingsVal($NAME, "sentMsgPeerId", "");;\
   my $contact = (devspec2array("TYPE=(ROOMMATE|GUEST):FILTER=msgContactPush=.*$sentMsgPeerId.*"))[0];;\
   my ($contact) = devspec2array("TYPE=(ROOMMATE|GUEST):FILTER=msgContactPush=.*$sentMsgPeerId.*");;\
   \
   \
   readingsSingleUpdate($dev_hash, "$contact\_sentMsgId", $sentMsgId, 1);;\
   readingsSingleUpdate($defs{$NAME}, "$contact\_sentMsgId", $sentMsgId, 1);;\
}
}
attr sentMsgIdByPeerId devStateIcon {ReadingsVal($name, "state", "inactive") eq "active" ? ".*:ios-on-blue:inactive" : ".*:ios-off:active"}
attr sentMsgIdByPeerId devStateIcon {ReadingsVal($name, 'state', 'inactive') eq 'active' ? '.*:ios-on-blue:inactive' : '.*:ios-off:active'}
attr sentMsgIdByPeerId icon audio_mic
attr sentMsgIdByPeerId icon audio_mic
attr sentMsgIdByPeerId room msg
attr sentMsgIdByPeerId room msg
Zeile 274: Zeile 415:
set <TelegramBot> queryEditInline  
set <TelegramBot> queryEditInline  
</pre>
</pre>
geändert. Dafür wird ein cmdAlias verwendet:
geändert. Dafür wird ein [[cmdalias]] verwendet:


<pre>
<pre>
Zeile 301: Zeile 442:


Um wieder zurück zum normalen Keyboard zu gelangen genügt es, das notify und den cmdAlias zu deaktivieren.
Um wieder zurück zum normalen Keyboard zu gelangen genügt es, das notify und den cmdAlias zu deaktivieren.
Das kann komfortabel über ein notify und einen dummy erledigt werden:
<pre>
defmod inline_normal.NOT notify inline_normal.DUM:.* {\
if ("$EVENT" =~ "on") {\
  fhem "set sentMsgIdByPeerId active;; attr message2queryEditInline disable 0";;\
  } \
elsif ("$EVENT" =~ "off") {\
  fhem "set sentMsgIdByPeerId inactive;; attr message2queryEditInline disable 1";;\
  }\
}
</pre>


Weitere Infos zum Thema Inline Keyboard sind in der Antwort #10 im Forum zu finden (siehe Link unten).
Weitere Infos zum Thema Inline Keyboard sind in der Antwort #10 im Forum zu finden (siehe Link unten).
== JABBER ==
Hier wird die Verwendung mit Jabber beschrieben...
== WhatsApp ==
Hier wird die Verwendung mit yowsup/WhatsApp beschrieben...


== Links ==
== Links ==
* Thread über das Modul im {{Link2Forum|Topic=77297|LinkText=FHEM Forum}}
* Thread über das Modul im {{Link2Forum|Topic=77297|LinkText=FHEM Forum}}


[[Kategorie:Gerätemodul]]
[[Kategorie:Hilfsmodul]]
[[Kategorie:Telegram]]
[[Kategorie:Jabber]]
[[Kategorie:WhatsApp]]

Aktuelle Version vom 26. Januar 2023, 15:20 Uhr

MsgDialog
Zweck / Funktion
Dialoge für Sofortnachrichten über TelegramBot, Jabber und WhatsApp
Allgemein
Typ Hilfsmodul
Details
Dokumentation EN / DE
Support (Forum) Frontends
Modulname 76_msgDialog.pm
Ersteller Beta-User (Forum /Wiki)
Wichtig: sofern vorhanden, gilt im Zweifel immer die (englische) Beschreibung in der commandref!


Zielsetzung

Ein zentrale Anforderung an die Haus-Automation ist die Kommunikation mit den Anwendern. Fast jeder Anwender möchte über bestimmte Ereignisse informiert werden und Einstellungen vornehmen. FHEM bietet schon seit längerem die Unterstützung für verschiedene Messaging Dienste an. Mit dem msgDialog Modul können vordefinierte Dialoge erstellt werden. Die Kommunikation erfolgt dabei über den msg Befehl welcher zurzeit die bidirektionale Kommunikation über TelegramBot, Jabber und yowsup (WhatsApp) unterstützt. Eingehende Nachrichten werden einem ROMMATE oder GUEST zugeordnet. Darüber werden auch die Berechtigungen für die einzelnen Dialoge festgelegt. Die grundlegende Bedienung wird in diesem Video am Beispiel von TelegramBot gezeigt.

Konfiguration

Voraussetzungen

Für dieses Modul wird das Perl JSON Modul benötigt. Auf einem Debian-basierten System (z.B RaspberryPI o.ä.) kann man das mit dem folgenden Befehl installieren:

sudo apt-get install libjson-perl

Hinweis zu den Code Beispielen

Es handelt sich bei den Beispielen um eine Raw definition, siehe auch Import_von_Code_Snippets

msgConfig

Als erstes benötigt man ein definiertes msgConfig device:

defmod myMsgConfig msgConfig
attr myMsgConfig userattr msgDialog_evalSpecials:textField-long msgDialog_msgCommand:textField
attr myMsgConfig msgContactPush <Name des TelegramBot-device>
attr myMsgConfig msgDialog_evalSpecials me=<Aktivierungswort bzw. -Nachricht>\
TelegramBot=<Name des TelegramBot-device>

Falls bereits vorhanden, müssen natürlich nur die fehlenden Attribute gesetzt werden.

msgDialog_evalSpecials
key1=value1 key2=value2 ... Leerzeichen getrennte Liste von Name=Wert Paaren. Wert kann Leerzeichen enthalten, falls es in "" oder {} eingeschlossen :ist. Wert wird als perl-Ausdruck ausgewertet, falls es in {} eingeschlossen ist. In der DEF werden %Name% Zeichenketten durch den zugehörigen Wert :ersetzt. Wenn der selbe Name im msgConfig und msgDialog definiert wurde, wird der Wert aus msgDialog verwendet.
msgDialog_msgCommand <command>
Befehl zum Versenden einer Nachricht. Die Vorgabe ist "msg push \@$recipients $message". Wenn nicht gesetzt, wird die Vorgabe verwendet.

ROOMMATE / GUEST

Für jeden Dialog kann festgelegt werden welche Person dazu berechtigt ist. Dazu sind Geräte vom Typ ROOMMATE oder GUEST mit definiertem msgContactPush Attribut erforderlich. Es ist darauf zu achten, dass das Reading fhemMsgRcvPush ein Event erzeugt. Siehe auch Attribut "allowed" in msgDialog.

Der Inhalt vom Attribut msgContactPush muss folgendem Muster entsprechen:

<NAME des TelegramBot Device>:@<zum ROOMMATE/GUEST gehörende Telegram ID>

Define

Definition des metaDialogs: Zur Auflistung aller berechtigten Dialoge dient - was sonst - ein Dialog!

defmod meta_Dialog msgDialog {\
  "%me%": {\
    "match": "\/?(start|%me%)",\
    "commands": "deletereading TYPE=msgDialog $recipient_history",\
    "message": [\
      "{return('(' . join(') (', sort{lc($a) cmp lc($b)} (split('\n', fhem('get TYPE=msgDialog:FILTER=NAME!=$SELF:FILTER=allowed=.*($recipient|everyone).* trigger', 1)))) . ') (abbrechen) ')}",\
      "Ich kann folgendes für dich tun:"\
    ]\
  },\
  "zurück": {\
    "commands": "set $recipient_history=.+ say @$recipient {(ReadingsVal($DEV, '$recipient_history', '') =~ m/((.+)\\|.+$)/;;;; return($2 || $1);;;;)}"\
  },\
  "abbrechen": {\
    "match": "\/?abbrechen",\
    "commands": "deletereading TYPE=msgDialog $recipient_history",\
    "message": [\
      "TelegramBot_MTYPE=queryInline (%me%) ",\
      "Dialog abgebrochen."\
    ]\
  },\
  "beenden": {\
    "match": "\/?beenden",\
    "commands": "deletereading TYPE=msgDialog $recipient_history",\
    "message": [\
      "TelegramBot_MTYPE=queryInline (%me%) ",\
      "Dialog beendet."\
    ]\
  }\
}

attr meta_Dialog allowed everyone

Aufgrund der Komplexität eines Dialogs mit JSON ist es am praktikabelsten, zunächst einen leeren Dialog zu definieren:

define <name> msgDialog {} 

Anschließend kann die DEF in der Detail-Ansicht des Dialog-Device bearbeitet werden. Jeder Dialog basiert auf der folgenden Struktur von einfach bis komplex.

{
  "<TRIGGER>": {
    "match": "<regex>",
    "setOnly": (true|false),
    "commands": "(fhem command|{perl code})",
    "message": [
      "{perl code}",
      "text"
    ],
    "<NEXT TRIGGER 1>": {
      ...
    },
    "<NEXT TRIGGER 2>": {
      ...
    }
  }
}

Um den JSON-Teil der Dialoge zu testen, empfiehlt sich ein Besuch auf https://jsonlint.com/ Hier kann der selbst geschriebene JSON-Code validiert werden.

TRIGGER
Kann ein beliebiger Text sein. Es wird geprüft ob die eingehende Nachricht damit übereinstimmt. Falls ja, wird der Dialog an dieser Stelle fortgesetzt.
match
Wenn nicht nur genau eine Nachricht zugelassen werden soll, kann noch eine regex angegeben werden. Die regex muss auf die gesamte eingehnde Nachricht :zutreffen.
setOnly
Kann optional auf true oder false gestellt werden. In beiden fällen wird der TRIGGER dann nicht bei "get <name> trigger" zurück gegeben.
Wenn setOnly auf "true" gestellt wird, kann der Dialog an dieser Stelle nicht durch eingehende Nachrichten ausgelöst werden, sondern nur über "get <name> say TRIGGER". Dies kann dazu genutzt werden um einen Dialog aus :FHEM heraus zu initiieren.
commands
Kann einen einzelnen oder mehrere Befehle enthalten:
"commands": "single command"
"commands": [
"command 1",
"command 2",
"{perl command}"
]
message
Kann einen einzelnen oder mehrere Texte enthalten die mit einen Zeilenumbruch verbunden werden:
"message": [
  "text 1",
  "text 2",
  "{return from perl command}"
]
Dialogfelder
Um Dialogfelder im Keyboard anzuzeigen müssen diese innerhalb des message-Blocks in runden Klammern stehen(). Wichtig ist das nach der schließenden Klammer ein Leerzeichen folgt. Damit das Keyboard angezeigt wird, muss auch ein normaler Text im bessage-Block enthalten sein.
"message": [
  "(Dialogfeld 1) ",
  "(Dialogfeld 2) ",
  "Bitte wähle ein Dialogfeld aus"
]

Bei mehrstufigen Dialogen wird diese Struktur ineinander verschachtelt angegeben. Es werden Variablen und Platzhalter, welche unter dem Attribut evalSpecials definiert werden, ausgewertet.

Variablen

  • $SELF: Eigenname des msgDialog
  • $message: eingegangene Nachricht
  • $recipient: Name des Dialogpartners

Attribute

  • allowed: Liste mit allen RESIDENTS und ROOMMATE die für diesen Dialog berechtigt sind.
  • disable: [0|1] Dialog ist aktiviert|deaktiviert.
  • disabledForIntervals: HH:MM-HH:MM HH:MM-HH-MM ...
  • evalSpecials: key1=value1 key2=value2 ...Leerzeichen getrennte Liste von Name=Wert Paaren. Wert kann Leerzeichen enthalten, falls es in "" oder {} eingeschlossen ist. Wert wird als perl-Ausdruck ausgewertet, falls es in {} eingeschlossen ist. In der DEF werden %Name% Zeichenketten durch den zugehörigen Wert ersetzt. Dieses Attribut ist als "msgDialog_evalSpecials" im msgConfig Gerät vorhanden. Wenn der selbe Name im msgConfig und msgDialog definiert wurde, wird der Wert aus msgDialog verwendet.
  • msgCommand <command>: Befehl der zum Versenden einer Nachricht verwendet wird. Die Vorgabe ist "msg push \@$recipients $message" Dieses Attribut ist als "msgDialog_msgCommand" im msgConfig Gerät vorhanden.

set

  • reset: Setzt den Dialog für alle Benutzer zurück.
  • say [@<recipient1>[,<recipient2>,...]] <TRIGGER>[|<NEXT TRIGGER>|...]: Der Dialog wird für alle angegeben Empänger an der angegeben Stelle fortgeführt. Sind keine Empfänger angegeben wird der Dialog für alle unter dem Attribut allowed angegebenen Empfänger fortgeführt.
  • updateAllowed: Aktualisiert die Auswahl für das Attribut allowed.

get

  • trigger: Listet alle TRIGGER der ersten Ebene auf bei denen nicht setOnly angegeben ist.

READINGS

  • $recipient_history: Durch | getrennte Liste von TRIGGER um den aktuellen Zustand des Dialogs zu sichern. Für jeden Dialogpartner wird ein Reading angelegt. Wenn der Dialog beendet ist wird das Reading zurückgesetzt.

Hilfe bei der Fehlersuche

Hier entsteht eine Sammlung von Tipps bei der Fehlersuche. Die meisten dieser "Stolpersteine" entstehen häufig bei der Erstellung eines ersten eigenen Dialoges.

Das Attribut allowed fehlt oder ist nicht korrekt gesetzt

Das allowed-Attribut dient dazu, einzelne Dialoge für alle oder nur bestimmte Benutzer zu berechtigen. Fehlt es, wird der Dialog dem aufrufenden Benutzer nicht angezeigt.

attr Testdialog allowed everyone

Das Attribut msgDialog_evalSpecials fehlt oder ist nicht korrekt gesetzt

Dieses Attribut gehört dem msgConfig device und muss wie in 2.3 zu lesen korrekt befüllt werden.

attr myMsgConfig msgDialog_evalSpecials me=<Aktivierungswort bzw. -Nachricht>
TelegramBot=<Name des TelegramBot-device>

Das Attribut utf8Special fehlt beim TelegramBot device

Dieses Attribut ist für die korrekte UTF-8 Kodierung zuständig und im Standard deaktiviert. Der Satz "Was kann ich für dich tun?" aus dem Meta-Dialog enthält bereits einen Umlaut!

Folgende FHEM-Log-Fehlermeldung kann auf das fehlende Attribut hindeuten:

<Datum> <Zeit> 3: TelegramBot_Callback <TelegramBot-Device>: resulted in NonBlockingGet timed out on read from <hidden> after 30s from SendIt

JSON-Syntax Fehler

Das Modul prüft vor dem Abspeichern auf eine korrekte JSON-Syntax. Meistens wird auch eine sprechende Meldung angezeigt:

Usage: define <name> msgDialog {JSON}

, or ] expected while parsing array, at character offset 73 (before "}\n}") at ./FHEM/76_msgDialog.pm line 93.

Eine gute Anlaufstelle zur Überprüfung von JSON-Code ist hier: https://jsonlint.com/

Es werden keine Dialoge angezeigt

Ohne die Angabe einer normalen Nachricht wird bei Keyboards kein Dialog angezeigt:

"message": [
"(Lampen einschalten) ",
"(Lampen ausschalten) ",
"(zurück:%me%) ", 
],

Erst mit der Nachricht "Möchtest Du Lampen ein oder ausschalten?" erscheint der Dialog:

"message": [
"(Lampen einschalten) ",
"(Lampen ausschalten) ",
"(zurück:%me%) ", 
"Möchtest Du Lampen ein oder ausschalten?"
],

Innerhalb eines Dialogs fehlen einzelne Menüeinträge

Das liegt das meistens an einem fehlenden Leerzeichen:

"message": [
"(Lampen einschalten)",
"(Lampen ausschalten) ",
"(zurück:%me%) ", 
],

Bei "Lampen ausschalten" ist dagegen korrekt ein Leerzeichen zu sehen und somit auch der Menüeintrag im Dialog.

":" bei Inline Keyboards

Bei Inline Keyboards wird der letzte : als Trennzeichen zwischen Nachricht und Befehl ausgewertet. Beispiel:

"(Temperatur: [heizung:desiredTemperature]°C:setheiz) "

oder

"([test:state]:weiter) " wenn [test:state] = "Status: ok"

Beispiele

Programmierung der Waschmaschine

Das ist ein Beispiel zur Programmierung einer Waschmaschine wie auch oben im Video zu sehen:

defmod Waschmaschine_Dialog msgDialog { "Waschmaschine": {\
    "message": [\
      "{return('(Zeitprogramm stoppen) ') if(ReadingsVal('%controlUnit%', 'controlMode', '') eq 'auto')}",\
      "{return('(programmieren) ') if(ReadingsVal('%actor%', 'state', '') ne 'on')}",\
      "{return('(einschalten) ') if(ReadingsVal('%actor%', 'state', '') ne 'on')}",\
      "(Verlaufsdiagramm) ",\
      "(abbrechen) ",\
      "{return('Waschmaschine: ' . (ReadingsVal('%actor%', 'state', '') eq 'on' ? 'eingeschaltet' : 'ausgeschaltet'))}",\
      "{return('Modus: ' . (ReadingsVal('%controlUnit%', 'controlMode', '') eq 'auto' ? 'Automatik' : 'Manuell (' . ReadingsVal('%controlUnit%', 'time', '') . ')'))}"\
    ],\
    "Zeitprogramm stoppen": {\
      "commands": "set %controlUnit% controlMode manual",\
      "message": [\
        "TelegramBot_MTYPE=queryInline (%me%) ",\
        "Das Zeitprogramm wurde gestoppt."\
      ]\
    },\
    "programmieren": {\
      "message": [\
        "(bestätigen|zurück|abbrechen) ",\
        "( 00:00 | 00:15 | 00:30 | 00:45 ) ",\
        "( 01:00 | 01:15 | 01:30 | 01:45 ) ",\
        "( 02:00 | 02:15 | 02:30 | 02:45 ) ",\
        "( 03:00 | 03:15 | 03:30 | 03:45 ) ",\
        "( 04:00 | 04:15 | 04:30 | 04:45 ) ",\
        "( 05:00 | 05:15 | 05:30 | 05:45 ) ",\
        "( 06:00 | 06:15 | 06:30 | 06:45 ) ",\
        "( 07:00 | 07:15 | 07:30 | 07:45 ) ",\
        "( 08:00 | 08:15 | 08:30 | 08:45 ) ",\
        "( 09:00 | 09:15 | 09:30 | 09:45 ) ",\
        "( 10:00 | 10:15 | 10:30 | 10:45 ) ",\
        "( 11:00 | 11:15 | 11:30 | 11:45 ) ",\
        "( 12:00 | 12:15 | 12:30 | 12:45 ) ",\
        "( 13:00 | 13:15 | 13:30 | 13:45 ) ",\
        "( 14:00 | 14:15 | 14:30 | 14:45 ) ",\
        "( 15:00 | 15:15 | 15:30 | 15:45 ) ",\
        "( 16:00 | 16:15 | 16:30 | 16:45 ) ",\
        "( 17:00 | 17:15 | 17:30 | 17:45 ) ",\
        "( 18:00 | 18:15 | 18:30 | 18:45 ) ",\
        "( 19:00 | 19:15 | 19:30 | 19:45 ) ",\
        "( 20:00 | 20:15 | 20:30 | 20:45 ) ",\
        "( 21:00 | 21:15 | 21:30 | 21:45 ) ",\
        "( 22:00 | 22:15 | 22:30 | 22:45 ) ",\
        "( 23:00 | 23:15 | 23:30 | 23:45 ) ",\
        "Wann soll die Wäsche fertig sein?",\
        "Bitte Uhrzeit in HH:MM angeben.",\
        "Aktuell ist [%controlUnit%:time] Uhr eingestellt."\
      ],\
      "Uhrzeit": {\
        "match": " ?([0-1][0-9]|2[0-3]):[0-5][0-9] ?",\
        "commands": [\
          "set %controlUnit% time $message",\
          "set $SELF say @$recipient Waschmaschine|programmieren|bestätigen"\
        ]\
      },\
      "bestätigen": {\
        "commands": "set %controlUnit% controlMode auto",\
        "message": [\
          "TelegramBot_MTYPE=queryInline (%me%) ",\
          "Das Zeitprogramm wurde eingestellt.",\
          "Die Wäsche wird voraussichtlich um [%controlUnit%:time] Uhr fertig sein.",\
          "Bitte die Waschmaschine vorbereiten."\
        ]\
      }\
    },\
    "einschalten": {\
      "commands": [\
        "set %controlUnit% controlMode manual",\
        "set %actor% on"\
      ]\
    },\
    "Verlaufsdiagramm": {\
      "commands": "set %TelegramBot% cmdSend {plotAsPng('%plot%')}",\
      "message": "TelegramBot_MTYPE=queryInline (%me%) $message"\
    }\
  },\
  "auto": {\
    "setOnly": true,\
    "commands": [\
      "set %actor% on",\
      "set %controlUnit% controlMode manual"\
    ],\
    "message": [\
      "TelegramBot_MTYPE=queryInline (%me%) ",\
      "Die Wachmaschine wurde automatisch eingeschaltet."\
    ]\
  },\
  "manual": {\
    "setOnly": true,\
    "message": [\
      "TelegramBot_MTYPE=queryInline (%me%) ",\
      "Die Wachmaschine wurde manuell eingeschaltet."\
    ]\
  },\
  "done": {\
    "setOnly": true,\
    "commands": "set %actor% off",\
    "message": [\
      "TelegramBot_MTYPE=queryInline (%me%) ",\
      "Die Wachmaschine ist fertig."\
    ]\
  }\
}
attr Waschmaschine_Dialog evalSpecials actor=HM_2C10D8_Sw\
controlUnit=Waschkeller_washer_controlUnit\
plot=Waschkeller_washer_SVG

TelegramBot

In Telegram gibt es die Möglichkeit die Art des Keyboards zu ändern. Links das normale Keyboard, rechts das Inline Keyboard.

Normales Keyboard Inline Keyboard

In diesem Video wird Telegram mit einem Inline Keyboard verwendet.

Um eine Nachricht bei Telegram zu verändern, gibt es den Befehl "queryEditInline". Dafür wird die MsgId von der Nachricht benötigt. Das erledigt ein notify, welches die msgId pro Peer abspeichert:

defmod sentMsgIdByPeerId notify .+:sentMsgId.+ {\
  return if $TYPE ne 'TelegramBot';;\
  \
  my $sentMsgId = ReadingsVal($NAME, 'sentMsgId', '');;\
  my $sentMsgPeerId = ReadingsVal($NAME, 'sentMsgPeerId', '');;\
  my $contact = (devspec2array("TYPE=(ROOMMATE|GUEST):FILTER=msgContactPush=.*$sentMsgPeerId.*"))[0];;\
  \
  readingsSingleUpdate($defs{$NAME}, "$contact\_sentMsgId", $sentMsgId, 1);;\
}
attr sentMsgIdByPeerId devStateIcon {ReadingsVal($name, 'state', 'inactive') eq 'active' ? '.*:ios-on-blue:inactive' : '.*:ios-off:active'}
attr sentMsgIdByPeerId icon audio_mic
attr sentMsgIdByPeerId room msg

Danach wird der Message-Befehl von

set <TelegramBot> message

auf

set <TelegramBot> queryEditInline 

geändert. Dafür wird ein cmdalias verwendet:

defmod message2queryEditInline cmdalias set .+ message (.|\n)+ AS {\
  my ($NAME, $cmd, $message) = split(/[\s]+/, $EVENT, 3);;\
  my $TYPE = InternalVal($NAME, "TYPE", "");;\
  (my $recipient, $message) = ($message =~ m/(@\S+)? (.+)/s);;\
  \
  if($TYPE eq "TelegramBot" && $recipient){\
    my ($contact) = devspec2array("TYPE=(ROOMMATE|GUEST):FILTER=msgContactPush=.*$recipient.*");;\
    my $sentMsgId = ReadingsVal($NAME, "$contact\_sentMsgId", "");;\
\
    if($sentMsgId ne ""){\
      fhem("set $NAME queryEditInline $sentMsgId $recipient $message");;\
    }\
    else{\
      fhem("set $NAME queryInline $recipient $message");;\
    }\
  }\
  else{\
    fhem("set $EVENT");;\
  }\
}
attr message2queryEditInline room msg

Um wieder zurück zum normalen Keyboard zu gelangen genügt es, das notify und den cmdAlias zu deaktivieren. Das kann komfortabel über ein notify und einen dummy erledigt werden:

defmod inline_normal.NOT notify inline_normal.DUM:.* {\
if ("$EVENT" =~ "on") {\
  fhem "set sentMsgIdByPeerId active;; attr message2queryEditInline disable 0";;\
  } \
elsif ("$EVENT" =~ "off") {\
  fhem "set sentMsgIdByPeerId inactive;; attr message2queryEditInline disable 1";;\
  }\
}

Weitere Infos zum Thema Inline Keyboard sind in der Antwort #10 im Forum zu finden (siehe Link unten).

JABBER

Hier wird die Verwendung mit Jabber beschrieben...

WhatsApp

Hier wird die Verwendung mit yowsup/WhatsApp beschrieben...

Links