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:

Bildschirmfoto: Nachricht vom Raspberry Pi in der Messenger-App Telegram

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.

Bildschirmfoto: Weboberfläche mit Buttons zum Ausführen von SSH-Befehlen auf dem Raspberry Pi
Mit der hier vorgestellten (einfachen) Lösung dient Telegram lediglich als Empfänger. Um aber Befehle an den Raspberry Pi zu senden, nutze ich OliveTin bzw. ein „Web-Dashboard“ (via VPN auch von unterwegs), nicht jedoch Telegram. Die ginge jedoch theoretisch auch.

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/null

Der 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."
fi

Auch 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.

Kommentar schreiben

Hier gibt es die Möglichkeit für Resonanz. Pflichtfelder sind mit * markiert.

Kommentare erscheinen nicht sofort bzw. werden manuell freigegeben. Mit dem Absenden des Formulars stimmen Sie der Datenschutzerklärung zu bzw., dass Ihre eingegebenen Daten gespeichert werden. IP-Adressen werden dabei grundsätzlich nicht gespeichert.