FHEM Tutorial-Reihe - Part 40: Alles über Mails

Mit ** gekennzeichnete Links auf dieser Seite sind Affiliatelinks.

FHEM Tutorial-Reihe - Part 40: Alles über Mails
FHEM Tutorial-Reihe - Part 40: Alles über Mails
  • Matthias Kleine
  • 04.10.2017
  • Webservice

So, neuer Teil - wieder sehr aufwändig für mich mich zu produzieren, aber wieder sehr einfach umzusetzen. In diesem Video geht es rund um Mails. Also FHEM mit Mails steuern, Stati von Postfächern anzeigen (wieviele ungelesene Mails sind vorhanden) und zuletzt auch Mails aus FHEM heraus versenden.

Achtung: Dieser Inhalt wurde vor mehreren Jahren aufgenommen und ist nicht mehr aktuell! Es ist nicht empfohlen nach dieser Anleitung vorzugehen.

Was wird benötigt?

  • Eine FHEM-Installation
  • Einen oder mehrere Mail-Accounts (gmail / web.de / gmx.de oder andere IMAP-Accounts)

Video

Hausbau-Kurs

Befehle per Mail an FHEM senden

Im ersten Teil lernen wir, wie man FHEM per Mail steuern kann. Dazu legt man sich ein neues Mailpostfach an (welches man sonst nicht verwendet). An diese Mailadresse kann man dann Nachrichten senden. FHEM erstellt dann automatisch Readings mit dem Absender und dem Betreff der letzten Nachricht. Wahlweise wird die Mail dann auch sofort aus dem Postfach gelöscht.

sudo apt-get install libpath-class-perl libhtml-tableextract-perl libemail-simple-perl libnet-imap-simple-perl libnet-imap-simple-ssl-perl libmail-imapclient-perl libmime-tools-perl libemail-mime-perl libemail-mime-attachment-stripper-perl

Alles was man dazu braucht ist das folgende define. Also Hostname, Benutzername und Passwort.

define mail mailcheck server.de benutzername 12345passwort12345

Mailpostfächer überwachen auf neue Mails

Hat man bestehende Accounts, kann man diese ganz einfach per FHEM überwachen lassen. Dazu habe ich die folgende Funktion aus dem FHEM-Forum übernommen und angepasst. Der Code aus dem Forum war nicht sehr sauber und hatte noch viele Fehler - also am besten nicht immer unbedacht Quelltext von anderen übernehmen.

sub CheckMails($$$$$) {
    my ($host, $user, $passwd, $devspec, $ssl) = @_;

    fhem("setreading $devspec connection_host $host");
    fhem("setreading $devspec connection_user $user");
    fhem("setreading $devspec connection_ssl $ssl");

    use Net::IMAP::Simple::SSL;
    use Net::IMAP::Simple;
    use Email::Simple;

    my $imap;

    if ($ssl == 1) {
        $imap = Net::IMAP::Simple::SSL->new($host);

        if (!$imap) {
            fhem("setreading $devspec imap_status Unable to connect to IMAP (SSL): $Net::IMAP::Simple::errstr");
            return 11;
        }
    } else {
        $imap = Net::IMAP::Simple->new($host);

        if (!$imap) {
            fhem("setreading $devspec imap_status Unable to connect to IMAP: $Net::IMAP::Simple::errstr");
            return 12;
        }
    }

    if ($imap->login($user, $passwd)) {
        my ($unreadMessages, $recent, $numMessages) = $imap->status('INBOX');

        fhem("setreading $devspec imap_status connected");
        fhem("setreading $devspec messages_unread $unreadMessages");
        fhem("setreading $devspec messages_total $numMessages");

        $imap->close();
        $imap->logout();

        return 0;
    } else {
        fhem("setreading $devspec imap_status " . $imap->errstr);
        return 13;
    }
}

Nun legt man einfach einen Dummy an. Dieser wird genutzt um Readings des Mailkontos zu speichern.

define meineMails dummy

Jedes mal, wenn nun die Funktion aufgerufen wird, werden die Readings auf dem Gerät aktualisiert. Meiner Meinung nach echt praktisch, da man so beliebig viele Accounts überwachen kann, ohne den Code jedes mal neuschreiben zu müssen.

{CheckMails("server.de", "benutzername", "12345passwort12345", "meineMails", 1)}

Mit einem at kann man die Funktion dann einfach alle 5 Minuten aufrufen, sodass dann auch entsprechend die Informationen aktualisiert werden.

define refreshMails at +*00:05:00 {CheckMails("server.de", "benutzername", "12345passwort12345", "meineMails", 1)}

Mails versenden

Senden ist etwas komplizierter, da erst einmal der SMTP-Server konfiguriert werden muss.

sudo apt-get update
sudo apt-get install ssmtp mailutils
sudo vi /etc/ssmtp/ssmtp.conf

Den Inhalt der Datei komplett löschen und mit diesem ersetzen (natürlich vorher anpassen).

root=fhem@server.de
mailhub=server.de:587
hostname=server.de
FromLineOverride=YES
AuthUser=benutzername
AuthPass=12345passwort12345
UseSTARTTLS=YES

Diese Datei beschreibt, wer welche Benutzer und Server nutzen darf.

sudo vi /etc/ssmtp/revaliases
root:fhem@server.de:server.de:587
pi:fhem@server.de:server.de:587
fhem:fhem@server.de:server.de:587

Zum testen kann dann einmal der folgende Befehl abgesetzt werden. Bitte die Mailadresse am Ende gegen Eure eigene austauschen, ansonsten bekomme ich viel Spam.

echo "Hallo vom Raspberry" | mail -s "Betreff" info@haus-automatisierung.com

Da der Absender nun noch etwas kryptisch ist, passen wir dies in der passwd an wie im Video gezeigt.

sudo vi /etc/passwd

Im FHEM fehlt nun nur noch die folgende Funktion um Mails zu versenden.

sub SendMail($$$) {
    my ($subject, $message, $recipient) = @_;

	system("echo \"$message\" | mail -s \"$subject\" $recipient");
}

Hier auf die einfachen Anführungszeichen achten. Alternativ kann das @ auch mit einem Backslash escaped werden. Der Code kann nun ganz einfach überall verwendet werden. Egal ob im DOIF oder notify, oder in eigenen Funktionen.

{SendMail('FHEM', 'Nachricht', 'info@haus-automatisierung.com')}
{SendMail("FHEM", "Nachricht", "info\@haus-automatisierung.com")}

Erweiterung: FritzBox-Mails vom Anrufbeantworter

Bitte zuvor alle Pakete von oben installieren

Der Ablauf:

  • Jemand ruft an und spricht eine Nachricht auf den AB
  • Die FritzBox sendet eine Mail mit Infos und WAV-Datei an Mailpostfach
  • FHEM überwacht dieses Postfach auf ungelesene Mails
  • FHEM holt die Informationen aus der Mail, legt sie in einem dummy und speichert den Anhang ab
  • Die Datei wird in ogg gewandelt, damit man diese in Telegram nutzen kann
  • Die Mail wird von FHEM als gelesen markiert
Hausbau-Kurs
apt-get install ffmpeg
sudo mkdir /opt/fhem/mails/
sudo chown -R fhem:dialout /opt/fhem/mails/
define FritzBoxTam dummy
attr FritzBoxTam alias FritzBox (Anrufbeantworter)
attr FritzBoxTam event-on-change-reading .*
attr FritzBoxTam icon phone_call_end_in
attr FritzBoxTam stateFormat messages_total Nachrichten
sub CheckTam($$$$$) {
    my ($host, $user, $passwd, $devspec, $ssl) = @_;
    my $mailDir = '/opt/fhem/mails/';

    fhem("setreading $devspec connection_host $host");
    fhem("setreading $devspec connection_user $user");
    fhem("setreading $devspec connection_ssl $ssl");
    fhem("setreading $devspec attachment_dir $mailDir");

    use Path::Class;
    use Net::IMAP::Simple::SSL;
    use Net::IMAP::Simple;
    use Email::Simple;
    use Email::MIME;
    use Email::MIME::Attachment::Stripper;
    use HTML::TableExtract;

    my $imap;

    if ($ssl == 1) {
        $imap = Net::IMAP::Simple::SSL->new($host);

        if (!$imap) {
            fhem("setreading $devspec imap_status Unable to connect to IMAP (SSL): $Net::IMAP::Simple::errstr");
            return 11;
        }
    } else {
        $imap = Net::IMAP::Simple->new($host);

        if (!$imap) {
            fhem("setreading $devspec imap_status Unable to connect to IMAP: $Net::IMAP::Simple::errstr");
            return 12;
        }
    }

    if ($imap->login($user, $passwd)) {
        my ($unreadMessages, $recent, $numMessages) = $imap->status('INBOX');

        fhem("setreading $devspec imap_status connected");
        fhem("setreading $devspec messages_total $numMessages");

        my $cnt = 1;

        if ($unreadMessages > 0) {
            Log3($devspec, 3, "Unseem mails found - removing all readings");
            fhem("deletereading $devspec message-.*");
        }

        my $index = $imap->list();
        foreach my $i (keys %$index) {
            if (!$imap->seen($i)) {
                Log3($devspec, 3, "Processing unseen message $i / $numMessages ($unreadMessages unread)");

                my $msg = $imap->get($i);

                if ($msg) {
                    my $body = join('', @$msg);
                    my $email = Email::Simple->new($body);

                    my %m = $email->header_pairs;
                    my $subject = Encode::decode('MIME-Header', $m{Subject});
                    my $from = Encode::decode('MIME-Header', $m{From});

                    fhem("setreading $devspec message-$cnt-mail-uid $i");
                    fhem("setreading $devspec message-$cnt-mail-subject $subject");
                    fhem("setreading $devspec message-$cnt-mail-from $from");
                    #fhem("setreading $devspec message-$cnt-mail-msg $msg");

                    my $st = Email::MIME::Attachment::Stripper->new($body);
                    if ($st->attachments) {
                        for my $attach ($st->attachments) {
                            my $fn = $attach->{filename};
                            next unless defined $fn;

                            my $data = $attach->{payload};

                            # if noname, its a msg body
                            if (!$fn) {
                                my $te = HTML::TableExtract->new(depth => 2, count => 2);
                                $te->parse($data);

                                # Examine all matching tables
                                foreach my $ts ($te->tables) {
                                    fhem("setreading $devspec message-$cnt-content-by " . $ts->cell(0, 1));
                                    fhem("setreading $devspec message-$cnt-content-for " . $ts->cell(1, 1));
                                    fhem("setreading $devspec message-$cnt-content-date " . $ts->cell(2, 1));
                                    fhem("setreading $devspec message-$cnt-content-time " . $ts->cell(3, 1));
                                    fhem("setreading $devspec message-$cnt-content-length " . $ts->cell(4, 1));

                                    my $phoneNumber = ReadingsNum($devspec, "message-$cnt-content-by", 0);
                                    my $searchResult = fhem("get TYPE=FB_CALLMONITOR search " . $phoneNumber);
                                    if ($searchResult) {
                                        fhem("setreading $devspec message-$cnt-content-by-name " . $searchResult);
                                    }
                                }

                            } else {
                                fhem("setreading $devspec message-$cnt-mail-attachment $fn");
                                fhem("setreading $devspec message-$cnt-mail-attachment-path $mailDir$fn");

                                my $file = file($mailDir, $fn);

                                open(my $ff, '>', $file);
                                binmode($ff);
                                print $ff $data;
                                close($ff);

                                if ($fn =~ ".wav") {
                                    system("ffmpeg -i $mailDir$fn -ac 1 -map 0:a -codec:a opus -b:a 128k -vbr off -ar 24000 $mailDir$fn.ogg");
                                    fhem("setreading $devspec message-$cnt-mail-attachment-path-ogg $mailDir$fn.ogg");
                                }
                            }
                        }
                    }

                    $imap->see($i);
                    $cnt++;
                }
            }
        }

        $imap->close();
        $imap->logout();

        return 0;
    } else {
        fhem("setreading $devspec imap_status " . $imap->errstr);
        return 13;
    }
}

Danach sind viele Szenarien denkbar:

  • FHEM übergibt die Audiodatei an SONOS
  • FHEM sendet die Audiodatei per Telegram

Test:

{CheckTam("server.de", "benutzername", "12345passwort12345", "FritzBoxTam", 1)}

set WEB_Telegram sendVoice @12345678 [FritzBoxTam:message-1-mail-attachment-path-ogg]
define doif_FritzBox_AnrufbeantworterTelegram DOIF ([FritzBox:tam2_newMsg] > 0) ({CheckTam("server.de", "benutzername", "12345passwort12345", "FritzBoxTam", 1)}) ([FritzBoxTam:message-1-mail-attachment-path-ogg] =~ ".ogg") (set WEB_Telegram msg @12345678 Anruf von [FritzBoxTam:message-1-content-by] um [FritzBoxTam:message-1-content-time] auf dem Anrufbeantworter, set WEB_Telegram sendVoice @12345678 [FritzBoxTam:message-1-mail-attachment-path-ogg])
attr doif_FritzBox_AnrufbeantworterTelegram alias FritzBox: Anrufbeantworter überwachen und Nachrichten per Telegram
attr doif_FritzBox_AnrufbeantworterTelegram do always
attr doif_FritzBox_AnrufbeantworterTelegram wait 10:0
Du willst mehr?

Smart-Home-Trainings von A-Z

Steig noch tiefer in die Themen ein und meistere Deine Projekte! Über 13.000 Teilnehmer konnten sich schon von der Qualität der Online-Kurse überzeugen.

ioBroker-Master-Kurs

ioBroker-Master-Kurs

Mehr Infos
Hausbau-Kurs

Hausbau mit KNX

Mehr Infos
Lox-Kurs

Lox-Kurs

Mehr Infos
Node-RED-Master-Kurs

Node-RED-Master-Kurs

Mehr Infos