Bedienung der Rollladensteuerung über einen Telegram-Bot mit Raspberry Pi
Vorwort
Ziel dieses Teilprojektes war es, meine Rollladensteuerung über ein Smartphone bedienen zu können. Die Entwicklung einer eigenen Smartphone-APP sowie der dazugehörigen API für den Steuercomputer erschien für den gewünschten Funktionsumfang zu aufwendig. So kam mir die Idee, einen bekannten Messenger-Dienst als Smartfone-App zu nutzen. Der Steuercomputer braucht so, nur die gesendeten Nachrichten auszuwerten und in die Befehle für die Rollladensteuerung umzusetzen. Der Messenger "Telegram" ist für diese Aufgabe gerade zu prädestiniert. Die Entwickler erlauben explizit eine "nicht menschliche" Nutzung.Vorbereitungen auf dem Smartphone
Zuerst muss natürlich die Messenger-App installiert werden. Dazu einfach im jeweiligen App-Store nach "Telegram" suchen und die App installieren. Danach muss nur noch der Bot erzeugt werden. Lustigerweise geschieht dies über einen anderen (von Telegram betriebenen) Bot.Nach dem Starten der Telegram-App sind folgende Schritte notwendig:
Suchfunktion öffnen |
1. "BotFather" suchen 2. und hinzufügen |
BotFather neu starten |
mit dem Befehl "/newbot" einen eigenen Bot erzeugen |
Bot-Namen vergeben |
User-Namen vergeben |
1. Token merken 2. Bot hinzufügen |
Bot öffnen |
Bot starten |
Ab jetzt ist der eigene Bot im Prinzip betriebsbereit. Wichtig hierbei ist, das vergebene Token (Bild7). Dieses wird später im Script des Raspberrys benötigt. Sollte das Token einmal vergessen worden sein - keine Panik. Man kann es sich über den "Botfather" immer wieder anzeigen lassen. Dazu einfach den Befehl "/mybots" an den Botfather senden. Nach der Auswahl des eigenen Bots ist das Token abrufbar. In diesem Menü sind auch diverse andere Einstellungen für den eigenen Bot möglich.
z.B.
Vorbereitung der Hardware
Hier kommt ein "Raspberry Pi 3 Model B" zum Einsatz. Aber auch die älteren Modelle sollten hier noch gut funktionieren.An den Raspberry ist folgende Peripherie angeschlossen:
- Spannungsversorgung an Pin2(+5V) und Pin3(GND)
- LED über Vorwiderstand an Pin5 der 40-poligen Stiftleiste - (GPIO9)
- Taster über Schutzwiderstand gegen Masse sowie Pullupwiderstand an Pin7 - (GPIO7)
- TTL UART zu RS232-Adapter an Pin8(TX) und Pin10(RX) - (GPIO15 und GPIO16)
Erfolgt die Spannungsversorgung direkt über die 40-polige Stiftleiste, wird dabei eine auf der Raspberry-Plaine aufgelötete Schmelzsicherung überbrückt. Wer das mit seinem Gewissen nicht vereinbaren kann, sollte eine externe Sicherung in die Verbindung zu Pin2 der Stiftleiste vorsehen. Ich sah dazu jedoch keine Veranlassung. Der TTL UART zu RS232 Adapter ist identisch mit dem in der Rollladensteuerung beschriebenen Adapter. D.h. aber auch, dass für die Verbindung zwischen Raspberry Pi und der Rollladensteuerung ein Nullmodemkabel benötigt wird.
Vorbereitung des Raspberry Pi
Der Raspberry Pi wird in der Regel ohne SD-Karte ausgeliefert. D.h. Das Betriebssystem muss selbst installiert werden. Die folgende Anleitung beschreibt die Installation und Konfiguration des Betriebssystems. Die Anleitung ist für das Betriebssystem Rasbian in der Version "Stretch Lite" vom 07.09.2017 geschrieben (aktuelle Version zum Zeitpunkt der Anleitungserstellung).Damit ist der Raspberry im Prinzip schon betriebsbereit. Für die weiteren Schritte sind ein Monitor, eine Tastatur und eine Internetverbindung notwendig. Nach dem ersten Login (user: "pi" und password: "raspberry" (ggf. "raspberrz" wegen per default englischer Tastatur)) sollten ein paar Grundeinstellungen am Raspberry vorgenommen werden.
Dazu folgenden Befehl in der Komandozeile ausführen:
sudo raspi-config
Es öffnet sich ein Tool zum Konfigurieren des Raspberrys in dem ich folgende Einstellungen getätigt habe:- update this tool to the latest version (Tool startet nach einer Weile von selbst neu.)
- Localisation Options -> Change Keyboard Layout -> Generic 105-key (Intl) PC -> Other -> German -> German -> Right Alt (AltGr) -> No compose key
- Change User Password
- Network Options -> Hostname
- Localisation Options -> Change Locale -> de_DE.UTF-8 UTF-8
- Localisation Options -> Change Timezone -> Europe -> Berlin
- Localisation Options -> Change Wifi Country -> DE Germany
- Interfacing Options -> Camera -> enabled? -> No
- Interfacing Options -> SSH -> enabled? -> Yes
- Interfacing Options -> VNC -> enabled? -> No
- Interfacing Options -> SPI -> enabled? -> No
- Interfacing Options -> I2C -> enabled? -> No
- Interfacing Options -> Serial -> shell? -> No -> hardware enabled? -> Yes
- Interfacing Options -> 1-Wire -> enabled? -> No
- Interfacing Options -> Remote GPIO -> over network? -> No
- Advanced Options -> Expand Filesystem
- Finish
Sollte der Raspberry nach dem Beenden des Tools nicht schon von selbst neu starten, folgenden Befehl ausführen:
sudo reboot
Nach dem Neustart wird der Raspberry mit folgenden Befehlen auf den neuesten Stand gebracht:
sudo apt-get update sudo apt-get -d upgrade sudo apt-get -y upgrade
Nochmals neu starten mit:
sudo reboot
Firmware updaten und danach neu starten mit:
sudo rpi-update sudo reboot
nur beim (ab?) Raspberry Pi 3
Der Raspberry Pi 3 Model B verfügt über ein Bluetooth-Modul, welches an die Hardware-UART-Schnittstelle (ttyAMA0) des Prozessors angeschlossen ist. (Bei den älteren Modellen war die Hardware-UART-Schnittstelle direkt mit der Stiftleiste verbunden.) Standardmäßig ist beim Raspberry 3 Model B jetzt eine Software-UART-Schnittstelle (ttyS0) mit der Stiftleiste verbunden. Die Baudraten werden damit nicht befriedigend eingehalten. Für dieses Problem gibt es mehrere im Netz beschriebene Methoden, ich habe mich für folgende entschieden:Dienst für das Bluetooth-Modul deaktivieren mit:
sudo systemctl disable hciuart.service
Die Datei "config.txt" mit einem Editor öffnen z.B.:
sudo nano /boot/config.txtIn der letzten Zeile den Text "dtoverlay=pi3-disable-bt"ergänzen.
Mit [STRG]+[X] den Editor beenden und die Änderungen mit [j] [ENTER]speichern.
Dabei wird die ttyAMA0 automatisch wieder auf die Stiftleiste gemappt.
Ab jetzt ist der Raspberry auf dem neuesten Stand und die Hardware-UART-Schnittstelle (ttyAMA0) für unsere Bedürfnisse angepasst. Nacheinander diverse, für die Scripte benötigte, Pakete installieren (die letzten beiden sollten schon drauf sein, aber eine Kontrolle ist besser):
sudo apt-get install python-pip sudo pip install python-telegram-bot sudo pip install pyserial sudo apt-get install wiringpi sudo apt-get install python-dev sudo apt-get install python-rpi.gpio
Jetzt kommen die Python-Scripte ins Spiel. Als erstes wird das Script für die LED und den Taster beschrieben. Die LED dient zur Betriebsanzeige und der Taster zum definierten Herunterfahren des Raspberrys. Wird der Taster länger als 3 Sekunden betätigt, führt dies zum Shutdown. Im Normalbetrieb blitzt die LED alle 5 Sekunden kurz auf. Dazu eine Datei "shutdown.py" erzeugen, diese ausführbar machen und anschließend im Editor öffnen:
mkdir /home/pi/shutdown touch /home/pi/shutdown/shutdown.py chmod +x /home/pi/shutdown/shutdown.py nano /home/pi/shutdown/shutdown.pyFolgenden Inhalt in die Datei schreiben:
#!/usr/bin/python # -*- coding: utf-8 -*- import time import RPi.GPIO as GPIO import os import sys GPIO.setmode(GPIO.BOARD) #Pinheadernummern als Referenz benutzen GPIO.setup(7, GPIO.IN) #PIN7 als Eingang dekalrieren (Taster gegen Masse mit 10k Pullup gegen 3,3V und 330R Widerstand in Serie) GPIO.setup(5, GPIO.OUT, initial=GPIO.LOW) #PIN5 als Ausgang deklarieren (LED über 330R an Masse und LED abschalten) def poweroff(channel): time.sleep(0.1) GPIO.output(5, GPIO.HIGH) time.sleep(3) GPIO.output(5, GPIO.LOW) if GPIO.input(7) == GPIO.LOW: for i in range(19): GPIO.output(5, GPIO.HIGH) time.sleep(0.1) GPIO.output(5, GPIO.LOW) time.sleep(0.1) os.system("wall das System wird herunter gefahren") os.system("sudo shutdown -h now") GPIO.add_event_detect(7, GPIO.FALLING, callback = poweroff, bouncetime = 50) try: while True: time.sleep(5) GPIO.output(5, GPIO.HIGH) time.sleep(0.05) GPIO.output(5, GPIO.LOW) except KeyboardInterrupt: GPIO.cleanup() sys.stdout.write("\nProgramm wurde manuell beendet!")Mit [STRG]+[X] den Editor beenden und die Änderungen mit [j] [ENTER]speichern.
Für Das Script des Telegram-Bots ist wieder eine Datei zu erzeugen, ausführbar zu machen und im Editor zu öffnen:
mkdir /home/pi/rollladensteuerung touch /home/pi/rollladensteuerung/telegram_bot.py chmod +x /home/pi/rollladensteuerung/telegram_bot.py nano /home/pi/rollladensteuerung/telegram_bot.pyFolgenden Inhalt in die Datei schreiben:
(Die richtigen IDs der berechtigten Nutzer werden später ergänzt)
#!/usr/bin/python # -*- coding: utf-8 -*- from telegram import (ReplyKeyboardMarkup, ReplyKeyboardRemove, ParseMode) from telegram.ext import (Updater, CommandHandler, MessageHandler, Filters, RegexHandler, ConversationHandler) import time import serial import sys try: my_tty = serial.Serial(port='/dev/ttyAMA0', baudrate = 9600, parity =serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=0.1) sys.stdout.write(my_tty.portstr + " geöffnet\n\n") my_tty.close() my_tty.open() my_tty.reset_input_buffer() my_tty.reset_output_buffer() except Exception, e: sys.stdout.write("serieller Port konnte nicht geöffnet werden:\n" + str(e) + "\n\n") exit() FENSTERWAHL, POSITION = range(2) Soll_Fenster = '' LIST_OF_ADMINS = [123456789, 445566778] keyboard1 = [['Wohnen', 'Erker', 'Terrasse'], ['Lars', 'Paula', 'Schlafen'], ['Kochen','Dusche', 'Bad'], ['NORD', 'OST', 'SUED', 'WEST', 'ALLE'], ['Positionsabfrage', 'Abbrechen'] ] markup1 = ReplyKeyboardMarkup(keyboard1) keyboard2 = [['100%', '90%', '80%'], ['70%', '60%', '50%'], ['40%', '30%', '20%'], ['10%', '0%', 'Abbrechen'] ] markup2 = ReplyKeyboardMarkup(keyboard2) def start(bot, update): try: user_id = update.message.from_user.id except (NameError, AttributeError): try: user_id = update.inline_query.from_user.id except (NameError, AttributeError): try: user_id = update.chosen_inline_result.from_user.id except (NameError, AttributeError): try: user_id = update.callback_query.from_user.id except (NameError, AttributeError): return ConversationHandler.END if user_id not in LIST_OF_ADMINS: update.message.reply_text('Hello %s %s.This is a private Bot.Your ChatID: "%s" has been blocked.' % (update.message.from_user.first_name, update.message.from_user.last_name, update.message.chat_id)) return ConversationHandler.END else: update.message.reply_text('Fensterauswahl:', reply_markup=markup1) return FENSTERWAHL def fensterauswahl(bot, update): global Soll_Fenster Soll_Fenster = update.message.text update.message.reply_text('Position:', reply_markup=markup2) return POSITION def position(bot, update): Soll_Position = update.message.text sys.stdout.write("*** \"%s\" soll auf %s gefahren werden" % ((Soll_Fenster), Soll_Position)) if Soll_Fenster == 'Wohnen': my_tty.write(('M8,' + Soll_Position[:-1]).encode() + '\n') elif Soll_Fenster == 'Erker': my_tty.write(('M9,' + Soll_Position[:-1]).encode() + '\n') time.sleep(0.2) my_tty.write(('M10,' + Soll_Position[:-1]).encode() + '\n') time.sleep(0.2) my_tty.write(('M11,' + Soll_Position[:-1]).encode() + '\n') elif Soll_Fenster == 'Terrasse': my_tty.write(('M1,' + Soll_Position[:-1]).encode() + '\n') elif Soll_Fenster == 'Lars': my_tty.write(('M3,' + Soll_Position[:-1]).encode() + '\n') elif Soll_Fenster == 'Paula': my_tty.write(('M6,' + Soll_Position[:-1]).encode() + '\n') elif Soll_Fenster == 'Schlafen': my_tty.write(('M7,' + Soll_Position[:-1]).encode() + '\n') elif Soll_Fenster == 'Kochen': my_tty.write(('M2,' + Soll_Position[:-1]).encode() + '\n') elif Soll_Fenster == 'Dusche': my_tty.write(('M4,' + Soll_Position[:-1]).encode() + '\n') elif Soll_Fenster == 'Bad': my_tty.write(('M5,' + Soll_Position[:-1]).encode() + '\n') elif Soll_Fenster == 'NORD': my_tty.write(('G1,' + Soll_Position[:-1]).encode() + '\n') elif Soll_Fenster == 'OST': my_tty.write(('G2,' + Soll_Position[:-1]).encode() + '\n') elif Soll_Fenster == 'SUED': my_tty.write(('G3,' + Soll_Position[:-1]).encode() + '\n') elif Soll_Fenster == 'WEST': my_tty.write(('G4,' + Soll_Position[:-1]).encode() + '\n') elif Soll_Fenster == 'ALLE': my_tty.write(('S,' + Soll_Position[:-1]).encode() + '\n') update.message.reply_text('Fensterauswahl:', reply_markup=markup1) return FENSTERWAHL def posabfrage(bot, update): mystring='' update.message.reply_text('Positionen werden abgefragt...', reply_markup=ReplyKeyboardRemove()) my_tty.reset_input_buffer() my_tty.reset_output_buffer() time.sleep(0.2) my_tty.write('P,0\n') time.sleep(0.5) while my_tty.in_waiting > 0: mystring += my_tty.read() my_text = '<code>Wohnen : ' + mystring.split(',')[7].rjust(3) + '%\nErker1 : ' + mystring.split(',')[8].rjust(3) + '%\nErker2 : ' + mystring.split(',')[9].rjust(3) + '%\nErker3 : ' + mystring.split(',')[10].rjust(3) + '%\nTerrasse: ' + mystring.split(',')[0].rjust(3) + '%\nLars : ' + mystring.split(',')[2].rjust(3) + '%\nPaula : ' + mystring.split(',')[5].rjust(3) + '%\nSchlafen: ' + mystring.split(',')[6].rjust(3) + '%\nKochen : ' + mystring.split(',')[1].rjust(3) + '%\nDusche : ' + mystring.split(',')[3].rjust(3) + '%\nBad : ' + mystring.split(',')[4].rjust(3) + '%</code>' bot.send_message(chat_id=update.message.chat_id, text=my_text , parse_mode=ParseMode.HTML) time.sleep(2) update.message.reply_text('Fensterauswahl:', reply_markup=markup1) return FENSTERWAHL def stop(bot, update): update.message.reply_text('Bis bald mit /start', reply_markup=ReplyKeyboardRemove()) return ConversationHandler.END def error(bot, update, error): return ConversationHandler.END def main(): updater = Updater("123456888:XXYYZZXXYYZZXXY_1235dhiulhewggfewizjA") dp = updater.dispatcher conv_handler = ConversationHandler( entry_points=[CommandHandler('start', start)], states={ FENSTERWAHL: [RegexHandler('^(Wohnen|Erker|Terrasse|Lars|Paula|Schlafen|Kochen|Dusche|Bad|NORD|OST|SUED|WEST|ALLE)$', fensterauswahl), RegexHandler('^Positionsabfrage$', posabfrage), RegexHandler('^Abbrechen$', stop)], POSITION: [RegexHandler('^(100%|90%|80%|70%|60%|50%|40%|30%|20%|10%|0%)$', position), RegexHandler('^Abbrechen$', stop)], }, fallbacks=[CommandHandler('/stop', stop)] ) dp.add_handler(conv_handler) dp.add_error_handler(error) updater.start_polling() updater.idle() if __name__ == '__main__': main()Mit [STRG]+[X] den Editor beenden und die Änderungen mit [j] [ENTER]speichern.
Jetzt müssen die Scripte noch als Dienste beschrieben und gestartet werden.
Datei für den Shutdown-Dienst erzeugen und im Editor öffnen:
sudo nano /etc/systemd/system/gpio_daemon.serviceFolgenden Inhalt in die Datei schreiben:
[Unit] Description=Shutdown ueber Taster After=multi-user.target [Service] Type=idle ExecStart=/usr/bin/python /home/pi/shutdown/shutdown.py Restart=on-failure RestartSec=1m [Install] WantedBy=multi-user.targetMit [STRG]+[X] den Editor beenden und die Änderungen mit [j] [ENTER]speichern.
Das Gleiche für den Telegram-Bot-Dienst:
sudo nano /etc/systemd/system/rollladen_daemon.serviceFolgenden Inhalt in die Datei schreiben:
[Unit] Description=Rollladen_Steuerung_ueber_Telegram After=multi-user.target [Service] Type=idle ExecStart=/usr/bin/python /home/pi/rollladensteuerung/telegram_bot.py Restart=on-failure RestartSec=1m [Install] WantedBy=multi-user.target
Beide Dienste aktivieren und den Raspberry Pi neu starten:
sudo systemctl daemon-reload sudo systemctl enable gpio_daemon.service sudo systemctl enable rollladen_daemon.service sudo reboot
Prüfen ob die Dienste laufen:
sudo systemctl status gpio_daemon.service sudo systemctl status rollladen_daemon.service
Wenn die Dienste ordungsgemäß laufen, kann auf dem Smartphone der erste Befehl an den eigenen Bot gesendet werden:
Auf den Befehl "/start" wird die API mit großer Warscheinlichkeit etwa so antworten:
Hello "Dein Telegram-Nickname". This is a private Bot. Your ChatID: "12345678" has been blocked.
Aus dieser Nachricht ist die ChatID des eigenen Telegram-Accounts zu entnehmen, und in die Datei "telegram_bot.py" an entsprechender Stelle einzutragen.
Bei "LIST_OF_ADMINS = [123456789, 445566778]" - es können mehrere Nutzer IDs (durch Kommata getrennt) angegeben werden.
nano /home/pi/rollladensteuerung/telegram_bot.pyDies dient einfach dazu, dass nicht jeder Mensch der Welt den Bot nutzen kann/darf. Damit die Änderungen in der Datei wirksam werden, ist es am einfachsten den Raspberry neu zu starten.
sudo reboot
Funktionstest
"/start" klicken |
Fenster per Schaltflüche auswählen |
Sollposition auswählen |
anschließend gelangt man zurück zur Fensterauswahl |
zum Beenden, die Schaltflächen scrollen |
ggf. verborgene Schaltflächen wieder anzeigen. |