Modul PostMe: Unterschied zwischen den Versionen

Aus FHEMWiki
Keine Bearbeitungszusammenfassung
 
(56 dazwischenliegende Versionen von 4 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
'''Achtung: Work in progress. Bitte vorläufig Finger weg !'''
{{
{{
Infobox Modul
Infobox Modul
Zeile 10: Zeile 9:
|ModOwner=Prof. Dr. Peter A. Henning
|ModOwner=Prof. Dr. Peter A. Henning
}}
}}
=Allgemeines=
=Anwendung=
[[File:Postme2.png |600px]]
 
Das Modul ''95_PostMe.pm'' stellt eine komfortable Oberfläche bereit, um per Webinterface beliebige Listen ähnlich wie gelbe Klebezettel (Post-It™) zu verwalten.
Das Modul ''95_PostMe.pm'' stellt eine komfortable Oberfläche bereit, um per Webinterface beliebige Listen ähnlich wie gelbe Klebezettel (Post-It™) zu verwalten.
==Erste Schritte==
Jede dieser Listen wird im Folgenden als PostMe bezeichnet, und wir verwenden zur Vereinfachung den Devicenamen PostIt (der natürlich durch den eigenen Devicenamen zu ersetzen ist).
==Einbinden in die FHEM-Anzeige==
==Anwendungsbeispiel==
Mit dem folgenden Beispiel wird eine Liste "Baumarktliste" erzeugt, auf ihr die Einträge für den Kauf eines Sacks Estrichbeton und einer unbestimmten Menge Sägekettenöl vorgenommen
set PostIt create Baumarktliste
set PostIt add Baumarktliste Estrichbeton
set PostIt add Baumarktliste Sägekettenöl
set PostIt modify Baumarktliste Estrichbeton Menge 1 Sack
und die Liste dann per Instant Messenger an den zugeordneten Empfänger verschickt:
get PostIt message Baumarktliste
Der Empfänger wird dann die folgende Nachricht erhalten:
Baumarktliste: Estrichbeton[Menge="1 Sack"],Sägekettenöl
 
==Listen==
[[File:Postme11.png |600px]]
 
Jedes PostMe besteht aus einem Listennamen (=Überschrift) zur eindeutigen Identifikation (z.B. "Einkaufsliste"), und beliebig vielen Listeneinträgen. Ein PostMe wird dem System mit den folgenden Kommandos hinzugefügt, entfernt oder geleert:
set PostIt create ''Listenname''
set PostIt delete ''Listenname''
set PostIt clear ''Listenname''
 
Ein PostMe kann mit einem '''get'''-Befehl ausgegeben oder an eine vordefinierte Adresse geschickt werden. Dazu gibt es die Kommandos
get PostIt list ''Listenname''
Zur Ausgabe an ein Text-to-Speech-Device, hierzu müssen die Attribut postmeTTSFun und postmeTTSDev gesetzt sein:
get PostIt TTS ''Listenname''
Zur Ausgabe per eMail, hierzu muss dass Attribut postme'''xx'''MailRec für diese Liste (Nr. '''xx''') auf den Empfängernamen gesetzt sein und das Attribut postmeMailFun auf den Namen der eMail-Funktion gesetzt sein:
get PostIt mail ''Listenname''
Zur Ausgabe per Instant Messenger, hierzu muss dass Attribut postme'''xx'''MsgRec für diese Liste (Nr. '''xx''') auf den Empfängernamen gesetzt sein und das Attribut postmeMsgFun auf den Namen der Messenger-Funktion gesetzt sein:
get PostIt message ''Listenname''
 
==Listeneinträge==
Die Listeneinträge haben das Datenformat
''item'' [''attribute1''="''data1''" ''attribute2''="''data2''" ....]
und sind in einem einzigen langen String gespeichert. Trennzeichen ist das Komma, es darf also im Listeneintrag selbst nicht auftauchen.
*''item'' ist der eigentliche Listeneintrag, es kann sich um einen beliebigen Text (auch mit Leerzeichen) handeln.
**Ein ''item'' wird dem PostMe mit den folgenden Kommandos hinzugefügt bzw. gelöscht
  set PostIt add ''Listenname'' ''text''
  set PostIt remove ''Listenname'' ''text''
**Dabei darf der ''text'' beim Hinzufügen auch Kommata enthalten - dann werden eben mehrere ''items'' erzeugt. Es dürfen kein eckigen Klammern enthalten sein.
*In eckigen Klammern [...] können einem ''item'' beliebige Metadaten in Form von Attribut-Wert-Paaren hinzugefügt werden. Dafür gibt es das Kommando
  set PostIt modify ''Listenname'' ''item'' ''attribute'' ''value''
**Ist das Attribut ''attribute'' noch nicht vorhanden, wird es angelegt und erhält den Wert ''value''
**Ist das Attribut ''attribute'' bereits vorhanden, wird sein Wert mit dem Wert ''value'' überschrieben. Wenn ''value'' leer ist, wird das Attribut entfernt.
==Definition==
Die folgende beispielhafte Definition setzt das PostMe-System mit dem Devicenamen '''PostIt''' in Betrieb:
define PostIt PostMe
attr PostIt postmeStd Einkaufsliste,Baumarktliste ''(Standard-Listen, die immer vorhanden sind (nicht löschbar))''
attr PostIt icon <embed src="/fhem/PostMe_widget?type=pins&postit=PostIt"/> ''(siehe unten)''
attr PostIt postmeTTSFun sendTTS ''(Funktionsname einer Funktion zur Sprachausgabe. Wird mit 1 Parameter (Titelzeile:Inhalt) aufgerufen)''
attr PostIt postmeMailFun DebianMail ''(Funktionsname einer Funktion zum Mailversand. Wird mit 3 Parametern (Empfänger,Titelzeile, Inhalt) aufgerufen)''
attr PostIt postmeMsgFun Telegram ''(Funktionsname einer Funktion zum Versand von Instant messages. Wird mit 3 Parametern (Empfänger,Titelzeile, Inhalt) aufgerufen)''
attr PostIt postme01MailRec ''<mail-Adresse Person 1>''
attr PostIt postme01MsgRec ''<messenger-Adresse Person 1>''
attr PostIt postme02MailRec ''<mail-Adresse Person 2>''
attr PostIt postme02MsgRec ''<messenger-Adresse Person 2>''
 
attr PostIt postmeStyle jQuery ''(siehe unten)''
attr PostIt postmeClick 0 ''(siehe unten)''
attr PostIt postmeIcon images/default/pin_red_32.png
 
In der Weboberfläche sieht das dann so aus: [[File:Postme13.png |600px]]
==Service-Routinen==
Das Modul verfügt über zwei Service-Routinen, mit denen ohne Kenntnis von internen Zuständen Listen abgefragt werden können.
===PostMe_tgi(''<Devicename>'',''<Listenname>'')===
liefert das PostMe ''Listenname'' in einem für ''Inline-Keyboards'' des Instant Messengers Telegram geeigneten Format zurück, also z.B.
PostMe_tgi('PostIt','Einkaufsliste');
=> (Milch:Einkaufsliste_item00) (Honig:Einkaufsliste_item01) (Streichhölzer:Einkaufsliste_item02) Einkaufsliste
Zur Weiterverarbeitung siehe das entsprechende Kapitel weiter unten. Die Abfrage dieser Service-Routine kann auch von einem anderen Rechner aus erfolgen, dazu verpackt man das idealerweise in ein kleines Shell-Skript:
#!/bin/bash
postme=$1
liste=$2
FHEMIP=''<IP-Adresse des FHEM-Servers''
echo "{PostMe_tgi('$postme','$liste')}" | socat -t50 - TCP:$FHEMIP:7072
 
===PostMe_tgj(''<Devicename>'',''<Listenname>'')===
liefert das PostMe ''Listenname'' in als "nacktes" Array zurück. Diese Abfrage macht nur Sinn, wenn sie innerhalb eines Perl-Codeblocks ausgeführt wird.
=Steuerung via Spracherkennung=
Für die folgende Anwendung gehen wir davon aus, dass auf einem FHEM-Gerät eine Spracherkennung installiert ist. Der erkannte Sprachstring wird an eine Perl-Funktion als Parameter ''$command'' übergeben. Im Nachfolgenden wird die Auswertung nur für das PostMe '''Einkaufsliste''' beschrieben
}elsif( $command =~ /.*(L|l)iste.*/) {
    my $liste;
    if( $command =~ /.*Einkaufs.*/ ){
      $liste = "Einkaufsliste";
    }
    if( $command =~ /.*löschen.*/ ){
      fhem("set PostIt clear ".$liste);
      fhem("set GalaxyTab ttsSay ".$liste." gelöscht");
    }elsif( $command =~ /.*E-Mail.*/ ){
      fhem("get PostIt mail ".$liste);
      fhem("set GalaxyTab ttsSay ".$liste." per Email verschickt");
    }elsif( $command =~ /.*(T|t)eleg.*/ ){
      fhem("get PostIt message ".$liste);
      fhem("set GalaxyTab ttsSay ".$liste." per Telegramm verschickt");
    }elsif( $command =~ /.*(H|h)inzufügen.*/ ){
      $command =~ s/.*(H|h)inzufügen\s+//;
      fhem("set PostIt add ".$liste." ".$command);
      fhem("set GalaxyTab ttsSay Zur ".$liste." hinzugefügt ".$command);
    }elsif( $command =~ /.*(Ä|ä)ndern.*/ ){
      $command =~ s/.*(Ä|ä)ndern\s+//;
      fhem("set PostIt modify ".$liste." ".$command);
      fhem("set GalaxyTab ttsSay Auf ".$liste." geändert ".$command);
    }elsif( $command =~ /.*(E|e)ntfernen.*/ ){
      $command =~ s/.*(E|e)ntfernen\s+//;
      my $res=fhem("set PostIt remove ".$liste." ".$command);
      fhem("set GalaxyTab ttsSay Von der ".$liste." entfernt ".$command);
    }else{
      fhem("get PostIt ttsSay ".$liste);
    }
 
=Einbinden in die FHEM-Anzeige=
PostMes können in jede beliebige Webseite eingebunden werden. Beispielsweise kann auch das Standard-Attribut ''icon'' des diesem Postme-System zugeordneten Devices durch einen der folgenden Codes ersetzt werden.
PostMes können in jede beliebige Webseite eingebunden werden. Beispielsweise kann auch das Standard-Attribut ''icon'' des diesem Postme-System zugeordneten Devices durch einen der folgenden Codes ersetzt werden.
* Mit dem Code  
* Mit dem HTML-Code  
  <embed src="/fhem/PostMe_widget?type=pins&postit=''<devicename>''"/>
  <embed src="/fhem/PostMe_widget?type=pins&postit=''<devicename>''"/>
wird eine interaktive Liste aller Titelzeilen der PostMes dieses Systems angezeigt.
wird eine interaktive Liste aller Titelzeilen der PostMes dieses Systems angezeigt.
* Mit dem Code  
* Mit dem Code  
  <embed src="/fhem/PostMe_widget?type=pin&postit=''<devicename>''&name=''<name>''"/>
  <embed src="/fhem/PostMe_widget?type=pin&postit=''<devicename>''&name=''<name>''"/>
wird ein einzelnes PostMe mit dem Namen ''<name>'' als Titelzeile angezeigt.
wird ein einzelnes PostMe mit dem Namen ''<name>'' als Titelzeile angezeigt. Zur Anpassung der Höhe kann man noch ein ''height''-Attribut einsetzen, z.B.:
===Art der Widgets===
<embed src="/fhem/PostMe_widget?type=pin&postit=''<devicename>''&name=''<name>''" height="350"/>
==Art der Widgets==
In diesen Darstellungen kann durch die Maus erreicht werden, dass zur einzeiligen Anzeige der Titelzeile der volle Inhalt des PostMes angezeigt wird.
In diesen Darstellungen kann durch die Maus erreicht werden, dass zur einzeiligen Anzeige der Titelzeile der volle Inhalt des PostMes angezeigt wird.
* Wenn das Attribut ''postmeClick'' auf 0 gesetzt ist (Default-Wert), erfolgt die Anzeige und das Löschen des PostMe-Inhaltes durch Überfahren bzw. Wegfahren des Mauszeigers auf die Titelzeile.
* Wenn das Attribut ''postmeClick'' auf 0 gesetzt ist (Default-Wert), erfolgt die Anzeige und das Löschen des PostMe-Inhaltes durch Überfahren bzw. Wegfahren des Mauszeigers auf die Titelzeile.
* Wenn das Attribut ''postmeClick'' auf 1 gesetzt ist, muss die Titelzeile angeklickt werden, um den Inhalt zu sehen - und das entsprechens Fenster bzw. die Dialogbock explizit geschlossen werden.
* Wenn das Attribut ''postmeClick'' auf 1 gesetzt ist, muss die Titelzeile angeklickt werden, um den Inhalt zu sehen - und das entsprechens Fenster bzw. die Dialogbock explizit geschlossen werden.
Welcher Art die Anzeige des PostMe-Inhalte sist, wird durch ein anderes Attribut geregelt.
Welcher Art die Anzeige des PostMe-Inhalte ist, wird durch ein anderes Attribut geregelt.
* Wenn ''postmeStyle=jQuery" (Default-Wert), erfolgt die Anzeige in einer jQuery-Dialogbox.  
* Wenn ''postmeStyle=jQuery" (Default-Wert), erfolgt die Anzeige in einer jQuery-Dialogbox.  
* Wenn ''postmeStyle=HTML", erfolgt die Anzeige in einem neuen (kleinen) Browser-Fenster.
* Wenn ''postmeStyle=HTML", erfolgt die Anzeige in einem neuen (kleinen) Browser-Fenster.
* Wenn ''postmeStyle=SVG", erfolgt die Anzeige in einer SVG-Viewbox.
* Wenn ''postmeStyle=SVG", erfolgt die Anzeige in einer SVG-Viewbox.
===Aussehen der widgets===
 
==Aussehen der Widgets==
Die oben genannten Widgets werden in ihrem Aussehen über Cascading Stylesheets gesteuert. Drei Klassen sind dafür relevant, sie sollten in die Datei '''style.css''' (oder, wenn man einen anderen Stylesheet-Präfix gesetzt hat, <präfix>style.css) eingetragen werden. Mit beispielhafter Farbe für die Widgets:
Die oben genannten Widgets werden in ihrem Aussehen über Cascading Stylesheets gesteuert. Drei Klassen sind dafür relevant, sie sollten in die Datei '''style.css''' (oder, wenn man einen anderen Stylesheet-Präfix gesetzt hat, <präfix>style.css) eingetragen werden. Mit beispielhafter Farbe für die Widgets:
.postmeclass { background-color:#ffee80; padding:10px; border-color:black; border:groove}
.postmeclass2 { background-color:#ffee80; padding:10px;}


  .postmeclass { background-color:#ffee80; padding:10px; border-color:black; border:groove}
=Steuerung per Telegram=
Mit dem Messenger ''Telegram'' kann man Kommandos von außen an FHEM schicken. Dazu pollt der Telegram-Client, der mit Hilfe des [[TelegramBot|TelegramBot-Moduls]] realisiert wird, in regelmäßigen Abständen einen entsprechenden Server im Internet. Nach derzeitigem Stand der Technik ist das sicher, allerdings ist es empfehlenswert, diesen TelegramBot nicht auf dem gleichen Rechner wie das FHEM-Hauptsystem laufen zu lassen (siehe Anleitung [[TelegramBot]]).
 
Mit einem solchen Kommando kann man z.B. einen Dummy setzen - das löst einen Event aus, der via FHEM2FHEM auch auf entfernten FHEM-Systemen registriert werden kann. Mit einem notify bzw. DOIF kann dieser Event abgefangen und zur Absendung einer Antwort durch das PostMe-Modul verwendet werden:
 
  define Remote.N DOIF ([Task] eq "Einkaufsliste") (get PostIt Einkaufsliste message,set Remote.Task Einkaufsliste Telegram)
 
Insgesamt führt dies dazu, dass man z.B. einfach von außen an seinen eigenen TelegramBot die Nachricht
  ''magisches_Codewort'' set Task Einkaufsliste
senden kann - und kurz darauf die Einkaufsliste zurückbekommt.
== Komfortabel verwalten mit ''Favorites''==
Komfortabler ist die Bedienung von PostMe mit Telegram, wenn man diesem Messenger so genannte ''Favorites'' beibringt - das sind bevorzugte Kommandos. Beim Absenden dieser Kommandos auf einem Smartphone genügt es, die Kommandoabkürzung mit einem vorangestellten "/" zu senden. Definiert man beispielsweise für das Telegram-Device (''NICHT für das Postme-Device'')
attr Telegram favorites
/H=set Telegram message "/Einkauf <sonstige Listen>";
/Einkauf=set Task get PostIt message Einkaufsliste;
/Einkauf+=set Task set PostIt add Einkaufsliste;
/Einkauf-=set Task set PostIt remove Einkaufsliste;
so kann man mit der Telegram-Nachricht
/H
ein auswählbares Menü seiner Listen erhalten, mit
/Einkauf
die Einkaufsliste abrufen und mit
/Einkauf+ <Item>
/Einkauf- <Item>
Gegenstände hinzufügen oder entfernen.
'''Achtung:''' Eigentlich ist das eine Sicherheitslücke, weil das magische Codewort nicht nötig ist. Man sollte also erstens die Telegram-User sorgfältig begrenzen, die diese Kommandos ausführen können, und zweitens auf keinen Fall beliebige FHEM-Kommandos über diesen Mechanismus ausführbar machen. Im hier diskutierten Fall (separate FHEM-Instanz und Trennung per FHEM2FHEM, indem hier nur die Task-Variable gesetzt wird) kann das also als sicher gelten.
== Noch komfortabler verwalten mit ''Inline-Keyboards''==
{{Randnotiz|RNTyp=Info|RNText=Inzwischen gibt es für diese Funktionalität ein eigenes Modul [https://fhem.de/commandref.html#TBot_List TBot_List], das die Kopplung von TelegramBot und PostME ohne zusätzlichen perlcode erlaubt }}
 
Noch komfortabler ist die Bedienung von PostMe mit Telegram, wenn man diesem Messenger so genannte ''Inline-Keyboards'' beibringt. Das geht auf zweierlei Arten, die im Folgenden dokumentiert sind.
{| class="wikitable"
|scope="col" style="width: 1000px" colspan="2"|[[File:Pmbot1.png |450px]]
|-
|scope="col" style="width: 1000px" colspan="2"|Benutzer tippt im Client auf den Button "PostIt". Im FHEM wird über ein notify, das auf ''queryData'' triggert, eine Erkennungsroutine aufgerufen, der Benutzer erhält:
|-
|style="width: 500px; vertical-align:top" |[[File:Pmbot2.png |450px]]
|style="width: 500px; vertical-align:top" |[[File:Pmbot2b.png |450px]]
|-
|scope="col" style="width: 1000px" colspan="2"|Benutzer tippt im Client auf den Button "Einkauf".Im FHEM wird über ein notify, das auf ''queryData'' triggert, eine Erkennungsroutine aufgerufen, der Benutzer erhält:
|-
|style="width: 500px; vertical-align:top" |[[File:Pmbot3.png |450px]]
|style="width: 500px; vertical-align:top" |[[File:Pmbot3b.png |450px]]
|-
|scope="col" style="width: 500px; vertical-align:top"|Benutzer tippt im Client auf den Button "Einkauf hinzu".
Im FHEM wird wieder über das notify, das auf ''queryData'' triggert, eine Erkennungsroutine aufgerufen. Diese stellt fest, dass etwas hinzugefügt werden soll sendet an den Client eine msgForceReply "Eingabe einzufügender Posten". Im TelegramBot wird ein Userreading gesetzt auf "add Einkaufsliste"
|scope="col" style="width: 500px; vertical-align:top"|Benutzer tippt im Client auf den Button "hinzufügen".
Im FHEM wird wieder über das notify, das auf ''queryData'' triggert, eine Erkennungsroutine aufgerufen. Diese stellt fest, dass etwas hinzugefügt werden soll sendet an den Client eine msgForceReply "Eingabe einzufügender Posten". Im TelegramBot wird ein Userreading gesetzt auf "add Einkaufsliste"
|-
|style="width: 1000px" colspan="2"|[[File:Pmbot4.png |450px]]
|-
|scope="col" style="width: 1000px" colspan="2"|Benutzer tippt im Client einen Gegenstand ein und sendet diesen an den TelegramBot. Dasselbe notify triggert AUCH auf msgReplyMsgId und ruft erneut die Erkennungsroutine, diese weiß aus dem Userreading, was sie zu tun hat und führt aus
set PostIt add Einkaufsliste Gegenstand
|-
|scope="col" style="width: 500px; vertical-align:top" |Zur Überprüfung tippt der Benutzer erneut auf den Button "Einkauf"  und erhält
|scope="col" style="width: 500px; vertical-align:top" |Der Benutzer erhält automatisch
|-
|style="width: 500px; vertical-align:top" |[[File:Pmbot5.png |450px]]
|style="width: 500px; vertical-align:top" |[[File:Pmbot5b.png |450px]]
|-
|scope="col" style="width: 1000px" colspan="2"|
Dazu benötigt man erst einmal, dass die Favorites beim Start eine externe Routine '''telegramRecognition''' aufrufen:
attr Telegram favorites /start={telegramRecognition("menuData: Hauptmenü")};
Außerdem das notify, das auf die richtigen Events triggert
define Telegram.N notify Telegram:((queryData)|(msgReplyMsgId)).* {telegramRecognition("$EVENT")}
|}
Die Erkennungsroutine ist recht komplex, sie wird im Folgenden komplett dargestellt:
#############################################################################
#
#  TelegramInlineKeyboard
#
#############################################################################
sub telegramRecognition($){
    my ($event)    = @_;
    my $querypeer      = ReadingsVal("TelegramBot", "queryPeer", 0);
    my $msgpeer        = ReadingsVal("TelegramBot", "msgPeer", 0);
    my $queryReplyMsgId= ReadingsVal("TelegramBot", "queryReplyMsgId", 0);
    my $MsgId          = ReadingsVal("TelegramBot", "MsgId", 0);
    my $menuMsgId      = ReadingsVal("TelegramBot", "menuMsgId", $queryReplyMsgId);
    my $calldata      = ReadingsVal("TelegramBot", "callData", "");
    my $tg;
    my $dp;
    my $dm;
    my $res;
    my $cmd;
    my $click;
    my ($cb1,$cb2,$cb1raw);
   
    #-- Dieses Flag wird =1 gesetzt, wenn das PostMe-Device auf in derselben FHEM-Instanz läuft,
    #                    =0, wenn das PostMe-Device auf einer anderen FHEM-Instanz läuft und per FHEM2FHEM
    #                        bedient wird
    my $postitlocal=1;
 
    #-- Hier kann man - ggf. für jeden $querypeer anders ! - einstellen, welcher Bestätigungstext vom Bot
        gesendet wird, und ob es eine nicht-klickbare Liste (linke Spalte oben) oder eine klickbare Liste
        (rechte Spalte oben) sein soll
    if( $querypeer eq "Peter_A._Henning"){
      fhem("attr TelegramBot queryAnswerText Gerne zu Diensten, Meister !");
      $click=1;
    }elsif( $querypeer eq "Jacqueline_Henning"){
      fhem("attr TelegramBot queryAnswerText Gerne zu Diensten, Jacqueline !");
      $click=0;
    }else{
      fhem("attr TelegramBot queryAnswerText Gerne zu Diensten!");
      $click=0;
    }


  .postmeclass2 { background-color:#ffee80; padding:10px;}
    #-- Klick event from inline keyboard
    if( $event =~ /queryData\:\s(.*)/ ){
      ($cb1,$cb2) = split(/ /,$1,2);
      #-- Level 0/1 => new menuMsgId after start of Bot
      if( $cb1 =~ /(PostIt)|(Steuerung)/ ){
        $menuMsgId = $queryReplyMsgId;
        fhem("setreading TelegramBot menuMsgId $menuMsgId");
      }
      #-- Level 0
      if( $cb1 eq "Hauptmenü"){
        fhem("set TelegramBot queryInline \@$querypeer (PostIt) (Steuerung) Hauptmenü"); 
      #-- Level 1
      }elsif( $cb1 eq "PostIt"){
        #-- PostIt-Menü für nicht-klickbare Listen
        if( $querypeer eq "Jacqueline_Henning"){
          fhem("set TelegramBot queryEditInline $menuMsgId \@$querypeer (Hauptmenü) (Einkauf|Einkauf hinzu|Einkauf entf) (Baumarkt|Baumarkt hinzu|Baumarkt entf) PostIt Listenverwaltung;");
        #-- PostIt-Menü für nicht-klickbare Listen
        }elsif( $querypeer eq "Peter_A._Henning"){
          fhem("set TelegramBot queryEditInline $menuMsgId \@$querypeer (Hauptmenü) (Einkauf) (Baumarkt) PostIt Listenverwaltung;");       
        }
      }elsif( $cb1 eq "Steuerung"){
        ... HIER NOCH ANDERE UNTERMENUS FÜR STEUERUNGSZWECKE
      #-- Level 2/3 PostIt
      }elsif( $cb1 =~ /((Einkauf)|(Baumarkt)).*/ ){
        my $cb1raw = $cb1;
        if( $cb1 =~ /Einkauf.*/){
          $cb1 = "Einkaufsliste";
          $tg  = 1;
          $dp  = "<hier Adressaten>";
          #$click = 0;
        }elsif( $cb1 =~ /Baumarkt.*/){
          $cb1 = "Baumarktliste";
          $tg  = 2;
          $dp  = "\<hier Adressaten>";
          #$click = 0;
        } 
        #-- Level 2/3 for clickable items
        if( $click==1 ){
          #-- Level 3 to delete an item
          if( $cb1raw =~ /.*item\d\d/ ){
            $cb2 = $cb1raw;
            $cb2 =~ s/^.*_//;
            if( $postitlocal==1 ){
              #-- local version
              fhem("set PostIt remove $cb1 $cb2");
            }else{
              #-- remote version
              fhem("set Task_90 set PostIt remove $cb1 $cb2");
            }
            #-- redisplay the list - small delay
            InternalTimer(gettimeofday()+1, "telegramRecognition","queryData: $cb1",0);
          #-- Level 3 to add an item
          }elsif( $cb1raw =~ /.*add/ ){
            fhem("set TelegramBot msgForceReply \@$querypeer Eingabe hinzuzufügender Posten");
            fhem("setreading TelegramBot prevCmd add $cb1");
          #-- Level 2 for clickable items
          }else{
            if( $postitlocal==1 ){
              #-- local version
              $res = PostMe_tgi("PostIt",$cb1); 
            }else{  
              #-- remote version
              $cmd = "/opt/fhem/getList.sh PostIt ".$cb1;
              $res = `$cmd`;
            }
            fhem("set TelegramBot queryEditInline $menuMsgId \@$querypeer (Hauptmenü|PostIt|hinzufügen:".$cb1."_add) ".$res."\n Klicken zum Entfernen");
          }
        #-- Level 2/3 for non-clickable items
        }elsif( $click==0 ){ 
          if( $cb2 ){
            #-- Level 3 to delete an item
            if( $cb2 eq "entf" ){
              fhem("set TelegramBot msgForceReply \@$querypeer Eingabe zu entfernender Posten");
              fhem("setreading TelegramBot prevCmd remove $cb1");
            #-- Level 3 to add an item
            }elsif( $cb2 eq "hinzu" ){
              fhem("set TelegramBot msgForceReply \@$querypeer Eingabe hinzuzufügender Posten");
              fhem("setreading TelegramBot prevCmd add $cb1");
            }
          #-- Level 2 for non-clickable items
          }else{
            if( $postitlocal==1 ){
              #-- local version
              fhem(sprintf("attr PostIt postme%02dMsgRec \@$querypeer",$tg));
              fhem(        "get PostIt  message $cb1");
              fhem(sprintf("attr PostIt postme%02dMsgRec %s",$tg,$dp));
            }else{
              #-- remote version
              fhem(sprintf("set Task_90 attr PostIt postme%02dMsgRec \@$querypeer",$tg));
              fhem(        "set Task_90 get  PostIt message $cb1");
              fhem(sprintf("set Task_90 attr PostIt postme%02dMsgRec %s",$tg,$dp));
            }
          }
        }
      }
    #-- Level 0 after start of bot
    }elsif( $event =~ /menuData\:\s*(.*)\s*(.*)/ ){
      my $cb1 = $1;
      my $cb2 = $2;
      if( $cb1 eq "Hauptmenü"){
        if( $msgpeer eq "Jacqueline_Henning"){
          fhem("set TelegramBot queryInline \@$msgpeer (PostIt) (Steuerung) Hauptmenü");
        }elsif( $msgpeer eq "Peter_A._Henning"){
          fhem("set TelegramBot queryInline \@$msgpeer (PostIt) (Steuerung) Hauptmenü");
        }else{
          fhem("set TelegramBot queryInline \@$msgpeer (PostIt) Hauptmenü");
        }
      }
    #-- Process line from forced reply
    }elsif( $event =~ /msgReplyMsgId\:\s+(\d*)/ ){
      my $mn = $1;
      my $mo = ReadingsVal("TelegramBot", "prevMsgId", 0)+1;
      my $prev = ReadingsVal("TelegramBot","prevCmd","none");
      if( $prev =~ /((add)|(remove)).*/ ){
        if( $postitlocal==1 ){
          #-- local version
          fhem("set PostIt $prev ".ReadingsVal("TelegramBot","msgText",""));
        }else{
          #-- remote version
          fhem("set Task_90 set PostIt $prev ".ReadingsVal("TelegramBot","msgText",""));
        }
        fhem("setreading TelegramBot prevCmd none");
        #-- redisplay the list - small delay
        $prev =~ s/((add)|(remove))\s*//;
        InternalTimer(gettimeofday()+1, "telegramRecognition","queryData: $prev",0);
      }
    }
  }


div.ui-widget-content {background-color: #ffee80; border-color:red;margin-top:-20px}
[[Kategorie:Telegram]]

Aktuelle Version vom 17. März 2019, 11:22 Uhr

PostMe
Zweck / Funktion
Das Modul stellt eine komfortable Oberfläche bereit, um per Webinterface beliebige Listen ähnlich wie gelbe Klebezettel (Post-It™) zu verwalten.
Allgemein
Typ Hilfsmodul
Details
Dokumentation EN / DE
Support (Forum) Unterstuetzende Dienste
Modulname 95_PostMe.pm
Ersteller Prof. Dr. Peter A. Henning
Wichtig: sofern vorhanden, gilt im Zweifel immer die (englische) Beschreibung in der commandref!

Anwendung

Postme2.png

Das Modul 95_PostMe.pm stellt eine komfortable Oberfläche bereit, um per Webinterface beliebige Listen ähnlich wie gelbe Klebezettel (Post-It™) zu verwalten. Jede dieser Listen wird im Folgenden als PostMe bezeichnet, und wir verwenden zur Vereinfachung den Devicenamen PostIt (der natürlich durch den eigenen Devicenamen zu ersetzen ist).

Anwendungsbeispiel

Mit dem folgenden Beispiel wird eine Liste "Baumarktliste" erzeugt, auf ihr die Einträge für den Kauf eines Sacks Estrichbeton und einer unbestimmten Menge Sägekettenöl vorgenommen

set PostIt create Baumarktliste
set PostIt add Baumarktliste Estrichbeton
set PostIt add Baumarktliste Sägekettenöl
set PostIt modify Baumarktliste Estrichbeton Menge 1 Sack

und die Liste dann per Instant Messenger an den zugeordneten Empfänger verschickt:

get PostIt message Baumarktliste 

Der Empfänger wird dann die folgende Nachricht erhalten:

Baumarktliste: Estrichbeton[Menge="1 Sack"],Sägekettenöl

Listen

Postme11.png

Jedes PostMe besteht aus einem Listennamen (=Überschrift) zur eindeutigen Identifikation (z.B. "Einkaufsliste"), und beliebig vielen Listeneinträgen. Ein PostMe wird dem System mit den folgenden Kommandos hinzugefügt, entfernt oder geleert:

set PostIt create Listenname
set PostIt delete Listenname
set PostIt clear Listenname

Ein PostMe kann mit einem get-Befehl ausgegeben oder an eine vordefinierte Adresse geschickt werden. Dazu gibt es die Kommandos

get PostIt list Listenname

Zur Ausgabe an ein Text-to-Speech-Device, hierzu müssen die Attribut postmeTTSFun und postmeTTSDev gesetzt sein:

get PostIt TTS Listenname 

Zur Ausgabe per eMail, hierzu muss dass Attribut postmexxMailRec für diese Liste (Nr. xx) auf den Empfängernamen gesetzt sein und das Attribut postmeMailFun auf den Namen der eMail-Funktion gesetzt sein:

get PostIt mail Listenname

Zur Ausgabe per Instant Messenger, hierzu muss dass Attribut postmexxMsgRec für diese Liste (Nr. xx) auf den Empfängernamen gesetzt sein und das Attribut postmeMsgFun auf den Namen der Messenger-Funktion gesetzt sein:

get PostIt message Listenname

Listeneinträge

Die Listeneinträge haben das Datenformat

item [attribute1="data1" attribute2="data2" ....]

und sind in einem einzigen langen String gespeichert. Trennzeichen ist das Komma, es darf also im Listeneintrag selbst nicht auftauchen.

  • item ist der eigentliche Listeneintrag, es kann sich um einen beliebigen Text (auch mit Leerzeichen) handeln.
    • Ein item wird dem PostMe mit den folgenden Kommandos hinzugefügt bzw. gelöscht
 set PostIt add Listenname text
 set PostIt remove Listenname text
    • Dabei darf der text beim Hinzufügen auch Kommata enthalten - dann werden eben mehrere items erzeugt. Es dürfen kein eckigen Klammern enthalten sein.
  • In eckigen Klammern [...] können einem item beliebige Metadaten in Form von Attribut-Wert-Paaren hinzugefügt werden. Dafür gibt es das Kommando
 set PostIt modify Listenname item attribute value
    • Ist das Attribut attribute noch nicht vorhanden, wird es angelegt und erhält den Wert value
    • Ist das Attribut attribute bereits vorhanden, wird sein Wert mit dem Wert value überschrieben. Wenn value leer ist, wird das Attribut entfernt.

Definition

Die folgende beispielhafte Definition setzt das PostMe-System mit dem Devicenamen PostIt in Betrieb:

define PostIt PostMe
attr PostIt postmeStd Einkaufsliste,Baumarktliste (Standard-Listen, die immer vorhanden sind (nicht löschbar))
attr PostIt icon <embed src="/fhem/PostMe_widget?type=pins&postit=PostIt"/> (siehe unten)
attr PostIt postmeTTSFun sendTTS (Funktionsname einer Funktion zur Sprachausgabe. Wird mit 1 Parameter (Titelzeile:Inhalt) aufgerufen)
attr PostIt postmeMailFun DebianMail (Funktionsname einer Funktion zum Mailversand. Wird mit 3 Parametern (Empfänger,Titelzeile, Inhalt) aufgerufen)
attr PostIt postmeMsgFun Telegram (Funktionsname einer Funktion zum Versand von Instant messages. Wird mit 3 Parametern (Empfänger,Titelzeile, Inhalt) aufgerufen)
attr PostIt postme01MailRec <mail-Adresse Person 1>
attr PostIt postme01MsgRec <messenger-Adresse Person 1>
attr PostIt postme02MailRec <mail-Adresse Person 2>
attr PostIt postme02MsgRec <messenger-Adresse Person 2>
attr PostIt postmeStyle jQuery (siehe unten)
attr PostIt postmeClick 0 (siehe unten)
attr PostIt postmeIcon images/default/pin_red_32.png

In der Weboberfläche sieht das dann so aus: Postme13.png

Service-Routinen

Das Modul verfügt über zwei Service-Routinen, mit denen ohne Kenntnis von internen Zuständen Listen abgefragt werden können.

PostMe_tgi(<Devicename>,<Listenname>)

liefert das PostMe Listenname in einem für Inline-Keyboards des Instant Messengers Telegram geeigneten Format zurück, also z.B.

PostMe_tgi('PostIt','Einkaufsliste'); 
=> (Milch:Einkaufsliste_item00) (Honig:Einkaufsliste_item01) (Streichhölzer:Einkaufsliste_item02) Einkaufsliste

Zur Weiterverarbeitung siehe das entsprechende Kapitel weiter unten. Die Abfrage dieser Service-Routine kann auch von einem anderen Rechner aus erfolgen, dazu verpackt man das idealerweise in ein kleines Shell-Skript:

#!/bin/bash
postme=$1
liste=$2
FHEMIP=<IP-Adresse des FHEM-Servers
echo "{PostMe_tgi('$postme','$liste')}" | socat -t50 - TCP:$FHEMIP:7072

PostMe_tgj(<Devicename>,<Listenname>)

liefert das PostMe Listenname in als "nacktes" Array zurück. Diese Abfrage macht nur Sinn, wenn sie innerhalb eines Perl-Codeblocks ausgeführt wird.

Steuerung via Spracherkennung

Für die folgende Anwendung gehen wir davon aus, dass auf einem FHEM-Gerät eine Spracherkennung installiert ist. Der erkannte Sprachstring wird an eine Perl-Funktion als Parameter $command übergeben. Im Nachfolgenden wird die Auswertung nur für das PostMe Einkaufsliste beschrieben

}elsif( $command =~ /.*(L|l)iste.*/) {
   my $liste;
   if( $command =~ /.*Einkaufs.*/ ){
     $liste = "Einkaufsliste";
   }
   if( $command =~ /.*löschen.*/ ){
     fhem("set PostIt clear ".$liste);
     fhem("set GalaxyTab ttsSay ".$liste." gelöscht");
   }elsif( $command =~ /.*E-Mail.*/ ){
     fhem("get PostIt mail ".$liste);
     fhem("set GalaxyTab ttsSay ".$liste." per Email verschickt");
   }elsif( $command =~ /.*(T|t)eleg.*/ ){
     fhem("get PostIt message ".$liste);
     fhem("set GalaxyTab ttsSay ".$liste." per Telegramm verschickt");
   }elsif( $command =~ /.*(H|h)inzufügen.*/ ){
     $command =~ s/.*(H|h)inzufügen\s+//;
     fhem("set PostIt add ".$liste." ".$command);
     fhem("set GalaxyTab ttsSay Zur ".$liste." hinzugefügt ".$command);
   }elsif( $command =~ /.*(Ä|ä)ndern.*/ ){
     $command =~ s/.*(Ä|ä)ndern\s+//;
     fhem("set PostIt modify ".$liste." ".$command);
     fhem("set GalaxyTab ttsSay Auf ".$liste." geändert ".$command);
   }elsif( $command =~ /.*(E|e)ntfernen.*/ ){
     $command =~ s/.*(E|e)ntfernen\s+//;
     my $res=fhem("set PostIt remove ".$liste." ".$command);
     fhem("set GalaxyTab ttsSay Von der ".$liste." entfernt ".$command);
   }else{
     fhem("get PostIt ttsSay ".$liste);
   }

Einbinden in die FHEM-Anzeige

PostMes können in jede beliebige Webseite eingebunden werden. Beispielsweise kann auch das Standard-Attribut icon des diesem Postme-System zugeordneten Devices durch einen der folgenden Codes ersetzt werden.

  • Mit dem HTML-Code
<embed src="/fhem/PostMe_widget?type=pins&postit=<devicename>"/>

wird eine interaktive Liste aller Titelzeilen der PostMes dieses Systems angezeigt.

  • Mit dem Code
<embed src="/fhem/PostMe_widget?type=pin&postit=<devicename>&name=<name>"/>

wird ein einzelnes PostMe mit dem Namen <name> als Titelzeile angezeigt. Zur Anpassung der Höhe kann man noch ein height-Attribut einsetzen, z.B.:

<embed src="/fhem/PostMe_widget?type=pin&postit=<devicename>&name=<name>" height="350"/>

Art der Widgets

In diesen Darstellungen kann durch die Maus erreicht werden, dass zur einzeiligen Anzeige der Titelzeile der volle Inhalt des PostMes angezeigt wird.

  • Wenn das Attribut postmeClick auf 0 gesetzt ist (Default-Wert), erfolgt die Anzeige und das Löschen des PostMe-Inhaltes durch Überfahren bzw. Wegfahren des Mauszeigers auf die Titelzeile.
  • Wenn das Attribut postmeClick auf 1 gesetzt ist, muss die Titelzeile angeklickt werden, um den Inhalt zu sehen - und das entsprechens Fenster bzw. die Dialogbock explizit geschlossen werden.

Welcher Art die Anzeige des PostMe-Inhalte ist, wird durch ein anderes Attribut geregelt.

  • Wenn postmeStyle=jQuery" (Default-Wert), erfolgt die Anzeige in einer jQuery-Dialogbox.
  • Wenn postmeStyle=HTML", erfolgt die Anzeige in einem neuen (kleinen) Browser-Fenster.
  • Wenn postmeStyle=SVG", erfolgt die Anzeige in einer SVG-Viewbox.

Aussehen der Widgets

Die oben genannten Widgets werden in ihrem Aussehen über Cascading Stylesheets gesteuert. Drei Klassen sind dafür relevant, sie sollten in die Datei style.css (oder, wenn man einen anderen Stylesheet-Präfix gesetzt hat, <präfix>style.css) eingetragen werden. Mit beispielhafter Farbe für die Widgets:

.postmeclass { background-color:#ffee80; padding:10px; border-color:black; border:groove}
.postmeclass2 { background-color:#ffee80; padding:10px;}

Steuerung per Telegram

Mit dem Messenger Telegram kann man Kommandos von außen an FHEM schicken. Dazu pollt der Telegram-Client, der mit Hilfe des TelegramBot-Moduls realisiert wird, in regelmäßigen Abständen einen entsprechenden Server im Internet. Nach derzeitigem Stand der Technik ist das sicher, allerdings ist es empfehlenswert, diesen TelegramBot nicht auf dem gleichen Rechner wie das FHEM-Hauptsystem laufen zu lassen (siehe Anleitung TelegramBot).

Mit einem solchen Kommando kann man z.B. einen Dummy setzen - das löst einen Event aus, der via FHEM2FHEM auch auf entfernten FHEM-Systemen registriert werden kann. Mit einem notify bzw. DOIF kann dieser Event abgefangen und zur Absendung einer Antwort durch das PostMe-Modul verwendet werden:

define Remote.N DOIF ([Task] eq "Einkaufsliste") (get PostIt Einkaufsliste message,set Remote.Task Einkaufsliste Telegram)

Insgesamt führt dies dazu, dass man z.B. einfach von außen an seinen eigenen TelegramBot die Nachricht

 magisches_Codewort set Task Einkaufsliste

senden kann - und kurz darauf die Einkaufsliste zurückbekommt.

Komfortabel verwalten mit Favorites

Komfortabler ist die Bedienung von PostMe mit Telegram, wenn man diesem Messenger so genannte Favorites beibringt - das sind bevorzugte Kommandos. Beim Absenden dieser Kommandos auf einem Smartphone genügt es, die Kommandoabkürzung mit einem vorangestellten "/" zu senden. Definiert man beispielsweise für das Telegram-Device (NICHT für das Postme-Device)

attr Telegram favorites
/H=set Telegram message "/Einkauf <sonstige Listen>"; 
/Einkauf=set Task get PostIt message Einkaufsliste; 
/Einkauf+=set Task set PostIt add Einkaufsliste; 
/Einkauf-=set Task set PostIt remove Einkaufsliste; 

so kann man mit der Telegram-Nachricht

/H

ein auswählbares Menü seiner Listen erhalten, mit

/Einkauf

die Einkaufsliste abrufen und mit

/Einkauf+ <Item>
/Einkauf- <Item> 

Gegenstände hinzufügen oder entfernen. Achtung: Eigentlich ist das eine Sicherheitslücke, weil das magische Codewort nicht nötig ist. Man sollte also erstens die Telegram-User sorgfältig begrenzen, die diese Kommandos ausführen können, und zweitens auf keinen Fall beliebige FHEM-Kommandos über diesen Mechanismus ausführbar machen. Im hier diskutierten Fall (separate FHEM-Instanz und Trennung per FHEM2FHEM, indem hier nur die Task-Variable gesetzt wird) kann das also als sicher gelten.

Noch komfortabler verwalten mit Inline-Keyboards

Info green.pngInzwischen gibt es für diese Funktionalität ein eigenes Modul TBot_List, das die Kopplung von TelegramBot und PostME ohne zusätzlichen perlcode erlaubt


Noch komfortabler ist die Bedienung von PostMe mit Telegram, wenn man diesem Messenger so genannte Inline-Keyboards beibringt. Das geht auf zweierlei Arten, die im Folgenden dokumentiert sind.

Pmbot1.png
Benutzer tippt im Client auf den Button "PostIt". Im FHEM wird über ein notify, das auf queryData triggert, eine Erkennungsroutine aufgerufen, der Benutzer erhält:
Pmbot2.png Pmbot2b.png
Benutzer tippt im Client auf den Button "Einkauf".Im FHEM wird über ein notify, das auf queryData triggert, eine Erkennungsroutine aufgerufen, der Benutzer erhält:
Pmbot3.png Pmbot3b.png
Benutzer tippt im Client auf den Button "Einkauf hinzu".

Im FHEM wird wieder über das notify, das auf queryData triggert, eine Erkennungsroutine aufgerufen. Diese stellt fest, dass etwas hinzugefügt werden soll sendet an den Client eine msgForceReply "Eingabe einzufügender Posten". Im TelegramBot wird ein Userreading gesetzt auf "add Einkaufsliste"

Benutzer tippt im Client auf den Button "hinzufügen".

Im FHEM wird wieder über das notify, das auf queryData triggert, eine Erkennungsroutine aufgerufen. Diese stellt fest, dass etwas hinzugefügt werden soll sendet an den Client eine msgForceReply "Eingabe einzufügender Posten". Im TelegramBot wird ein Userreading gesetzt auf "add Einkaufsliste"

Pmbot4.png
Benutzer tippt im Client einen Gegenstand ein und sendet diesen an den TelegramBot. Dasselbe notify triggert AUCH auf msgReplyMsgId und ruft erneut die Erkennungsroutine, diese weiß aus dem Userreading, was sie zu tun hat und führt aus
set PostIt add Einkaufsliste Gegenstand
Zur Überprüfung tippt der Benutzer erneut auf den Button "Einkauf" und erhält Der Benutzer erhält automatisch
Pmbot5.png Pmbot5b.png

Dazu benötigt man erst einmal, dass die Favorites beim Start eine externe Routine telegramRecognition aufrufen:

attr Telegram favorites /start={telegramRecognition("menuData: Hauptmenü")};

Außerdem das notify, das auf die richtigen Events triggert

define Telegram.N notify Telegram:((queryData)|(msgReplyMsgId)).* {telegramRecognition("$EVENT")}

Die Erkennungsroutine ist recht komplex, sie wird im Folgenden komplett dargestellt:

#############################################################################
#
#  TelegramInlineKeyboard
#
#############################################################################
sub telegramRecognition($){
   my ($event)    = @_;
   my $querypeer      = ReadingsVal("TelegramBot", "queryPeer", 0);
   my $msgpeer        = ReadingsVal("TelegramBot", "msgPeer", 0);
   my $queryReplyMsgId= ReadingsVal("TelegramBot", "queryReplyMsgId", 0);
   my $MsgId          = ReadingsVal("TelegramBot", "MsgId", 0);
   my $menuMsgId      = ReadingsVal("TelegramBot", "menuMsgId", $queryReplyMsgId);
   my $calldata       = ReadingsVal("TelegramBot", "callData", "");
   my $tg;
   my $dp;
   my $dm;
   my $res;
   my $cmd;
   my $click;
   my ($cb1,$cb2,$cb1raw);
   
   #-- Dieses Flag wird =1 gesetzt, wenn das PostMe-Device auf in derselben FHEM-Instanz läuft, 
   #                    =0, wenn das PostMe-Device auf einer anderen FHEM-Instanz läuft und per FHEM2FHEM 
   #                        bedient wird
   my $postitlocal=1;
   #-- Hier kann man - ggf. für jeden $querypeer anders ! - einstellen, welcher Bestätigungstext vom Bot 
       gesendet wird, und ob es eine nicht-klickbare Liste (linke Spalte oben) oder eine klickbare Liste
       (rechte Spalte oben) sein soll
   if( $querypeer eq "Peter_A._Henning"){
      fhem("attr TelegramBot queryAnswerText Gerne zu Diensten, Meister !");
      $click=1;
   }elsif( $querypeer eq "Jacqueline_Henning"){
      fhem("attr TelegramBot queryAnswerText Gerne zu Diensten, Jacqueline !");
      $click=0;
   }else{
      fhem("attr TelegramBot queryAnswerText Gerne zu Diensten!");
      $click=0;
   }
   #-- Klick event from inline keyboard
   if( $event =~ /queryData\:\s(.*)/ ){
     ($cb1,$cb2) = split(/ /,$1,2);
     #-- Level 0/1 => new menuMsgId after start of Bot
     if( $cb1 =~ /(PostIt)|(Steuerung)/ ){
       $menuMsgId = $queryReplyMsgId;
       fhem("setreading TelegramBot menuMsgId $menuMsgId");
     }
     #-- Level 0
     if( $cb1 eq "Hauptmenü"){
       fhem("set TelegramBot queryInline \@$querypeer (PostIt) (Steuerung) Hauptmenü");   
     #-- Level 1
     }elsif( $cb1 eq "PostIt"){
       #-- PostIt-Menü für nicht-klickbare Listen
       if( $querypeer eq "Jacqueline_Henning"){
         fhem("set TelegramBot queryEditInline $menuMsgId \@$querypeer (Hauptmenü) (Einkauf|Einkauf hinzu|Einkauf entf) (Baumarkt|Baumarkt hinzu|Baumarkt entf) PostIt Listenverwaltung;");
       #-- PostIt-Menü für nicht-klickbare Listen
       }elsif( $querypeer eq "Peter_A._Henning"){
         fhem("set TelegramBot queryEditInline $menuMsgId \@$querypeer (Hauptmenü) (Einkauf) (Baumarkt) PostIt Listenverwaltung;");        
       }
     }elsif( $cb1 eq "Steuerung"){
       ... HIER NOCH ANDERE UNTERMENUS FÜR STEUERUNGSZWECKE
     #-- Level 2/3 PostIt
     }elsif( $cb1 =~ /((Einkauf)|(Baumarkt)).*/ ){
       my $cb1raw = $cb1;
       if( $cb1 =~ /Einkauf.*/){
         $cb1 = "Einkaufsliste";
         $tg  = 1;
         $dp  = "<hier Adressaten>";
         #$click = 0;
       }elsif( $cb1 =~ /Baumarkt.*/){
         $cb1 = "Baumarktliste";
         $tg  = 2;
         $dp  = "\<hier Adressaten>";
         #$click = 0;
       }  
       #-- Level 2/3 for clickable items
       if( $click==1 ){
         #-- Level 3 to delete an item
         if( $cb1raw =~ /.*item\d\d/ ){
           $cb2 = $cb1raw;
           $cb2 =~ s/^.*_//;
           if( $postitlocal==1 ){
             #-- local version
             fhem("set PostIt remove $cb1 $cb2");
           }else{
             #-- remote version
             fhem("set Task_90 set PostIt remove $cb1 $cb2");
           }
           #-- redisplay the list - small delay
           InternalTimer(gettimeofday()+1, "telegramRecognition","queryData: $cb1",0);
         #-- Level 3 to add an item
         }elsif( $cb1raw =~ /.*add/ ){
           fhem("set TelegramBot msgForceReply \@$querypeer Eingabe hinzuzufügender Posten");
           fhem("setreading TelegramBot prevCmd add $cb1");
         #-- Level 2 for clickable items
         }else{
           if( $postitlocal==1 ){
             #-- local version
             $res = PostMe_tgi("PostIt",$cb1);  
           }else{  
             #-- remote version
             $cmd = "/opt/fhem/getList.sh PostIt ".$cb1;
             $res = `$cmd`;
           }
           fhem("set TelegramBot queryEditInline $menuMsgId \@$querypeer (Hauptmenü|PostIt|hinzufügen:".$cb1."_add) ".$res."\n Klicken zum Entfernen"); 
         }
       #-- Level 2/3 for non-clickable items
       }elsif( $click==0 ){  
         if( $cb2 ){
           #-- Level 3 to delete an item
           if( $cb2 eq "entf" ){
             fhem("set TelegramBot msgForceReply \@$querypeer Eingabe zu entfernender Posten");
             fhem("setreading TelegramBot prevCmd remove $cb1");
           #-- Level 3 to add an item
           }elsif( $cb2 eq "hinzu" ){
             fhem("set TelegramBot msgForceReply \@$querypeer Eingabe hinzuzufügender Posten");
             fhem("setreading TelegramBot prevCmd add $cb1");
           }
         #-- Level 2 for non-clickable items
         }else{
           if( $postitlocal==1 ){
             #-- local version
             fhem(sprintf("attr PostIt postme%02dMsgRec \@$querypeer",$tg));
             fhem(        "get PostIt  message $cb1");
             fhem(sprintf("attr PostIt postme%02dMsgRec %s",$tg,$dp));
           }else{
             #-- remote version
             fhem(sprintf("set Task_90 attr PostIt postme%02dMsgRec \@$querypeer",$tg));
             fhem(        "set Task_90 get  PostIt message $cb1");
             fhem(sprintf("set Task_90 attr PostIt postme%02dMsgRec %s",$tg,$dp));
           }
         } 
       }
     }
   #-- Level 0 after start of bot
   }elsif( $event =~ /menuData\:\s*(.*)\s*(.*)/ ){
     my $cb1 = $1;
     my $cb2 = $2;
     if( $cb1 eq "Hauptmenü"){
       if( $msgpeer eq "Jacqueline_Henning"){
         fhem("set TelegramBot queryInline \@$msgpeer (PostIt) (Steuerung) Hauptmenü");
       }elsif( $msgpeer eq "Peter_A._Henning"){
         fhem("set TelegramBot queryInline \@$msgpeer (PostIt) (Steuerung) Hauptmenü");
       }else{
         fhem("set TelegramBot queryInline \@$msgpeer (PostIt) Hauptmenü");
       }
     }
   #-- Process line from forced reply
   }elsif( $event =~ /msgReplyMsgId\:\s+(\d*)/ ){
     my $mn = $1;
     my $mo = ReadingsVal("TelegramBot", "prevMsgId", 0)+1;
     my $prev = ReadingsVal("TelegramBot","prevCmd","none");
     if( $prev =~ /((add)|(remove)).*/ ){
       if( $postitlocal==1 ){
         #-- local version
         fhem("set PostIt $prev ".ReadingsVal("TelegramBot","msgText",""));
       }else{
         #-- remote version
         fhem("set Task_90 set PostIt $prev ".ReadingsVal("TelegramBot","msgText",""));
       }
       fhem("setreading TelegramBot prevCmd none");
       #-- redisplay the list - small delay
       $prev =~ s/((add)|(remove))\s*//;
       InternalTimer(gettimeofday()+1, "telegramRecognition","queryData: $prev",0);
     } 
   }
 }