Python exemplarisch |
Alle Programme können von hier heruntergeladen werden. |
Datenübertragung mit TCP/IP |
Der Informationsaustausch zwischen einem Programm, das auf dem Raspberry Pi und einem Programm, das auf einem entfernten Computer ausgeführt wird, ist vor allem dann wichtig, wenn der Raspberry Pi als Messsystem eingesetzt wird und die Sensordaten auf einem abgesetzten PC verarbeitet werden, oder wenn der Raspberry Pi (bzw. eine daran angeschlossene Roboterhardware) fremdgesteuert wird (sog. Remote Modus). Die Datenübertragung erfolgt meistens über TCP/IP mit der Client-/Server-Technologie, die auf der Socket-Programmierung basiert. In diesem Kapitel zeigen wir typische Programme für eine einfache TCP-Kommunikation, ohne in Einzelheiten zu gehen. (Bei Unklarheiten ziehen Sie ein spezifisches Tutorials über Socket-Programmierung zu Rate.) Wichtig und vielleicht etwas unerwartet ist die Tatsache, dass die Kommunikationspartner, Server und Client nicht symmetrisch sind. Zuerst muss das Serverprogramm gestartet werden und erst dann kann der Client eine Verbindung aufbauen. Die beiden Computer identifizieren sich im Internet mit ihrer IP-Adresse und kommunizieren über einen bestimmten TCP-Port (mit einer Portnummer von 0..65 535).
|
Experiment 1: Programmierung mit einer Low-Level Socket- Bibliothek |
In einer Client-/Server-Anwendung zwischen einem Raspberry Pi und einem Computer kann der Raspberry Pi (wie im Bild oben) der Server und der PC der Client sein. Je nach Situation können die Rollen aber auch vertauscht werden. Das folgende Beispiel zeigt eine typische Anwendung, wo der Raspberry Pi ein Frontend eines Messsystems ist, der dafür verantwortlich ist, Sensordaten an einen entfernten Computer weiterzuleiten. Das Programm ist unter Verwendung des standardmässigen Python Socket-Moduls geschrieben und dadurch relativ kompliziert. Ziel: Programm:[►] # DataServer1.py from threading import Thread import socket import time import RPi.GPIO as GPIO VERBOSE = False IP_PORT = 22000 P_BUTTON = 24 # adapt to your wiring def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(P_BUTTON, GPIO.IN, GPIO.PUD_UP) def debug(text): if VERBOSE: print "Debug:---", text # ---------------------- class SocketHandler ------------------------ class SocketHandler(Thread): def __init__(self, conn): Thread.__init__(self) self.conn = conn def run(self): global isConnected debug("SocketHandler started") while True: cmd = "" try: debug("Calling blocking conn.recv()") cmd = self.conn.recv(1024) except: debug("exception in conn.recv()") # happens when connection is reset from the peer break debug("Received cmd: " + cmd + " len: " + str(len(cmd))) if len(cmd) == 0: break self.executeCommand(cmd) conn.close() print "Client disconnected. Waiting for next client..." isConnected = False debug("SocketHandler terminated") def executeCommand(self, cmd): debug("Calling executeCommand() with cmd: " + cmd) if cmd[:-1] == "go": # remove trailing "\0" if GPIO.input(P_BUTTON) == GPIO.LOW: state = "Button pressed" else: state = "Button released" print "Reporting current state:", state self.conn.sendall(state + "\0") # ----------------- End of SocketHandler ----------------------- setup() serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # close port when process exits: serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) debug("Socket created") HOSTNAME = "" # Symbolic name meaning all available interfaces try: serverSocket.bind((HOSTNAME, IP_PORT)) except socket.error as msg: print "Bind failed", msg[0], msg[1] sys.exit() serverSocket.listen(10) print "Waiting for a connecting client..." isConnected = False while True: debug("Calling blocking accept()...") conn, addr = serverSocket.accept() print "Connected with client at " + addr[0] isConnected = True socketHandler = SocketHandler(conn) # necessary to terminate it at program termination: socketHandler.setDaemon(True) socketHandler.start() t = 0 while isConnected: print "Server connected at", t, "s" time.sleep(10) t += 10 Bemerkungen: Es wird empfohlen, ein Flag VERBOSE oder DEBUG zu definieren, um das System in einen "Debug-Modus" zu versetzen, damit zusätzliche Statusmeldungen ausgeschrieben werden. Sobald das Programm korrekt arbeitet, kann man den Debug-Modus abschalten. Der Client sendet alle 2 Sekunden ein "go" und schreibt die Antwort aus, die er vom Server erhalten hat. Programm:[►] # DataClient1.py from threading import Thread import socket, time VERBOSE = False IP_ADDRESS = "192.168.0.17" IP_PORT = 22000 def debug(text): if VERBOSE: print "Debug:---", text # ------------------------- class Receiver --------------------------- class Receiver(Thread): def run(self): debug("Receiver thread started") while True: try: rxData = self.readServerData() except: debug("Exception in Receiver.run()") isReceiverRunning = False closeConnection() break debug("Receiver thread terminated") def readServerData(self): debug("Calling readResponse") bufSize = 4096 data = "" while data[-1:] != "\0": # reply with end-of-message indicator try: blk = sock.recv(bufSize) if blk != None: debug("Received data block from server, len: " + \ str(len(blk))) else: debug("sock.recv() returned with None") except: raise Exception("Exception from blocking sock.recv()") data += blk print "Data received:", data # ------------------------ End of Receiver --------------------- def startReceiver(): debug("Starting Receiver thread") receiver = Receiver() receiver.start() def sendCommand(cmd): debug("sendCommand() with cmd = " + cmd) try: # append \0 as end-of-message indicator sock.sendall(cmd + "\0") except: debug("Exception in sendCommand()") closeConnection() def closeConnection(): global isConnected debug("Closing socket") sock.close() isConnected = False def connect(): global sock sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) debug("Connecting...") try: sock.connect((IP_ADDRESS, IP_PORT)) except: debug("Connection failed.") return False startReceiver() return True sock = None isConnected = False if connect(): isConnected = True print "Connection established" time.sleep(1) while isConnected: print "Sending command: go..." sendCommand("go") time.sleep(2) else: print "Connection to %s:%d failed" % (IP_ADDRESS, IP_PORT) print "done" Bemerkungen: |
Experiment 2: Viel einfacher mit einer zusätzlichen Software-Schicht |
Unser ereignisgesteuertes Modul tcpcom kapselt die Komplexität der Socket-Programmierung (inklusive der Threads) in einer Klassenbibliothek (Informationen unter www.aplu.ch/tcpcom). Laden Sie das Modul tcpcom.py von hier herunter und kopieren Sie es in das gleiche Verzeichnis, in dem sich Ihr Programm befindet. Bei gleich bleibender Funktionalität wird der Programmcode durch die Verwendung des Moduls wesentlich einfacher und man nimmt gerne eine zusätzliche Software-Schicht in Kauf. Programm:[►] # DataServer2.py from tcpcom import TCPServer import time import RPi.GPIO as GPIO IP_PORT = 22000 P_BUTTON = 24 # adapt to your wiring 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 if msg == "go": if GPIO.input(P_BUTTON) == GPIO.LOW: server.sendMessage("Button pressed") else: server.sendMessage("Button released") def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(P_BUTTON, GPIO.IN, GPIO.PUD_UP) setup() server = TCPServer(IP_PORT, stateChanged = onStateChanged) Bemerkungen: Programm:[►] # DataClient2.py from tcpcom import TCPClient import time IP_ADDRESS = "192.168.0.17" IP_PORT = 22000 def onStateChanged(state, msg): global isConnected if state == "CONNECTING": print "Client:-- Waiting for connection..." elif state == "CONNECTED": print "Client:-- Connection estabished." elif state == "DISCONNECTED": print "Client:-- Connection lost." isConnected = False elif state == "MESSAGE": print "Client:-- Received data:", msg client = TCPClient(IP_ADDRESS, IP_PORT, stateChanged = onStateChanged) rc = client.connect() if rc: isConnected = True while isConnected: print "Client:-- Sending command: go..." client.sendMessage("go") time.sleep(2) print "Done" else: print "Client:-- Connection failed" |
Experiment 3: Zugriff auf einen lokalen Server über das Internet |
In diesem Experiment werden die Rollen vertauscht: Der Raspberry Pi ist nun ein Client, der mit einem PC-Server kommuniziert. Dieser ist über ein WLAN- oder Ethernet-Router mit dem Internet verbunden. Client und Server befindet sich damit nicht mehr im gleichen IP-Segment, sondern irgendwo im Internet.
Ziel: 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) Auch der Client-Code auf dem Raspberry Pi ist einfach. (Wenn sich zu Testzwecken Server und Client im gleichen internen IP-Segment befinden, verwenden Sie beim Client die IP-Adresse des Server-PCs, die Sie mit ipconfig unter Windows oder mit ifconfig auf Mac / Linux oder unter Verwendung eines anderen Netzwerk-Tool herausfinden können.) Programm:[►] # DataClient3.py from tcpcom import TCPClient import time import RPi.GPIO as GPIO IP_ADDRESS = "192.168.0.111" # PC on same IP segment #IP_ADDRESS = "5.149.19.200" # router's WAN address #IP_ADDRESS = "raplu.zapto.org" # router's no-ip alias IP_PORT = 22000 P_BUTTON = 24 # adapt to your wiring def setup(): GPIO.setmode(GPIO.BOARD) GPIO.setup(P_BUTTON, GPIO.IN, GPIO.PUD_UP) def onStateChanged(state, msg): global isConnected if state == "CONNECTING": print "Client:-- Waiting for connection..." elif state == "CONNECTED": print "Client:-- Connection estabished." elif state == "DISCONNECTED": print "Client:-- Connection lost." isConnected = False elif state == "MESSAGE": print "Client:-- Received data:", msg setup() client = TCPClient(IP_ADDRESS, IP_PORT, stateChanged = onStateChanged) rc = client.connect() if rc: isConnected = True while isConnected: if GPIO.input(P_BUTTON) == GPIO.LOW: reply = "Button pressed" else: reply = "Button released" client.sendMessage(reply) print "Client:-- Sending message:", reply time.sleep(2) print "Done" else: print "Client:-- Connection failed" Bemerkungen: Wenn Sie die IP-Adresse des Routers nicht kennen, so können Sie mit einem PC-Browser die Site www.portchecktool.com besuchen, wo die Adresse sichtbar ist. Wie Sie sehen, kann Sie ein externer Server bis zur IP-Adresse des Routers zurückverfolgen, die Sie von Ihrem Provider erhalten haben, der wahrscheinlich auch Ihre Personalien kennt. Wenn alles funktioniert, können Sie mit dem Raspberry Pi von einem anderen Ort (auch weit entfernt) eine Internetverbindung zum PC-Server aufbauen. Wenn Sie zudem ein Konto bei no-ip.org erwerben und einen DUC (Dynamic Update Client) auf dem PC einrichten, so können Sie sogar eine feste URL verwenden. Mit Ihrem Know-how ist es nun leicht, die Rollen zu tauschen und Server und DUC auf einem Raspberry Pi einzurichten. |