FHEM Tutorial-Reihe - Part 40: Alles über Mails
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.
Was wird benötigt?
- Eine FHEM-Installation
- Einen oder mehrere Mail-Accounts (gmail / web.de / gmx.de oder andere IMAP-Accounts)
Video
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
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