Python exemplarisch - RPi Tutorial
deutsch     english

GSM, SMS, EMAIL

 

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.

Empfehlenswert sind GSM Breakout-Module, die einen 40-pin-Verbinder haben, der direkt am GPIO eingesteckt wird (RPI GSM Add-on V2.0, zu finden bei eBay). Dieses Modul ist einfach zu benützen, weil es am UART Rx/Tx des Raspberry Pi angeschlossen ist und zudem mit GPIO-I/O-Ports softwaremässig ein- und ausgeschaltet, sowie eine Reset durchgeführt werden kann. Mit der 5V-Stromversorgung des Moduls wird gleichzeitig auch der Raspberry Pi mit Strom versorgt.

Stecken Sie das Modul auf den GPIO-Header und schliessen Sie es mit einem USB-Kabel an ein 5V-Netzgerät an, das mindestens 2A liefert. Wenn die Einschalttaste eine Weile gedrückt wird, startet das System und stellt automatisch eine Verbindung zum GSM-Netz her.

gsm1

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:
Der Raspberry Pi soll die Funktion eines SMS-Butler übernehmen, der nach dem Empfang einer SMS eine Antwort-SMS mit Statusinformationen zurück sendet.

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:

 Command (beenden mit<cr>) Antwort : Bedeutung
 AT+CMGF=1  OK : Setzt das Modem in den Textmodus
 AT+CMGS="+41764331357"  >: Bereitschaftsprompt zum Senden einer SMS an die gegebene Telefonnummer
 Guten Tag!<^Z>  OK : Text wird nach Ctrl+Z als SMS gesendet
 Drittes SMS empfangen  +CMTL: "SM", 3
 AT+CMGR=3  Zeigt den Inhalt der SMS #3 an
 AT+CMGDA="DEL ALL"  Löscht alle vorhandenen SMS

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)    
Programmcode markieren (Ctrl+C copy, Ctrl+V einfügen)

Bemerkungen:
Beachten Sie, dass Sie eine kurze Zeit auf die Antwort vom Modem warten müssen, bevor Sie den Inhalt des Empfangspuffers mit ser.read() abrufen.

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

 
Befindet sich der Raspberry Pi ausserhalb der Reichweite eines WLAN Access Points, so kann die Anbindung an das Internet über GSM erfolgen. In hier gezeigten Beispiel übernimmt der RPi mit aufgesetzten SIM800-Board die Rolle eines TCP-Clients. Das Szenario ist das gleiche wie im Experiment 3 des Kapitels "Datenübertragung", ausser dass der Link über das GSM-Netzwerk aufgebaut wird. gsm2

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:
Lassen Sie auf dem PC einen einfachen TCP-Server laufen, der den Port 22000 verwendet und lediglich Status-Informationen in einer Konsole ausschreibt. Konfigurieren Sie Ihren Router durch IP-Forwarding so, dass der Server von aussen sichtbar ist. Ein Client auf dem Raspberry Pi soll über GSM eine Verbindung herstellen und alle 5 Sekunden eine Sensor-Information übermitteln (hier lediglich der Zustand eines Tastenschalters).

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)
Highlight program code (Ctrl+C copy, Ctrl+V paste)

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:

 Command (beenden mit <cr>)  Antwort : Bedeutung
 AT  OK
 AT+CREG?  +CREG: 0,1 : Modem ist beim Netzbetreiber registriert
 AT+CSTT="gprs.swisscom.ch"  OK : Verbindet das Modem mit dem Netzbetreiber (APN)
(APN des eigenen Providers verwenden)
 AT+CIICR  OK : Drahtlosverbindung ist aktiviert
 AT+CIFSR  10.221.69.48 : IP-Adresse erhalten

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
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

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:
Sie schreiben unter Verwendung des Moduls SIM800Modem einen Client, der über GSM ungefähr alle 5 Sekunden eine Verbindung zum Server aufnimmt und ihm einen Sensorwert (hier lediglich den Zustand eines Tastenschalters) übermittelt.

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)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

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:
Beim Drücken eines Tastenschalters wird ein E-Mail vom Raspberry Pi mit einem beliebigen Text, z.B. mit einer Zeitangabe und einer Warnung, an einen Empfänger gesendet.

Im folgenden Programm wird ein Google Mail-Konto verwendet.

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)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

Bemerkungen:
Die Funktion sendmail() wird nicht in Einzelheiten erläutert. Die Verwendung ist aber einfach und aus den Parameternamen zu entnehmen. Mit der gleichen Funktion kann auch ein E-Mail von einem PC aus versendet werden. Verwenden Sie den Parameter img, um ein Bild anzufügen.