deutsch     english    français     Print

 

11.6. TCP SOCKETS

 

 

INTRODUCTION

 

The exchange of data between computer systems plays an extremely important role in our interconnected world.  Therefore we often speak of the combined computer- and communication technologies that should be mastered. In this chapter you learn how to handle the data exchange between two computer systems using the TCP/IP protocol which is used in all Internet connections, for example the Web and all streaming services (data clouds, voice, music, video transmission).

 

 

TCPCOM: AN EVENT-DRIVEN SOCKET LIBRARY

 

The socket programming is based on the client-server model, which has already been described in Section 6.2. The server and the client programs are not completely symmetrical. In particular, first the server program must be started before the client program can connect to the server. In order to identify the server on the Internet, its IP address is used. In addition, the server has 65536 communication channels (IP ports), that is selected with a number in the range 0..64535.

When the server starts, it creates a server socket (like a electrical plug) that uses a particular port and goes in a wait state. We say that the server is listening for an incoming client, so the server is in the LISTENING state. The client creates a client socket (the plug counterpart) and tries to establish a communication link to the server using its IP address and port number.

The library tcpcom simplifies the socket programming essentially, since it describes the current state of the server and client with state variables. The change of the variable is considered to be caused by an event.  This programming model corresponds to the natural feeling of many people to describe the communication between two partners by a sequence of events.

As usual in a event-driven model, a callback function, here called stateChanged(state, msg) is invoked by the system, when an event is fired. The Python module is integrated into TigerJython, but can also be downloaded from here to be studied or used outside TigerJython.

The server is started by creating a TCPServer object specifying the ports and the callback function onStateChanged() and embarks in the LISTENING state.

from tcpcom import TCPServer 
server = TCPServer(port, stateChanged = onStateChanged)

The callback onStateChanged (state, msg) has two string parameters state and msg that describe the status change of the server:

state msg description
TCPServer.LISTENING
port
An existing connection was terminated (or the server is started) and the server listens for a new connection
Server.PORT_IN_USE
port
The server cannot go into the LISTENING state because the port is occupied by another process
TCPServer.CONNECTED
IP address client
A client has signed up and was accepted
TCPServer.MESSAGE
received message
The server has received a message
TCPSever.TERMINATED
(empty)
The server is terminated and does not  listen anymore

The client starts with the creation of a TCPClient object specifying the IP address of the server, the port and the callback function onStateChanged (). By invoking connect() it starts a connection trial.

from tcpcom import TCPClient
client = TCPClient(host, port, stateChanged = onStateChanged)
client.connect()

Again, the callback onStateChanged (state, msg) has two string parameters state and msg, describing the state change of the client:

state msg description
TCPClient.CONNECTING
IP address server
Starting connection attempt
TCPClient.CONNECTION_FAILED
IP address server
Connection trial failed
TCPClient.CONNECTED
IP address server
Connection established
TCPClient.MESSAGE
received message
The client has received a message
TCPClient.DISCONNECTED
(empty)
Connection interrupted (aborted by client or server)

The call to connect() is blocking, which means that the function returns True once the connection has succeeded, or False after a certain timeout period (approximately 10 seconds), if the connection fails. The information about the success or failure of the connection can also be detected via the callback.

You can try out the client-server programs on the same PC by starting two TigerJython windows. In this case you choose the host address localhost. Using two different computers for the client and the server is more close to reality. They must be connected with a network cable or via wireless LAN and the link must be open for TCP/IP communication with the selected port. If the connection fails with your normal hotspot (WLAN access point), this is mostly due to firewall restrictions. In this case you can use your own router or start a mobile hot spot app on your smartphone. Access of the mobile phone to the Internet is not necessary.

As your first socket programming duty you create a server that provides a time service. When a client logs on, it sends the current time (with date) back to the client. There are numerous such  time server on the Internet and you can be proud that you are already able to code a professional server application.

In order to turn off the time server, you use a well-known trick: The main program "hangs" in a TigerJython modal message dialog opened by the blocking function msgDlg(). When the function returns by pressing the OK or clicking the close button, the server is stopped by calling terminate().

from tcpcom import TCPServer
import datetime
        
def onStateChanged(state, msg):
    print(state, msg)
    if state == TCPServer.CONNECTED:
        server.sendMessage(str(datetime.datetime.now()))
        server.disconnect()

port = 5000
server = TCPServer(port, stateChanged = onStateChanged)             
msgDlg("Time Server running. OK to stop")
server.terminate()
Highlight program code (Ctrl+C copy, Ctrl+V paste)

The client first asks for the IP address and tries to establish the link to the server by calling connect(). The time information received back from the server is written in a dialog window.

from tcpcom import TCPClient

def onStateChanged(state, msg):
    print(state, msg)
    if state == TCPClient.MESSAGE:
        msgDlg("Server reports local date/time: " + msg)
    if state == TCPClient.CONNECTION_FAILED:
        msgDlg("Server " + host + " not available")

host = inputString("Time Server IP Address?")
port = 5000
client = TCPClient(host, port, stateChanged = onStateChanged)
client.connect()
Highlight program code (Ctrl+C copy, Ctrl+V paste)

 

 

MEMO

 

The server and the client implement the event model with a callback function onStateChanged(state, msg). The two parameters provide important information about the event. Make sure that you finish the server with terminate(), in order to release the IP port.

 

 

ECHO-SERVER

 

The next training session is famous because it shows the archetype of a client-server communication. The server makes nothing else than sending back the non-modified message received from the client. For this reason it is called an echo server. The system can be easily extended so that the server analyzes the message received from the client and returns a tailored response. This is exactly the concept of all WEB servers, because they reply to a HTTP request issued by the client browser.

You first code the echo server and start it immediately. As you see, the code differs only slightly from the time server. For illustration and debugging purposes you display the state and msg parameter values in the console window.

from tcpcom import TCPServer

def onStateChanged(state, msg):
    print(state, msg)
    if state == TCPServer.MESSAGE:
        server.sendMessage(msg)

port = 5000
server = TCPServer(port, stateChanged = onStateChanged)             
msgDlg("Echo Server running. OK to stop")
server.terminate()
Highlight program code (Ctrl+C copy, Ctrl+V paste)

For the client you invest a bit more effort by using the EntryDialog class to display a non-modal dialog to show status information. Also in other contexts the EntryDialog is very convenient, since you can easily add editable and non-editable text fields and other GUI elements such as various types of buttons and sliders.

from tcpcom import TCPClient
from entrydialog import *
import time

def onStateChanged(state, msg):
    print(state, msg)
    if state == TCPClient.MESSAGE:
        status.setValue("Reply: " + msg)
    if state == TCPClient.DISCONNECTED:
        status.setValue("Server died")
        
def showStatusDialog():
    global dlg, btn, status
    status = StringEntry("Status: ")
    status.setEditable(False)
    pane1 = EntryPane(status)
    btn = ButtonEntry("Finish")
    pane2 = EntryPane(btn)
    dlg = EntryDialog(pane1, pane2)
    dlg.setTitle("Client Information")
    dlg.show()
        
host = "localhost"
port = 5000
showStatusDialog()
client = TCPClient(host, port, stateChanged = onStateChanged)
status.setValue("Trying to connect to " + host + ":" + str(port) + "...")
time.sleep(2)
rc = client.connect()
if rc: 
    time.sleep(2)
    n = 0
    while not dlg.isDisposed():
        if client.isConnected():
            status.setValue("Sending: " + str(n))
            time.sleep(0.5)
            client.sendMessage(str(n), 5)  # block for max 5 s
            n += 1
        if btn.isTouched():
            dlg.dispose()
        time.sleep(0.5)    
    client.disconnect()
else:
    status.setValue("Connection failed.")
Highlight program code (Ctrl+C copy, Ctrl+V paste)

 

MEMO

 

In the client program you use the function sendMessage(msg, timeout) with an additional timeout parameter. The call is blocking for a maximum time interval (in seconds) until the server sends back a response. sendMessage() returns either the server's response or None, if no response is received within the given timeout.

It is important to know the difference between modal and non-modal (modeless) dialog boxes. While the modal window blocks the program until the dialog is closed, with a modeless dialog the program continues, so that at any time status information can be displayed and user input read by the running program.

 

 

TWO PERSONS ONLINE GAME WITH TURTLE GRAPHICS

 

"Sinking Boats" is a popular game between two people, in which the memory plays an important role. The playing boards of each player is a one or two dimensional arrangement of grid cells where vessels are placed. They occupy one or multiple cells and have different values depending on the type of the ship.  By turns, each player denotes a cell where, so to speak, a bomb is dropped. If the cell contains a portion of a ship, it will be sunk, so removed from the board and its value credited to the aggressor. If one of the players has no more ships, the game ends and the player with the greater credit wins.

In the simplest version, the ships are displayed as colored square cells in a one-dimensional array. All ships are of equal rank. The winner is the player who has first eliminated all enemy ships.

As for the game logic, the client and server programs are largely identical. To show the board, it is sufficient to use elementary drawing operations from turtle graphics that you know for a long time. You select a cell numbering from 1 to 10 and you have to produce 4 different random numbers out of 1..10 for the random selection of the ships. To do so, is is elegant to use the library function random.sample(). To launch a bomb, you send the full Python command to the partner who executes it with exec(). (Such a dynamic code execution is only possible in a few programming languages.) To find out whether there has been a hit, you test the background color with getPixelColorStr().

In the game, the two players are equal, but their programs vary slightly, depending on who is server and and who is client. In addition, the server must start first.

from gturtle import *
from tcpcom import TCPServer
from random import sample 

def initGame():
    clear("white")
    for x in range(-250, 250, 50):   
        setPos(x, 0)
        setFillColor("gray")
        startPath()
        repeat 4:
            forward(50)
            right(90)
        fillPath()  

def createShips():
    setFillColor("red")
    li = sample(range(1, 10), 4) # 4 unique random numbers 1..10
    for i in li:       
        fill(-275 + i * 50, 25)                                 
                                                                             
def onMouseHit(x, y):
    global isMyTurn
    setPos(x, y) 
    if getPixelColorStr() == "white" or isOver or not isMyTurn:
        return
    server.sendMessage("setPos(" + str(x) + "," + str(y) + ")")
    isMyTurn = False

def onCloseClicked():
    server.terminate()
    dispose()
    
def onStateChanged(state, msg):
    global isMyTurn, myHits, partnerHits
    if state == TCPServer.LISTENING:
        setStatusText("Waiting for game partner...")
        initGame()
    if state == TCPServer.CONNECTED:
        setStatusText("Partner entered my game room")
        createShips()
    if state == TCPServer.MESSAGE:        
        if msg == "hit":
            myHits += 1
            setStatusText("Hit! Partner's remaining fleet size " 
                + str(4 - myHits))
            if myHits == 4:
                setStatusText("Game over, You won!") 
                isOver = True
        elif msg == "miss":
            setStatusText("Miss! Partner's remaining fleet size " 
                + str(4 - myHits))
        else:        
            exec(msg)
            if getPixelColorStr() != "gray":
                server.sendMessage("hit")  
                setFillColor("gray")
                fill() 
                partnerHits += 1                     
                if partnerHits == 4:
                    setStatusText("Game over, Play partner won!")
                    isOver = True
                    return                                        
            else:
                server.sendMessage("miss")
            setStatusText("Make your move")         
            isMyTurn = True
    
makeTurtle(mouseHit = onMouseHit, closeClicked = onCloseClicked)
addStatusBar(30)
hideTurtle()
port = 5000
server = TCPServer(port, stateChanged = onStateChanged)
isOver = False
isMyTurn = False
myHits = 0
partnerHits = 0
Highlight program code (Ctrl+C copy, Ctrl+V paste)

The client program is almost the same:

from gturtle import *
from random import sample
from tcpcom import TCPClient

def initGame():    
    for x in range(-250, 250, 50):   
        setPos(x, 0)
        setFillColor("gray")
        startPath()
        repeat 4:
            forward(50)
            right(90)
        fillPath()    

def createShips():
    setFillColor("green")
    li = sample(range(1, 10), 4) # 4 unique random numbers 1..10
    for i in li:       
        fill(-275 + i * 50, 25)    

def onMouseHit(x, y):
    global isMyTurn 
    setPos(x, y) 
    if getPixelColorStr() == "white" or isOver or not isMyTurn:
        return     
    client.sendMessage("setPos(" + str(x) + "," + str(y) + ")")
    isMyTurn = False  

def onCloseClicked():
    client.disconnect()
    dispose()
    
def onStateChanged(state, msg):
    global isMyTurn, myHits, partnerHits    
    if state == TCPClient.DISCONNECTED:
        setStatusText("Partner disappeared")
        initGame()
    elif state == TCPClient.MESSAGE:
        if msg == "hit":
            myHits += 1
            setStatusText("Hit! Partner's remaining fleet size " 
                + str(4 - myHits))
            if myHits == 4:
                setStatusText("Game over, You won!")
                isOver = True            
        elif msg == "miss":
            setStatusText("Miss! Partner's remaining fleet size " 
                + str(4 - myHits))
        else:        
            exec(msg)
            if getPixelColorStr() != "gray":
                client.sendMessage("hit")  
                setFillColor("gray")
                fill()
                partnerHits += 1                      
                if partnerHits == 4:
                    setStatusText("Game over, Play partner won")
                    isOver = True 
                    return                            
            else:
                client.sendMessage("miss")
            setStatusText("Make your move")          
            isMyTurn = True

makeTurtle(mouseHit = onMouseHit, closeClicked = onCloseClicked)
addStatusBar(30)
hideTurtle()
initGame()
host = "localhost"
port = 5000
client = TCPClient(host, port, stateChanged = onStateChanged)
setStatusText("Client connecting...")
isOver = False
myHits = 0
partnerHits = 0
if client.connect():
    setStatusText("Connected. Make your first move!")
    createShips()
    isMyTurn = True
else:
    setStatusText("Server game room closed")
Highlight program code (Ctrl+C copy, Ctrl+V paste)



 

MEMO

 

In two players games, the players can have the same or different game situations. In the second case, which includes most card games, the game must necessarily be played with two computers, because the game situation must be kept secret.

Instead of always let the client start the game, the player to move first could be selected randomly or by negotiating.

Both programs are completed by clicking on the close button in the title bar. But then some additional "cleanup" is required like stopping the server or breaking the communication link. To do so, register the callback onCloseClicked() and the default closing process is disabled. It is now up to you, to perform the adequate operation in the callback and closing the turtle window by calling dispose(). Disregarding so, the Task Manager is the needed to get you out of a jam.

 

 

TWO PERSONS ONLINE GAME WITH GAMEGRID

 

For more complex games, it is of great advantage to refer to a game library that simplifies the code considerably. You already learned in Chapter 7 how to use GameGrid, a full-featured game engine integrated into TigerJython. By combining GameGrid with tcpcom, you can create sophisticated multi-person online games where the game partners are located anywhere in the world. For illustration you expand the "Sinking Boats" game in 2 dimensions. The game logic remains unchanged,  however you transfer now x and y coordinates of the selected cell. On the receiving side, the partner can find out if one of his ships is hit and report back a "hit" or "miss" message.

In game programming it is important to make a special effort that games proceed "reasonably", even if the two players behave somewhat unreasonably. So the firing of bombs must be disabled if it is not the player's move. When one of the programs terminates unexpectedly, the partner should be informed. Although these additional checks blow up the code, this is the hallmark of careful programming

Since a large part of the code for the server and client is the same, and  code duplication is one of the greatest sins of programming, the common code is exported into a module shiplib.py that can imported by both applications. Different behavior is taken into account by additional parameters like node  that refers to a TCPServer or TCPClient object.

Library:

# shiplib.py

from gamegrid import *

isOver = False
isMyMove = False
dropLoc = None
myHits = 0
partnerHits = 0
nbShips = 2

class Ship(Actor):
     def __init__(self):
         Actor.__init__(self, "sprites/boat.gif")

def handleMousePress(node, loc):
    global isMyMove, dropLoc
    dropLoc = loc
    if not isMyMove or isOver:
          return
    node.sendMessage("" + str(dropLoc.x) + str(dropLoc.y)) # send location
    setStatusText("Bomb fired. Wait for result...")
    isMyMove = False

def handleMessage(node, state, msg):
    global isMyMove, myHits, partnerHits, first, isOver
    if msg == "hit":
        myHits += 1
        setStatusText("Hit! Partner's fleet size " + str(nbShips - myHits) 
            + ". Wait for partner's move!")
        addActor(Actor("sprites/checkgreen.gif"), dropLoc)
        if myHits == nbShips:
            setStatusText("Game over, You won!")
            isOver = True        
    elif msg == "miss":
        setStatusText("Miss! Partner's fleet size " + str(nbShips - myHits) 
            + ". Wait for partner's move!")
        addActor(Actor("sprites/checkred.gif"), dropLoc)
    else:
        x = int(msg[0])
        y = int(msg[1])
        loc = Location(x, y)            
        bomb = Actor("sprites/explosion.gif")
        addActor(bomb, loc)
        delay(2000)
        bomb.removeSelf()
        refresh()
        actor = getOneActorAt(loc, Ship)
        if actor != None:
            actor.removeSelf()
            refresh()
            node.sendMessage("hit")
            partnerHits += 1             
            if partnerHits == nbShips:
                setStatusText("Game over! Partner won")
                isOver = True 
                return  
        else:
            node.sendMessage("miss")     
        isMyMove = True
        setStatusText("You fire!")
 
Highlight program code (Ctrl+C copy, Ctrl+V paste)

By using the external module shiplib.py the code of the server and the client becomes much simpler and clearer.

Server:

from gamegrid import *
from tcpcom import TCPServer
import shiplib

def onMousePressed(e):
     loc = toLocationInGrid(e.getX(), e.getY())
     shiplib.handleMousePress(server, loc)  

def onStateChanged(state, msg):
    global first        
    if state == TCPServer.PORT_IN_USE:
        setStatusText("TCP port occupied. Restart IDE.")
    elif state == TCPServer.LISTENING:
        setStatusText("Waiting for a partner to play")
        if first:
            first = False
        else:
            removeAllActors()
            for i in range(shiplib.nbShips):
                addActor(shiplib.Ship(), getRandomEmptyLocation())
    elif state == TCPServer.CONNECTED:
        setStatusText("Client connected. Wait for partner's move!")
    elif state == TCPServer.MESSAGE:
        shiplib.handleMessage(server, state, msg)

def onNotifyExit():
    server.terminate()
    dispose()

makeGameGrid(6, 6, 50, Color.red, False, mousePressed = onMousePressed, 
             notifyExit = onNotifyExit)
addStatusBar(30)
for i in range(shiplib.nbShips):
    addActor(shiplib.Ship(), getRandomEmptyLocation())
show()
port = 5000
first = True
server = TCPServer(port, stateChanged = onStateChanged)
shiplib.node = server
Highlight program code (Ctrl+C copy, Ctrl+V paste)

Client:

from gamegrid import *
from tcpcom import TCPClient
import shiplib

def onMousePressed(e):
     loc = toLocationInGrid(e.getX(), e.getY())
     shiplib.handleMousePress(client, loc)
     
def onStateChanged(state, msg):    
    if state == TCPClient.CONNECTED:
        setStatusText("Connection established. You fire!")
        shiplib.isMyMove = True
    elif state == TCPClient.CONNECTION_FAILED:
        setStatusText("Connection failed")
    elif state == TCPClient.DISCONNECTED:
        setStatusText("Server died")
        shiplib.isMyMove = False
    elif state == TCPClient.MESSAGE:
        shiplib.handleMessage(client, state, msg)

def onNotifyExit():
    client.disconnect()
    dispose()

makeGameGrid(6, 6, 50, Color.red, False, 
    mousePressed = onMousePressed, notifyExit = onNotifyExit)
addStatusBar(30)
for i in range(shiplib.nbShips):
    addActor(shiplib.Ship(), getRandomEmptyLocation())
show()
host = "localhost"
port = 5000
client = TCPClient(host, port, stateChanged = onStateChanged)
client.connect()
Highlight program code (Ctrl+C copy, Ctrl+V paste)

 

  Client Server
 

 

 

MEMO

 

The end of the game is a special situation that must be programmed carefully. Since it is an "emergency state", you are using a global flag isOver that is set to True when the game is over. It also raises the question of whether the game can be played again without restarting the server or client program. In this implementation, the client needs to terminate his program and the server then goes back into the LISTENING state, waiting for a new client to play again.

The programs could be improved in many ways. As general rule game programming is very attractive because there are no limits for the imagination and ingenuity of the developer. Moreover, after all your effort,  relaxing and playing the game is also lot of fun.

Until now the two playmates have to start their programs according to the rule "First, the server and then the client". To circumvent this restriction the following trick could be applied: A program always starts first as a client and tries to create a connection to the server. If this fails, it starts a server [more... Such a system is called "Peer-to-Peer Communication"] The two programs are identical in this case].

 

 

COMMUNICATION WITH HANDSHAKE

 

Even in every days life, communication between two partners need some kind of  synchronization. In particular, data may be sent only if the recipient is actually ready for the reception and further processing. Failure to observe this rule may result in data loss or even  block the programs. Different computing power of the two nodes and a variing transmission time must be considered too.

One known method to get these timing problems under control, is to provide feedback from the receiver to the transmitter, which can be compared with an amicable handshake. The process is basically simple: data is transmitted in blocks and the receiver acknowledges the correct reception and and readiness for the next block with a feedback. Only when it is received, the next block is sent. The feedback may also cause the transmitter to send the same block again [more... Different temporal behavior can be compensated by implementing buffers (Queues, FIFO)].

To demonstrate the handshake principle, your the turtle program of the server draws relatively slowly lines dictated by the client's mouse clicks. The client's turtle moves much faster. Therefore the  client must wait from click to click until the server reports back that he has completed its drawing operation and is ready to dispatch the next command.

Server:

from gturtle import *
from tcpcom import TCPServer
                                                                          
def onCloseClicked():
    server.terminate()
    dispose()
    
def onStateChanged(state, msg):
    if state == TCPServer.MESSAGE:
        li = msg.split(",")
        x = float(li[0])
        y = float(li[1])
        moveTo(x, y)
        dot(10)
        server.sendMessage("ok")
    
makeTurtle(mouseHit = onMouseHit, closeClicked = onCloseClicked)
port = 5000
server = TCPServer(port, stateChanged = onStateChanged)
Highlight program code (Ctrl+C copy, Ctrl+V paste)

Client:

from gturtle import *
from tcpcom import TCPClient

def onMouseHit(x, y):
    global isReady
    if not isReady:
        return
    isReady = False
    client.sendMessage(str(x) + "," + str(y))
    moveTo(x, y)
    dot(10)      

def onCloseClicked():
    client.disconnect()
    dispose()
    
def onStateChanged(state, msg):
   global isReady
   if state == TCPClient.MESSAGE:              
        isReady = True
 
makeTurtle(mouseHit = onMouseHit, closeClicked = onCloseClicked)

speed(-1)
host = "localhost"
port = 5000
isReady = True
client = TCPClient(host, port, stateChanged = onStateChanged)
client.connect()    
Highlight program code (Ctrl+C copy, Ctrl+V paste)

 

  Server: Client:

 

 

MEMO

 

To bring the actions of the transmitter and receiver in an orderly temporal sequence, the transmitter waits to send the next data until he receives a feedback that the receiver is ready for further processing.

 

 

EXERCISES

 
1a.

Create a client-server system in which the server provides good math skills. In an input dialog  the client can enter any function from the module math, for example sqrt(2), and gets the result as a server response that is displayed in the output window. Use the Python function exec(cmd) to execute a given command.

   
1b.

Modify your program that the server reports "illegal" if he cannot fulfill the request. Hint: Catch the exception that is thrown by exec().

   
2.

Create a remote commander: The server starts a turtle window, waiting for a commander client. In a client dialog box, you can type a turtle command that is sent to the server where the command is executed. If successful, "OK", otherwise "Fail" is sent back to the client and displayed there. After receiving this response, the commander can send the next command. Note: You can also merge together multiple commands separated by a semicolon.

   
3.

Add sound output to the two-dimensional Sinking-Boats game. Suggestion: Emit a short sound clip when firing, and when you get a hit or miss feedback. Use your skills from Chapter 4: Sound. You can use predefined sounds, but your own sounds are funnier.

   
4*.

Improve the one-dimensional Sinking-Boat game so ships are no more represented by colored cells, but with pictures that disappears when hit.

   

 

 

 

ADDITIONAL MATERIAL

 

REMOTE-SENSING WITH RASPBERRY PI

 

Client-server communications over TCP/IP play an important role in measuring and control technology. Often a measuring instrument or a robot is far away from a command center and the measurement and control data are transmitted via TCP/IP. In example 2 you already realized a remote control. Below you are using a Raspberry Pi, which acts as interface between the measuring sensors and the Internet, and sends the measurement data to a remote recording station. To simplify the system, no actual sensors are used and only the pressed or released state of a pushbutton is reported.

For the Raspberry Pi you write a measurement detection program in Python using the tcpcom module that you need to copy to the same directory on the RPi. You can either work with a keyboard and a screen directly attached to the RPi or from a remote PC using SSH, WinSCP or VNC. For further instructions, check the literature or search the Internet.

The server program is very simple: In the measuring loop you call periodically GPIO.input() to get the state of the button. Here 0 is reported when the button is pressed. Subsequently, the button state information is transferred to the client. By holding the key pressed during 3 measuring cycles, the server program terminated.

import time
import RPi.GPIO as GPIO
from tcpcom import TCPServer

def onStateChanged(state, msg):
    print("State:", state, "Msg:", msg)
P_BUTTON1 = 16 # Switch pin number
dt = 1  # 1 s period
port = 5000 # IP port

GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
GPIO.setup(P_BUTTON1, GPIO.IN, GPIO.PUD_UP)
server = TCPServer(port, stateChanged = onStateChanged)
n = 0
while True:
    if server.isConnected():
        rc = GPIO.input(P_BUTTON1)
        if rc == 0:
            server.sendMessage("pressed")
            n += 1
            if n == 3:
                break
        else:
            server.sendMessage("released")
            n = 0
        time.sleep(dt)
server.terminate()
print("Server terminated")
Highlight program code (Ctrl+C copy, Ctrl+V paste)

The client writes only the data obtained in the output window.

from tcpcom import TCPClient

def onStateChanged(state, msg):
    print("State: " + state + ". Message: " + msg)
host = "192.168.0.5"
#host = inputString("Host Address?")           
port = 5000 # IP port
client = TCPClient(host, port, stateChanged = onStateChanged)
rc = client.connect()
if rc:
    msgDlg("Connected. OK to terminate")
    client.disconnect()
Highlight program code (Ctrl+C copy, Ctrl+V paste)