SocketServer used to control PiBot remotely (python)
Question: this is my first question! (despite using the site to find most of the answers
to programming questions i've ever had)

I have created a PiBotController which i plan to run on my laptop which i want
to pass the controls (inputs from arrow keys) to the raspberry pi controlling
my robot. Now the hardware side of this isn't the issue i have created a
program that responds to arrow key inputs and i can control the motors on the
pi through a ssh connection.

Searching online i found the following basic server and client code using
socketserver which i can get to work sending a simple string.

Server:

    
    
    import socketserver
    
    class MyTCPHandler(socketserver.BaseRequestHandler):
    """
    The RequestHandler class for our server.
    
    It is instantiated once per connection to the server, and must
    override the handle() method to implement communication to the
    client.
    """
    
        def handle(self):
            # self.request is the TCP socket connected to the client
            self.data = self.request.recv(1024).strip()
            print("{} wrote:".format(self.client_address[0]))
            print(self.data)
            # just send back the same data, but upper-cased
            self.request.sendall(self.data.upper())
    
    if __name__ == "__main__":
        HOST, PORT = "localhost", 9999
    
    # Create the server, binding to localhost on port 9999
    server = socketserver.TCPServer((HOST, PORT), MyTCPHandler)
    
    # Activate the server; this will keep running until you
    # interrupt the program with Ctrl-C
    server.serve_forever()
    

Client:

    
    
    import socket
    import sys
    
    HOST, PORT = "192.168.2.12", 9999
    data = "this here data wont send!! "
    
    
    # Create a socket (SOCK_STREAM means a TCP socket)
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    try:
        # Connect to server and send data
        sock.connect((HOST, PORT))
        sock.sendall(bytes(data + "\n", "utf-8"))
    
        # Receive data from the server and shut down
        received = str(sock.recv(1024), "utf-8")
    finally:
        sock.close()
    
    print("Sent:     {}".format(data))
    print("Received: {}".format(received))
    

Now this works fine and prints the results both on my Raspberry Pi (server)
and on my laptop (client) however i have tried multiple times to combine it
into a function that activates along with my key press' and releases' like i
have in my controller.

PiBotController

    
    
    #import the tkinter module for the GUI and input control
    try:
        # for Python2
        import Tkinter as tk
        from Tkinter import *
    except ImportError:
        # for Python3
        import tkinter as tk
        from tkinter import *
    
    import socket
    import sys
    
    #variables
    Drive = 'idle'
    Steering = 'idle'
    
    
    
    
    #setting up the functions to deal with key presses
    def KeyUp(event):
        Drive = 'forward'
        drivelabel.set(Drive)
        labeldown.grid_remove()
        labelup.grid(row=2, column=2)
    def KeyDown(event):
        Drive = 'reverse'
        drivelabel.set(Drive)
        labelup.grid_remove()
        labeldown.grid(row=4, column=2)
    def KeyLeft(event):
        Steering = 'left'
        steeringlabel.set(Steering)
        labelright.grid_remove()
        labelleft.grid(row=3, column=1)
    def KeyRight(event):
        Steering = 'right'
        steeringlabel.set(Steering)
        labelleft.grid_remove()
        labelright.grid(row=3, column=3)
    def key(event):
        if event.keysym == 'Escape':
            root.destroy()
    
    #setting up the functions to deal with key releases
    def KeyReleaseUp(event):
        Drive = 'idle'
        drivelabel.set(Drive)
        labelup.grid_remove()
    def KeyReleaseDown(event):
        Drive = 'idle'
        drivelabel.set(Drive)
        labeldown.grid_remove()
    def KeyReleaseLeft(event):
        Steering = 'idle'
        steeringlabel.set(Steering)
        labelleft.grid_remove()
    def KeyReleaseRight(event):
        Steering = 'idle'
        steeringlabel.set(Steering)
        labelright.grid_remove()
    
    #connection functions
    def AttemptConnection():
        connectionmessagetempvar = connectionmessagevar.get()
        connectionmessagevar.set(connectionmessagetempvar + "\n" + "Attempting to        connect...") 
    
    def transmit(event):
        HOST, PORT = "192.168.2.12", 9999
        data = "this here data wont send!! "
    
    
        # Create a socket (SOCK_STREAM means a TCP socket)
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
        try:
             # Connect to server and send data
             sock.connect((HOST, PORT))
             sock.sendall(bytes(data + "\n", "utf-8"))
    
             # Receive data from the server and shut down
             received = str(sock.recv(1024), "utf-8")
        finally:
             sock.close()
    
        print("Sent:     {}".format(data))
        print("Received: {}".format(received))
    
    
    
    
    
    #setting up GUI window        
    root = tk.Tk()
    root.minsize(300,140)
    root.maxsize(300,140)
    root.title('PiBot Control Centre')
    root.grid_columnconfigure(0, minsize=50)
    root.grid_columnconfigure(1, minsize=35)
    root.grid_columnconfigure(2, minsize=35)
    root.grid_columnconfigure(3, minsize=35)
    root.grid_rowconfigure(2, minsize=35)
    root.grid_rowconfigure(3, minsize=35)
    root.grid_rowconfigure(4, minsize=35)
    root.configure(background='white')
    root.option_add("*background", "white")
    
    
    
    
    #set up the labels to display the current drive states
    drivelabel = StringVar()
    Label(root, textvariable=drivelabel).grid(row=0, column=1, columnspan=2)
    steeringlabel = StringVar()
    Label(root, textvariable=steeringlabel).grid(row=1, column=1, columnspan=2)
    Label(root, text="Drive: ").grid(row=0, column=0, columnspan=1)
    Label(root, text="Steering: ").grid(row=1, column=0, columnspan=1)
    
    #set up the buttons and message for connecting etc..
    messages=tk.Frame(root, width=150, height=100)
    messages.grid(row=1,column=4, columnspan=2, rowspan=4)
    
    
    connectionbutton = Button(root, text="Connect", command=AttemptConnection)
    connectionbutton.grid(row=0, column=4)
    connectionmessagevar = StringVar()
    connectionmessage = Message(messages, textvariable=connectionmessagevar, width=100, )
    connectionmessage.grid(row=1, column=1, rowspan=1, columnspan=1)
    disconnectionbutton = Button(root, text="Disconnect")
    disconnectionbutton.grid(row=0, column=5)
    
    
    
    
    
    
    
    #pictures
    photodown = PhotoImage(file="down.gif")
    labeldown = Label(root, image=photodown)
    labeldown.photodown = photodown
    #labeldown.grid(row=4, column=1)
    
    photoup = PhotoImage(file="up.gif")
    labelup = Label(root, image=photoup)
    labelup.photoup = photoup
    #labelup.grid(row=2, column=1)
    
    photoleft = PhotoImage(file="left.gif")
    labelleft = Label(root, image=photoleft)
    labelleft.photoleft = photoleft
    #labelleft.grid(row=3, column=0)
    
    photoright = PhotoImage(file="right.gif")
    labelright = Label(root, image=photoright)
    labelright.photoright = photoright
    #labelright.grid(row=3, column=2)
    
    photoupleft = PhotoImage(file="upleft.gif")
    labelupleft = Label(root, image=photoupleft)
    labelupleft.photoupleft = photoupleft
    #labelupleft.grid(row=2, column=0)
    
    photodownleft = PhotoImage(file="downleft.gif")
    labeldownleft = Label(root, image=photodownleft)
    labeldownleft.photodownleft = photodownleft
    #labeldownleft.grid(row=4, column=0)
    
    photoupright = PhotoImage(file="upright.gif")
    labelupright = Label(root, image=photoupright)
    labelupright.photoupright = photoupright
    #labelupright.grid(row=2, column=2)
    
    photodownright = PhotoImage(file="downright.gif")
    labeldownright = Label(root, image=photodownright)
    labeldownright.photodownright = photodownright
    #labeldownright.grid(row=4, column=2)
    
    
    
    
    #bind all key presses and releases to the root window
    root.bind_all('<Key-Up>', KeyUp)
    root.bind_all('<Key-Down>', KeyDown)
    root.bind_all('<Key-Left>', KeyLeft)
    root.bind_all('<Key-Right>', KeyRight)
    
    root.bind_all('<KeyRelease-Up>', KeyReleaseUp)
    root.bind_all('<KeyRelease-Down>', KeyReleaseDown)
    root.bind_all('<KeyRelease-Left>', KeyReleaseLeft)
    root.bind_all('<KeyRelease-Right>', KeyReleaseRight)
    
    root.bind_all('<Key>', key)
    root.bind_all('<Key>', transmit)
    
    
    
    
    #set the labels to an initial state
    steeringlabel.set('idle')
    drivelabel.set('idle')
    connectionmessagevar.set ('PiBotController Initiated')
    
    #initiate the root window main loop
    root.mainloop()
    

this program compiles fine but then doesn't send any data to the server? (i'm
aware its still just sending a string but i thought id start with something
easy... and well evidently i got stuck so it is probably for the best)

Any suggestions to make it work just sending the string or sending the
varibales drive and steering every time they change would be greatly
appreciated.

Dave xx

_**EDIT_**

here is the transmit function i got to it work in the sense it sends data
whenever i do a key press/release (like i wanted before) however it only sends
the initial setting for the variables of 'idle'. Looking at the code now as
well i think i should probably take the host and port info and creating a
socket connection out of the function that runs every time? but im not sure so
here is what i have right now anyway.

    
    
    def transmit():
        HOST, PORT = "192.168.2.12", 9999
        DriveSend = drivelabel.get
        SteeringSend = steeringlabel.get
    
    
        # Create a socket (SOCK_STREAM means a TCP socket)
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
        try:
            # Connect to server and send data
            sock.connect((HOST, PORT))
            sock.sendall(bytes(Drive + "\n", "utf-8"))
            sock.sendall(bytes(Steering + "\n", "utf-8"))
    
            # Receive data from the server and shut down
            received = str(sock.recv(1024), "utf-8")
        finally:
            sock.close()
    
        print("Sent:     {}".format(Steering))
        print("Sent:     {}".format(Drive))
        print("Received: {}".format(received))
    


Answer: The problem is that when Tkinter catches a key event, it triggers the more
specific binding first (for example 'Key-Up'), and the event is never passed
to the more general binding ('Key'). Therefore, when you press the 'up' key,
KeyUp is called, but transmit is never called.

One way to solve this would be to just call transmit() within all the callback
functions (KeyUp, KeyDown, etc).

For example, KeyUp would become

    
    
    def KeyUp(event):
        Drive = 'forward'
        drivelabel.set(Drive)
        labeldown.grid_remove()
        labelup.grid(row=2, column=2)
        transmit()
    

Then you can get rid of the event binding to 'Key'.

Another option would be to make "Drive" and "Steering" into Tkinter.StringVar
objects, then bind to write events using "trace", like this:

    
    
    Drive = tk.StringVar()
    Drive.set('idle')
    Drive.trace('w', transmit)
    

Note that `trace` sends a bunch of arguments to the callback, so you'd have to
edit `transmit` to accept them.

**EDIT**

Ok, I see the problems - there are three.

1\. When you write

    
    
    Drive = 'forward'
    

in your callback functions, you're **not** setting the variable `Drive` in
your module namespace, you're setting `Drive` in the local function namespace,
so the module-namespace `Drive` never changes, so when `transmit` accesses it,
it's always the same.

2\. In `transmit`, you write

    
    
    DriveSend = drivelabel.get
    SteeringSend = steeringlabel.get
    

This is a good idea, but you're just referencing the functions, not calling
them. You need

    
    
    DriveSend = drivelabel.get()
    SteeringSend = steeringlabel.get()
    

3\. In `transmit`, the values you send through the socket are the module-level
variables `Drive` and `Steering` (which never change as per problem #1),
rather than `DriveSend` and `SteeringSend`.

**Solution:**

I would recommend doing away with all the `Drive` and `Steering` variables
entirely, and just using the `StringVars` 'drivelabel`and`steeringlabel`. Thus
your callbacks can become:

    
    
    def KeyUp(event):
    #    Drive = 'forward'   (this doesn't actually do any harm, but to avoid confusion I'd just get rid of the Drive variables altogether)
        drivelabel.set('forward')
        labeldown.grid_remove()
        labelup.grid(row=2, column=2)
    

(and so on for the rest of the callbacks) and your transmit function will
become

    
    
    def transmit():
        HOST, PORT = "192.168.2.12", 9999
        DriveSend = drivelabel.get()        # Note the ()
        SteeringSend = steeringlabel.get()
    
        # Create a socket (SOCK_STREAM means a TCP socket)
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
        try:
            # Connect to server and send data
            sock.connect((HOST, PORT))
            sock.sendall(bytes(DriveSend + "\n", "utf-8"))    # Note Drive ==> DriveSend
            sock.sendall(bytes(SteeringSend + "\n", "utf-8")) # Note Steering ==> SteeringSend
    
            # Receive data from the server and shut down
            received = str(sock.recv(1024), "utf-8")
        finally:
            sock.close()
    
        print("Sent:     {}".format(SteeringSend))   # Note Steering ==> SteeringSend
        print("Sent:     {}".format(DriveSend))      # Note Drive ==> DriveSend
        print("Received: {}".format(received))
    

**modified solution (from OP):**

Since playing around with this method for a little while i found that
constantly changing the variables every 100ms due to key being held down is
troublesome and causes isues with the smoothness of the motor control when i
am for example just driving forward. to fix this i used the following edit
into each function

    
    
    def KeyUp(event):
        if drivelabel.get() == "forward":
            pass
        else:
            drivelabel.set("forward")
            labeldown.grid_remove()
            labelup.grid(row=2, column=2)
            transmit()
            print (drivelabel.get())
    

The code now checks if the varibale is already set to the relevent direction
if it is it does nothing, otherwise it modifies it. the print line is just
there for me to check it was working properly and could be removed or
commented out.

