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))
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
'drivelabeland
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.