stets auf dem neuesten Stand
Den Raspberry Pi Nachrichten an Telegram senden lassen
In diesem Beitrag demonstriere ich, wie mir mein Raspberry Pi jeden Morgen einen „Morgengruß“ via Telegram sendet mit diversen Informationen. Dies ist tatsächlich relativ einfach zu bewerkstelligen und kann auch für Warnmeldungen (z. B. erhöhte Temperatur) oder Fehlermeldungen im Log genutzt werden.
Telegram ist ein sehr interessanter Messenger-Dienst. Denn dort können auch Roboter schreiben. Es ist ziemlich einfach, sich mittels der Telegram-App für den eigenen Computer bzw. Raspberry Pi einen sogenannten „Bot“ zu erstellen, welcher einem selbst dann hin und wieder Nachrichten zukommen lässt. Mein Raspberry Pi benötigte hierfür keine neue Software und es musste dort auch nichts eingerichtet werden.
Der tägliche Morgengruß schaut bei mir in der Telegram-App dann so aus:

Dieser Bot erhält dann einen eigenen Bot-Token und das „Gespräch“ mit diesem eine Chat-ID. Beide Werte werden dann in einem Skript (s. u.)integriert, welches regelmäßig per Cronjob ausgeführt wird. Fertig.
Zunächst legen wir innerhalb von Telegram diesen ›Bot‹ an:
Schritt 1: Bot anlegen und Chat-ID auslesen
Innerhalb der Telegram-App suchen wir den „Nutzer“ @BotFather. Dies ist ein offizieller Telegram-Dienst, mittels welchem man diverse Bots anlegen- bzw. verwalten kann. Man startet dort nun einen Chat („Start“).
Man gibt nun den Befehl „/newbot“ ein bzw. klickt auf den entsprechenden Link und folgt den Anweisungen, die einem der BotFather gibt. Man gibt seinem Bot einen internen Namen z. B. „Raspberry Pi“. Danach muss man dem Bot einen externen Namen geben, z. B. „irgendwas_raspberry_bot“ (der Name muss auf „_bot“ enden). Da jeder externe Bot-Name einzigartig ist, muss man hier etwas herum experimentieren, was noch frei ist.
Nun wird der eigene Bot erstellt und man erhält den dazugehörigen Bot-Token. Diesen sollte man sich gut abspeichern. Man kann ihn aber später jederzeit wieder im Konfigurationsmenü von „@BotFather“ einsehen.
Als zweiten Wert für unser späteres Skript benötigen wir noch die Chat-ID. Irgendwo im „Gespräch“ mit dem @BotFather findet sich ein Link zum Chat mit dem eigenen, eben erstellten Bot (bei mir ist das Anlegen schon etwas länger her, daher weiß ich das nicht mehr so genau). Dort klickt man drauf, dann auf „Start“ und schreibt seinem Bot erst einmal nacheinander zwei Nachrichten. Dies dient nur zur Aktivierung.
Nun ruft man diesen Link im Browser auf: https://api.telegram.org/botBOT-TOKEN/getUpdates
„BOT-TOKEN“ ersetzt man natürlich mit dem eigenen. Danach wird man im Browser etwas Code sehen. Relevant ist hier nur eine Stelle wie {"id":1234567890,. Es geht hier also um die id. Diese Ziffernfolge notieren wir uns nun. Falls man dies nicht sieht, schreibt man noch einmal eine Nachricht an den Bot und versucht es noch einmal.
Schritt 2: Via Konsole testen
Nachdem wir nun den Bot-Token für unseren Bot haben und auch die korrekte Chat-ID, können wir am Raspberry Pi in der Konsole eine Nachricht an unseren eigenen Telegram-Account senden:
curl -X POST 'https://api.telegram.org/botXXXXX/sendMessage?chat_id=XXXXX&text=hallo'
Bot-Token und Chat-ID muss man hier im Code natürlich korrekt eintragen.
Das Ganze geht auch direkt als URL in jedem beliebigen Browser:
https://api.telegram.org/botXXXXX/sendMessage?chat_id=XXXXX&text=hallo
Interessant, oder? Somit könnten einem sogar Personen ohne Telegram Nachrichten schicken.
Schritt 3: Skript ausführen
Was man über die Konsole absenden kann, kann man – deutlich komplexer – natürlich auch in ein Skript verpacken und an seinen Telegram-Bot senden:
Telegram Guten-Morgen-Skript
#!/bin/bash
# --- 0. KONFIGURATION ---
# Telegram
BOT_TOKEN="XXXXX"
CHAT_ID="XXXXX"
# Wetter (OpenWeatherMap)
OWM_API_KEY="XXXXX"
CITY="Leipzig"
COUNTRY="DE"
# Kalender
CAL_URL_1="https://kalenderserver.XXXXX.de/dav.php/calendars/user/default/?export"
CAL_URL_2="https://kalenderserver.XXXXX.de/dav.php/calendars/user/feiertage/?export"
CAL_USER="user"
CAL_PASS="passwort"
# NEWS-FEED
RSS_URL="https://www.tagesschau.de/infoservices/alle-meldungen-100~rss2.xml"
# Verhindert unendliches Warten bei Netzwerkproblemen
CURL_SAFE="curl -s --connect-timeout 10 --max-time 20"
# --- 1. DATUM UND ZEIT ---
# Formatiert das Datum für die Begrüßung
DATUM_STR=$(LC_TIME=de_DE.UTF-8 date +"%A, der %d. %B")
TODAY_CAL=$(date +"%Y%m%d")
# --- 2. WETTERDATEN (OpenWeatherMap) ---
# Wir rufen die 5-Tage-Vorhersage ab (enthält auch aktuelle Trends für heute)
WEATHER_JSON=$($CURL_SAFE "https://api.openweathermap.org/data/2.5/forecast?q=${CITY},${COUNTRY}&appid=${OWM_API_KEY}&units=metric&cnt=8")
# Prüfung auf gültige API-Antwort
if echo "$WEATHER_JSON" | jq -e . >/dev/null 2>&1; then
# Aktuelle Temperatur aus dem ersten Eintrag der Liste (nächster Messpunkt)
CUR_TEMP=$(echo "$WEATHER_JSON" | jq '.list[0].main.temp')
OUT_TEMP=$(LC_NUMERIC=C printf "%.1f" "$CUR_TEMP")
# Maximale Tagestemperatur aus den nächsten 8 Vorhersage-Zeitpunkten (ca. 24h)
W_TEMP_MAX=$(echo "$WEATHER_JSON" | jq '.list[].main.temp_max' | sort -nr | head -n1)
W_TEMP_MAX_FMT=$(LC_NUMERIC=C printf "%.1f" "$W_TEMP_MAX")
# Regen-Check
if echo "$WEATHER_JSON" | jq '.list[].weather[].main' | grep -qi "Rain"; then
W_RAIN_HINT="Es ist Regen angesagt."
else
W_RAIN_HINT="Es bleibt heute voraussichtlich trocken."
fi
WEATHER_SECTION="<b>Wetterprognose:</b>
Heute werden bis zu <b>${W_TEMP_MAX_FMT} °C</b> erwartet.
${W_RAIN_HINT}"
else
OUT_TEMP="--"
WEATHER_SECTION="<b>Wetterprognose:</b>
<i>Daten aktuell nicht verfügbar.</i>"
fi
# --- 3. KALENDERDATEN (WebDAV) ---
fetch_calendar() {
local url=$1
$CURL_SAFE -u "$CAL_USER:$CAL_PASS" "$url" | awk -v today="$TODAY_CAL" '
/^BEGIN:VEVENT/ { sum=""; is_today=0 }
/^SUMMARY:/ { sum=substr($0, 9) }
/^DTSTART/ { if ($0 ~ today) is_today=1 }
/^END:VEVENT/ { if (is_today && sum) print sum }
'
}
# Daten abrufen und HTML-kritische Zeichen maskieren
CAL_DATA=$(printf "%s\n%s" "$(fetch_calendar "$CAL_URL_1")" "$(fetch_calendar "$CAL_URL_2")" | sed '/^[[:space:]]*$/d; s/\r//g' | sed 's/&/\&/g; s/</\</g; s/>/\>/g')
if [ -z "$CAL_DATA" ]; then
CAL_SECTION="Kein Kalendereintrag für heute"
else
COUNT=$(echo "$CAL_DATA" | grep -c "^")
[ "$COUNT" -eq 1 ] && TITLE="Kalendereintrag heute:" || TITLE="Kalendereinträge heute:"
CAL_SECTION="<b>$TITLE</b>
$CAL_DATA"
fi
# --- 4. HISTORISCHES (Wikipedia On-this-day) ---
WP_MONTH=$(date +"%m")
WP_DAY=$(date +"%d")
WP_JSON=$($CURL_SAFE "https://api.wikimedia.org/feed/v1/wikipedia/de/onthisday/selected/${WP_MONTH}/${WP_DAY}")
if echo "$WP_JSON" | jq -e '.selected[0]' >/dev/null 2>&1; then
WP_EVENT=$(echo "$WP_JSON" | jq -r '.selected[0].text' | sed 's/&/\&/g; s/</\</g; s/>/\>/g')
WP_YEAR=$(echo "$WP_JSON" | jq -r '.selected[0].year')
HIST_SECTION="<b>Historisches:</b>
Heute vor $(( $(date +%Y) - WP_YEAR )) Jahren ($WP_YEAR): $WP_EVENT"
else
HIST_SECTION=""
fi
# --- 5. NACHRICHTEN-FEED (RSS) ---
RAW_NEWS=$($CURL_SAFE "$RSS_URL")
if [ -n "$RAW_NEWS" ]; then
NEWS_TITLES=$(echo "$RAW_NEWS" | grep -oP '(?<=<title>).*?(?=</title>)' | sed 's/<!\[CDATA\[//g; s/\]\]>//g; s/&/\&/g; s/</\</g; s/>/\>/g' | head -n 6 | tail -n 5)
NEWS_LINKS=$(echo "$RAW_NEWS" | grep -oP '(?<=<link>).*?(?=</link>)' | head -n 6 | tail -n 5)
NEWS_SECTION="<b>Neueste Meldungen:</b>"
for i in {1..5}; do
TITLE=$(echo "$NEWS_TITLES" | sed -n "${i}p")
LINK=$(echo "$NEWS_LINKS" | sed -n "${i}p")
if [ -n "$TITLE" ]; then
NEWS_SECTION="${NEWS_SECTION}
<a href='${LINK}'>${TITLE}</a>
"
fi
done
else
NEWS_SECTION="<b>Neueste Meldungen:</b>
<i>Feed aktuell nicht erreichbar.</i>"
fi
# --- 6. NACHRICHT FORMATIEREN & VERSAND ---
MSG="<b>Guten Morgen</b>,
heute ist <b>${DATUM_STR}</b>. In ${CITY} sind es aktuell <b>${OUT_TEMP} °C</b>.
${WEATHER_SECTION}
${CAL_SECTION}
${HIST_SECTION}
${NEWS_SECTION}"
# Finaler Versand
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
--data-urlencode "chat_id=${CHAT_ID}" \
--data-urlencode "text=${MSG}" \
--data-urlencode "parse_mode=HTML" > /dev/nullDer Code ist recht übersichtlich gestaltet. Dinge, die man nicht braucht, kann man entfernen (die jeweiligen Blöcke). Die Wenigsten werden nämlich Zugriff auf einen eigenen CalDAV-Kalender haben. Für die Wetterdaten wird ein kostenloser API-Key für OpenWeatherMap benötigt. Den RSS-Feed (Nachrichten) kann man natürlich frei wählen. Der kleine Bereich mit einem historischen Ereignis stammt von Wikipedia („Was geschah heute vor x Jahren?“).
Noch zwei Hinweise zum Wetter: Das Programm „jq“ muss für die Wetterdaten vorhanden sein (bei meinem DietPi offenbar standardmäßig vorhanden). Bei Städten mit Umlauten (z. B. München) ist es oft sicherer, „Muenchen“ zu schreiben oder direkt die „CITY_ID“ bzw. Geokoordinaten zu nutzen.
Natürlich könnte man hier noch viele weitere Dinge unterbringen – etwa die aktuelle Temperatur des Raspberry Pi oder dessen RAM-Auslastung. Bei mir geht es hier aber lediglich um einen täglichen „Morgengruß“. Zu viele Informationen sollten hier auch nicht integriert werden.
Aufmerksame Leser werden auf meinen oben abgebildeten Bildschirmfoto die Zeile mit der Luftfeuchtigkeit bemerkt haben. Hier nutze ich für die aktuelle Temperatur / Luftfeuchte tatsächliche Sensordaten meines Bluetooth-Sensors auf dem Balkon. Dies Funktionalität habe ich hier im Code natürlich nicht berücksichtigt. Aber dies zeigt sehr schön, wie man so eine Telegram-Meldung für eigene Smart-Home-Geschichten nutzen kann.
➜ Achso: Ausgeführt wird das Skript natürlich jeden Morgen automatisch via Cronjob.
Log-Fehlmeldungen per Telegram empfangen
Typischerweise läuft so ein Raspberry Pi permanent und erledigt automatisiert so manche Aufgabe. Da wäre es doch schön, wenn man bei Fehlern automatisch via Telegram benachrichtigt wird, wenn es irgendwo hakt.
Dies erledigt ein weiteres Skript, welches ich bei mir jede 30 Minuten automatisch via Cronjob ausführe:
Telegram Log-Warn-Meldung
#!/bin/bash
# --- KONFIGURATION ---
# Zeitraum in Minuten, der rückwirkend geprüft werden soll
CHECK_PERIOD_MINUTES=30
# Schon ab Fehlerstufe 3 (3: err, 2: crit, 1: alert, 0: emerg)
LOG_PRIORITY=3
# Schlagworte, bei denen das Skript IMMER feuert
KEYWORDS="timeout|unreachable|failed|refused"
# Schlagworte, die das Skript KOMPLETT IGNORIERT (Grundrauschen)
# Mehrere Begriffe mit | trennen, z.B. "kex_protocol_error|another_error"
EXCLUDE_KEYWORDS="kex_protocol_error|COMMAND="
# Zeichen-Sicherheitsgrenze für Telegram (Limit ist 4096 Zeichen)
MAX_CHARS=3500
# Telegram Zugangsdaten
BOT_TOKEN="XXXXX"
CHAT_ID="XXXXX"
# Zeitstempel für journalctl berechnen
SINCE_TIME="$(date -d "${CHECK_PERIOD_MINUTES} minutes ago" '+%Y-%m-%d %H:%M:%S')"
# 1. Rohdaten abfragen
ERRORS=$(sudo journalctl --since "$SINCE_TIME" -p "$LOG_PRIORITY" --no-pager -o short-precise 2>/dev/null)
KEYWORD_HITS=$(sudo journalctl --since "$SINCE_TIME" --no-pager -o short-precise 2>/dev/null | grep -Ei "$KEYWORDS")
# 2. Ergebnisse zusammenführen, Dubletten entfernen und Ausschluss-Filter anwenden
# grep -Ev filtert alle Zeilen heraus, die die EXCLUDE_KEYWORDS enthalten
COMBINED_LOGS=$(echo -e "${ERRORS}\n${KEYWORD_HITS}" | \
grep -v "^--" | \
grep -Ei -v "$EXCLUDE_KEYWORDS" | \
sort -u | \
grep . )
# 3. Prüfung und Aufbereitung
if [ -n "$COMBINED_LOGS" ]; then
NL=$'\n'
MSG="<b>Achtung: Es gibt kritische Einträge im Log</b>${NL}Zeitraum: Letzte ${CHECK_PERIOD_MINUTES} Min.${NL}${NL}"
LOG_CONTENT=""
LAST_MSG=""
while read -r line; do
TIMESTAMP=$(echo "$line" | awk '{print $2}' | cut -d'.' -f1)
IDENTIFIER=$(echo "$line" | awk '{print $4}' | tr -d ':')
MESSAGE=$(echo "$line" | cut -d' ' -f5-)
if [ "$MESSAGE" == "$LAST_MSG" ]; then
continue
fi
LAST_MSG="$MESSAGE"
MESSAGE=$(echo "$MESSAGE" | sed 's/&/\&/g; s/</\</g; s/>/\>/g')
NEW_LINE="<code>$TIMESTAMP</code> <b>$IDENTIFIER</b>: $MESSAGE${NL}"
TOTAL=$(( ${#MSG} + ${#LOG_CONTENT} + ${#NEW_LINE} ))
if [ "$TOTAL" -gt "$MAX_CHARS" ]; then
LOG_CONTENT="${LOG_CONTENT}${NL}<b>... (weitere Logs gekürzt)</b>"
break
fi
LOG_CONTENT="${LOG_CONTENT}${NEW_LINE}"
done <<< "$COMBINED_LOGS"
FULL_MSG="${MSG}${LOG_CONTENT}"
RESPONSE=$(curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-d "chat_id=${CHAT_ID}" \
-d "parse_mode=HTML" \
--data-urlencode "text=${FULL_MSG}")
if [[ "$RESPONSE" == *"\"ok\":true"* ]]; then
echo "Erfolg: Fehlerbericht an Telegram gesendet."
else
echo "Fehler: Telegram-API meldet Problem: $RESPONSE"
fi
else
echo "Status: Keine relevanten Fehler seit $SINCE_TIME gefunden."
fiAuch hier muss man natürlich den jeweiligen ›Bot-Token‹ und die ›Chat-ID‹ oben eintragen. Das Skript berücksichtigt alle Log-Einträge der letzten 30 Minuten (dies kann man ändern). Daher sollte es (hier) auch nur alle 30 Minuten per Cronjob aufgerufen werden – Ansonsten würde man die selben Fehlermeldungen ja doppelt in mehreren Nachrichten erhalten.
- Es berücksichtigt die Stufen Error, Critical, Alert, Emergency – alles darunter natürlich nicht. Auch dies kann man ändern.
- Unabhängig davon feuert das Skript auch bei Begriffen wie timeout, unreachable, failed, refused. Dies kann man oben ebenso definieren.
- Zudem gibt es eine „Whitelist“: Das Skript feuert dann bei bestimmten Begriffen nicht. Dies hatte ich deswegen einbauen lassen (von der KI), weil es bei meinem Pi (einige Ports sind via Router nach Außen ins Internet freigegeben) zu einem gewissen „Grundrauschen“ kommt bzw. um automatisierte Verbindungsversuche von Bots. Offenbar ist dies normal. Diese scheitern natürlich und irgendwann würde eh fail2ban greifen (hoffentlich). Aber ich möchte nicht jeden Tag darüber informiert werden, wenn jemand anklopft.
Gibt es in diesem Zeitraum keine neuen Einträge dieser Art im Log (journalctl), erfolgt natürlich keine Meldung an Telegram. Hinweis: Nach einem Neustart des Raspberry Pi werden vermutlich neue Fehlermeldungen eintreffen. Dies ist aber normales „Rauschen“. Beispielsweise habe ich bei mir kein HDMI-Kabel bzw. keinen Monitor angeschlossen und dies ergibt dann halt eine „Warnung“.
Man kann durchaus einige Test-Fehler im Log via Konsole provozieren, um Das Skript zu testen:
Test-Eintrag via Priorität (Error):
logger -p user.err "Dies ist ein Test-Fehler fuer das Telegram-Skript"
Test-Eintrag via Schlagwort:
logger "Die Verbindung zum Server ist fehlgeschlagen: timeout"
Nach manuellem Aufruf des Skriptes, sollte man darüber also Meldungen in Telegram erhalten.
Wenn man via Cronjob regelmäßig (andere) Skripte ausführt, deren evtl. Fehler geloggt werden sollen, sollte man diese in der Crontab nach diesem Schema anlegen:
* * * * * /bin/bash -c 'set -o pipefail; /bin/bash /home/pi/00_Skripte/mein_skript.sh 2>&1 >/dev/null | /usr/bin/logger -t "Mein-Skript" -p user.err'
Dann werden solche Fehler dabei auch berücksichtigt.
Ich nutze anstelle der Crontab das sehr schöne Programm Cronicle zur Automatisierung. Hier sähe ein entsprechender Befehl so aus:
set -o pipefail; bash /home/pi/00_Skripte/mein_skript.sh 2>&1 >/dev/null | logger -t "Mein-Skript" -p user.err
In beiden Fällen landen dortige Fehlermeldungen ebenfalls in ›journalctl‹ und werden durch das Melde-Skript regelmäßig und frisch an Telegram übergeben.
Da Telegram pro Nachricht nur eine bestimmte Anzahl an Zeichen zulässt, wurde diese Grenze im Melde-Skript bereits berücksichtigt (konfigurierbar). Es werden dort auch Dubletten übersprungen. Man möchte primär ja nur über Fehler per se informiert werden.
In meinem Fall handelt es sich primär um Backup-Skripte. Falls also ein solches Backup fehlschlägt (warum auch immer), werde ich via Telegram darüber informiert. Eine schöne Sache ist das.
