Python exemplarisch |
Alle Programme können von hier heruntergeladen werden. |
GSM - Die Tür zur drahtlos verbundenen Welt |
Wenn Sie mit Ihrem Raspberry Pi mit der Aussenwelt kommunizieren möchten und sich nicht in der Reichweite eines WLAN Access Points befinden, können Sie das digitale Mobilfunknetz verwenden. Wie bei Mobiltelefonen benötigen Sie dazu eine SIM-Karte und ein Abonnement von einem beliebigen Anbieter. Da Sie wahrscheinlich für die meisten Anwendungen nur ein sehr kleines Datenvolumen und eine niedrige Übertragungsgeschwindigkeit benötigen, genügt ein billiges Abonnement (Daten-Einsteigerpaket, eventuell ohne SMS und ohne Telefonie). Als Hardware benötigen Sie ein GSM-Modul, auch GSM-Modem genannt. Die meisten Modems werden an der seriellen Schnittstelle (RS-232) des Raspberry Pi angeschlossen und verwenden für die Kommunikation den erweiterten Hayes-Befehlssatz, bei dem die Befehle mit AT... beginnen. Wir verwenden in diesem Kapitel ein weit verbreitetes Modem mit dem SIM800 GSM/GPRS Chip.
Bemerkung: Normalerweise ist eine SIM-Karte mit einem PIN-Code gesichert, den Sie bei jeder Inbetriebnahme eingeben müssen. Um die Karte im Modem zu verwenden, müssen Sie diesen Schutz (SIM-Lock) entfernen, d.h. die SIM-Karte "entsperren". Dazu legen Sie die SIM-Karte in ein Handy oder Smartphone ein und suchen im Setup nach der entsprechenden Option (eventuell konsultieren Sie die Betriebsanleitung). Sie können auch Ihren Netzbetreiber oder Verkäufer der SIM-Karte bitten, den Schutz zu entfernen. Überprüfen Sie dann, ob das Handy/Smartphone startet, ohne den Pin-Code zu verlangen. |
Experiment 1: Raspberry Pi als SMS-Butler verwenden |
Das GSM-Modem öffnet Ihnen die Welt für viele interessante Kommunikationsprojekte. Einige von ihnen können sehr nützlich und bereits durchaus professionell sein. Im folgenden Beispiel kommuniziert der Raspberry Pi per SMS. Sie benötigen dazu allerdings ein Mobile-Abo, bei dem das Empfangen und Senden von SMS gestattet ist. Ziel: Die Anwendung kann erweitert werden: Nach dem Empfang einer SMS wird eine nützliche Aktionen ausgeführt, zum Beispiel die Heizung in einer abgelegenen Wohnung ein- oder ausgeschaltet und die aktuelle Raumtemperatur zurückgesendet. Für die manuelle Eingabe der SMS-Befehle können Sie das PuTTY- Terminal auf dem Raspberry Pi verwenden (siehe Kapitel Serieller Port). Testen Sie mit dem SIM800-Modem beispielsweise Folgendes:
Das Programm soll auf den Eingang einer SMS mit dem Text "getStatus" warten und dann eine SMS mit dem aktuellen Zeitpunkt (Datum und Zeit) und dem Zustand des GPIO Pin # 24 (Tastenschalter) an eine bestimmte Telefonnummer senden. Programm:[►] # SIMSMS1.py import RPi.GPIO as GPIO import serial import time, sys import datetime P_BUTTON = 24 # Button, adapt to your wiring def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(P_BUTTON, GPIO.IN, GPIO.PUD_UP) SERIAL_PORT = "/dev/ttyAMA0" # Raspberry Pi 2 #SERIAL_PORT = "/dev/ttyS0" # Raspberry Pi 3 ser = serial.Serial(SERIAL_PORT, baudrate = 9600, timeout = 5) setup() ser.write("AT+CMGF=1\r") # set to text mode time.sleep(3) ser.write('AT+CMGDA="DEL ALL"\r') # delete all SMS time.sleep(3) reply = ser.read(ser.inWaiting()) # Clean buf print "Listening for incomming SMS..." while True: reply = ser.read(ser.inWaiting()) if reply != "": ser.write("AT+CMGR=1\r") time.sleep(3) reply = ser.read(ser.inWaiting()) print "SMS received. Content:" print reply if "getStatus" in reply: t = str(datetime.datetime.now()) if GPIO.input(P_BUTTON) == GPIO.HIGH: state = "Button released" else: state = "Button pressed" ser.write('AT+CMGS="+41764331356"\r') time.sleep(3) msg = "Sending status at " + t + ":--" + state print "Sending SMS with status info:" + msg ser.write(msg + chr(26)) time.sleep(3) ser.write('AT+CMGDA="DEL ALL"\r') # delete all time.sleep(3) ser.read(ser.inWaiting()) # Clear buf time.sleep(5) Bemerkungen: Falls der Zeitpunkt nicht korrekt ist, so müssen Sie die Systemzeit gemäss den Anleitungen im Tutorial Timer richtig einstellen. |
Experiment 2: Daten über GSM zum TCP-Server senden |
Der PC-Server muss vom Internet her durch IP-Forwarding sichtbar gemacht werden, wie es dort beschrieben wurde, wobei entweder die gepunktete IP-Adresse oder das IP-Alias von no-ip verwendet werden kann. Ziel: Nachdem Ihr Router richtig konfiguriert ist, starten Sie das Serverprogramm. Programm:[►] # DataServer3.py from tcpcom import TCPServer IP_PORT = 22000 def onStateChanged(state, msg): if state == "LISTENING": print "Server:-- Listening..." elif state == "CONNECTED": print "Server:-- Connected to", msg elif state == "MESSAGE": print "Server:-- Message received:", msg server = TCPServer(IP_PORT, stateChanged = onStateChanged) Als nächstes müssen Sie Ihr SIM800-Modem als Client konfigurieren. Dazu können Sie die AT-Befehlsliste konsultieren, aber im folgenden Beispiel sehen Sie leicht, was zu tun ist (weitere Informationen zum TCP-Modus finden Sie hier). Wie oben gezeigt, ist es vorteilhaft, einen Terminal-Emulator (PuTTY) zu verwenden, um die Befehle "von Hand" auszutesten, bevor ein Programm geschrieben wird. Nach dem Start des Emulator tippt man als erstes AT <cr> ein und das Modem muss mit OK antworten (falsch eingegebene Zeichen löscht man mit ^H). Für den Aufbau der Internet-Verbindung sind folgende Kommandos hilfreich:
Da das Eintippen mühsam ist, schreiben wir besser ein Programm, das die Befehle an das Modem sendet und die Rückgabe-Information überprüft. Beachten Sie, dass nicht gleichzeitig das Programm und PuTTY laufen können, da sonst PuTTY die Rückgaben des Modems "wegfrisst". Dafür sieht man aber im Terminalfenster, was das Programm tatsächlich sendet und welches die Antworten des Modems sind. Wie üblich strukturieren wir das Programm mit einem Modul SIM800Modem.py, das zweckmässige Modem-Funktionen definiert. Es wäre schöner, eine Klasse SIM800Modem zu schreiben, was aber Ihnen überlassen wird. Um die Antwort des Modems zu holen, überprüfen wir mit n = ser.inWaiting()die Anzahl der Zeichen, die im Antwortpuffer warten und lesen sie alle gleichzeitig mit ser.read(n)ein, wobei ser die Instanz des seriellen Ports ist, die man erhält, wenn der Port mit serial.Serial() geöffnet wird. Programm:[►] #SIM800Modem.py import RPi.GPIO as GPIO import time VERBOSE = False P_POWER = 11 # Power pin P_RESET = 12 # Reset pin def debug(text): if VERBOSE: print "Debug:---", text def resetModem(): GPIO.setmode(GPIO.BOARD) GPIO.setup(P_RESET, GPIO.OUT) GPIO.output(P_RESET, GPIO.LOW) time.sleep(0.5) GPIO.output(P_RESET, GPIO.HIGH) time.sleep(0.5) GPIO.output(P_RESET, GPIO.LOW) time.sleep(3) def togglePower(): GPIO.setmode(GPIO.BOARD) GPIO.setup(P_POWER, GPIO.OUT) GPIO.output(P_POWER, GPIO.LOW) time.sleep(0.5) GPIO.output(P_POWER, GPIO.HIGH) time.sleep(3) GPIO.output(P_POWER, GPIO.LOW) def isReady(ser): # Resetting to defaults cmd = 'ATZ\r' debug("Cmd: " + cmd) ser.write(cmd) time.sleep(2) reply = ser.read(ser.inWaiting()) time.sleep(8) # Wait until connected to net return ("OK" in reply) def connectGSM(ser, apn): # Login to APN, no userid/password needed cmd = 'AT+CSTT="' + apn + '"\r' debug("Cmd: " + cmd) ser.write(cmd) time.sleep(3) # Bringing up network cmd = "AT+CIICR\r" debug("Cmd: " + cmd) ser.write(cmd) time.sleep(5) # Getting IP address cmd = "AT+CIFSR\r" debug("Cmd: " + cmd) ser.write(cmd) time.sleep(3) # Returning all messages from modem reply = ser.read(ser.inWaiting()) debug("connectGSM() retured:\n" + reply) return reply def connectTCP(ser, host, port): cmd = 'AT+CIPSTART="TCP","' + host + '","' + str(port) + '"\r' ser.write(cmd) time.sleep(5) reply = ser.read(ser.inWaiting()) debug("connctTCP() retured:\n" + reply) return reply def sendHTTPRequest(ser, host, request): ser.write("AT+CIPSEND\r") time.sleep(2) request = "GET " + request + " HTTP/1.1\r\nHost: " + host + "\r\n\r\n" ser.write(request + chr(26)) # data<^Z> time.sleep(2) def closeTCP(ser, showResponse = False): ser.write("AT+CIPCLOSE=1\r") reply = ser.read(ser.inWaiting()) debug("closeTCP() retured:\n" + reply) if showResponse: print "Server reponse:\n" + reply[(reply.index("SEND OK") + 9):] time.sleep(2) def getIPStatus(ser): cmd = "AT+CIPSTATUS\n" ser.write(cmd) time.sleep(1) reply = ser.read(ser.inWaiting()) return reply Bemerkungen: Wie Sie sehen, sind doch einige Kenntnisse und praktische Erfahrungen notwendig, um diesen Code zu schreiben. Wichtig ist es, angemessene Delays zwischen den Aktionen einzubauen, da das Programm verglichen mit dem Modem sehr schnell ist und man warten muss, bis das Modem bzw. das GSM-Netz den Befehl ausgeführt hat. Dies ist allerdings eine verpönte Programmiertechnik, weil die erforderlichen Delays wegen unvorhersehbaren Verzögerungen variieren können. Eine bessere Lösung wäre es, in einer Schleife zu warten, bis das Modem mit einer Erfolgsmeldung antwortet. Eine weitere Verbesserung besteht darin, beim einem Fehler oder Timeout nicht die ganze Aktion abzubrechen, sondern den Versuch zu wiederholen (retry). Diese Verbesserungen führen aber zu einem deutlich längeren Code und werden deshalb hier nicht berücksichtigt. Sie können das Flag VERBOSE = True setzen, damit beim Programmablauf zusätzliche Information ausgeschrieben werden, die Ihnen insbesondere bei der Fehlersuche helfen. Ziel: Programm:[►] # SIMClient.py import serial import time, sys from SIM800Modem import * import RPi.GPIO as GPIO APN = "gprs.swisscom.ch" #HOST = "5.149.19.125" HOST = "raspibrick.zapto.org" PORT = 5000 SERIAL_PORT = "/dev/ttyAMA0" # Raspberry Pi 2 #SERIAL_PORT = "/dev/ttyS0" # Raspberry Pi 3 P_BUTTON = 24 # adapt to your wiring def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(P_BUTTON, GPIO.IN, GPIO.PUD_UP) setup() print "Resetting modem..." resetModem() ser = serial.Serial(SERIAL_PORT, baudrate = 9600, timeout = 5) if not isReady(ser): print "Modem not ready." sys.exit(0) print "Connecting to GSM net..." connectGSM(ser, APN) print "Connecting to TCP server..." reply = connectTCP(ser, HOST, PORT) if "CONNECT OK" not in reply: print "Connection failed" sys.exit(0) print "Connection established. Sending data..." while True: if GPIO.input(P_BUTTON) == GPIO.LOW: msg = "Button pressed" else: msg = "Button released" k = len(msg) # do not exceed value returned by AT+CIPSEND? (max 1460) ser.write("AT+CIPSEND=" + str(k) +"\r") # fixed length sending time.sleep(1) # wait for prompt ser.write(msg) time.sleep(4) Bemerkungen: Wie im Kapitel Datenübertragung erwähnt, muss der Client die IP-Adresse kennen, mit der der Server über das Internet erreicht werden kann. Weil der Router IP-Forwarding verwendet, ist dies die IP-Adresse des Routers. |
Experiment 3: E-Mails mit Attachments verschicken |
Auch mit E-Mails können Informationen und Warnungen vom Raspberry Pi aus versendet werden. Es lassen sich sogar Bilder, die von der Kamera aufgenommenen werden, als Mail-Attachment mitsenden. Im Folgenden setzen wir voraus, dass der Raspberry PI über Ethernet oder WLAN mit dem Internet verbunden ist. Wie bei E-Mail-Apps üblich, wird das Mail vom Raspberry Pi zum SMTP-Server eines persönlichen Mail-Kontos weitergeleitet. Haben Sie noch keines, so können Sie ein kostenloses Mail-Konto bei irgendeinem E-Mail-Provider erstellen, zum Beispiel ein GMail-Konto. (Sie müssen dort aber den Zugriff "für weniger sichere Apps" zulassen, damit es funktioniert). Ziel: Programm:[►] # SendMail1.py import RPi.GPIO as GPIO import smtplib, datetime, os, time from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.image import MIMEImage USERNAME = 'raspi4kids@gmail.com' # Username for authentication PASSWORD = 'raspi1234' # Password for authentication SMTP_SERVER = 'smtp.gmail.com' # URL of SMTP server FROM = "Aegidius Pluess" # Name shown as sender TO = 'a2015@pluess.name' # Mail address of the recipient SSL_PORT = 465 P_BUTTON = 24 # Button pin, adapt to your wiring def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(P_BUTTON, GPIO.IN, GPIO.PUD_UP) def sendMail(subject, text, img = None): print("Sending the mail...") msg = MIMEMultipart("alternative") msg.attach(MIMEText(text, "html")) tmpmsg = msg msg = MIMEMultipart() msg.attach(tmpmsg) if img != None: if not os.path.exists(img): print "File", img, "does not exist." else: fp = open(img, 'rb') img = MIMEImage(fp.read()) # included in mail, not as attachment fp.close() msg.attach(img) msg['Subject'] = subject msg['From'] = FROM server = smtplib.SMTP_SSL(SMTP_SERVER, SSL_PORT) server.login(USERNAME, PASSWORD) server.sendmail(FROM, [TO], msg.as_string()) server.quit() print("Mail successfully sent.") setup() print "Waiting for button event..." while True: if GPIO.input(P_BUTTON) == GPIO.LOW: t = str(datetime.datetime.now()) text = "Alert on " + t + "<br>Button was <b>pressed!</b>" subject = "Alert from Raspberry Pi" sendMail(subject, text) #sendMail(subject, text, "c:/scratch/mailtest.png") time.sleep(30) Bemerkungen: |