We have a need to send two numbers to a remote Arduino and receive a complex text string from its GPS.
I decided to use the arrow keys on the keyboard, the right and left arrows increment and decrement one of the numbers from 0 to 99 (might change). The up and down arrow keys increment and decrement the other number. These will eventually control Rudder and sail position on a model yacht, using two servos connected to an arduino on the boat. It is connected to a RFM69HCW radio transceiver (obtained from Pimoroin.co.uk, it is sold by Adafruit in the states). A PC on the shore or support boat is wired to another arduino and that arduino also has an RFM69HCW radio transceiver. The boat arduino also has a GPS module. Future extensions will add wind sensors etc.,
To get a very basic working system I decided to use the Tkinter graphics system that is supplied with python. This allows a basic crude graphic user interface (GUI) that looks dated now. There are extensions to Tkinter and there are other graphic libraries available, but I decided to keep it simple.
In addition there are a number of ways to put graphical objects (known as widgets) on a main window. You can specify a .pack method or a .grid method. Most books will guide you through these, or just copy code and experiment. However, I used a much simpler system that uses a .place method.
The .place method is passed an x and a y value and the widget is simply placed there. This works fine until you try to resize the main window. It is also a bit tedious as you have to plan and work out where you want everything to line, I do not consider that onerous. And I don't need resizable windows.
Before presenting code let me say that I am cherry picking bits of code and ignoring others, I use a minimum of widgets, just enough to get the job done, it could be prettier.
I only use one window, I call it master, it could have been named anything, some books call the first window root. It is possible to create a second window totally within the master window, the frame widget allows this. We may need that later. I use a pulldown widget to set the comms port. I use labels and I use the .bind method to attach a function to the keys that I want to trigger an action.
That should make the code make some sense!
Also of note is that Tkinter (and most of the other GUI systems) use a different way of working from simple code. It is event driven programming. You are used to simple code where the execution flows down the page, one statement after another, with the odd loop and jump to control the flow of control.
TKinter programs initially look a bit funny, the code moves down the page but then there is a final function call. In traditional programming terms this function is entered and NEVER leaves. You might think the code would just freeze but what actually happens is that any widget defined before that final call has a number of methods attached to it and the final call creates a list of possible events and as each event happens (such as you clicking a mouse or hitting a key) that final function calls the appropriate method. After it returns you are back in that final function again. It is running an event scheduler. You can supply your own functions and these get called is they are defined as callback functions by the appropriate widget. It will make more sense once you run the program.
A further complication is that the widgets can use variables of several types but these are derived from the normal types of variable. So, whilst we might set the text attribute of a widget to some fixed text, if we want to have a string that changes we have to (a) use the textvariable attribute, not the text one. and (b) we have to use a type known as stringvar, not string. We assign values to stringvar variables using the .set(value) method and we read values out of stringvars using the .get() method. Watch out for this as it took me a while to realise this. We can trigger a function if a stringvar changes which may be useful later (I write this blog post before the project code is complete...)
Here is a demo Python/Tkinter program; (watch out for correct indenting...)
from tkinter import *
def keypress(event): # we bind the arrow keys, rest ignored here
"""Receive a keypress and move the Servos by a specified amount"""
if event.keysym == 'Up':
Sail.set(Sail.get()+1)
if Sail.get() == 100:
Sail.set(99)
elif event.keysym == 'Down':
Sail.set(Sail.get()-1)
if Sail.get() < 0:
Sail.set(0)
elif event.keysym == 'Right':
Rudder.set(Rudder.get()+1)
if Rudder.get() == 100:
Rudder.set(99)
elif event.keysym == 'Left':
Rudder.set(Rudder.get()-1)
if Rudder.get() < 0:
Rudder.set(0)
else:
pass
##########################################################
master = Tk() # every TKinter program uses this to create the first window
master.geometry("600x200") # here we use the .geometry window to set size
# Dropdown options
Ports = [ "COM3:","COM4:","COM5:","COM6:"
,"COM7:","COM8:","COM9:","COM10:"
,"COM11:","COM12:","COM13:","COM14:"]
# Selected option variable
Port = StringVar()
# Dropdown menu
OptionMenu(master, Port, *Ports).place(x=1,y=1) # place puts it in the window
# setup rudder and sail controls to arrow keys
Rudder = IntVar(master,50)
Sail = IntVar(master,60)
master.bind('<Up>',keypress)
master.bind('<Down>',keypress)
master.bind('<Left>',keypress)
master.bind('<Right>',keypress)
Label(master, text = "Rudder Position = ").place(x=25,y=40)
Label(master, textvariable = Rudder).place(x=125, y=40)
Label(master, text = "Sail Position = ").place(x=225,y=40)
Label(master, textvariable = Sail).place(x=305, y=40)
# could paint graphic of Rudder and Sail positions here...
#
#
# Got Comm port and rudder and sail position, send them...
# every time they change.
# and receive GPS position into a stringvar (so Tk updates them)
#
mainloop() # infinite loop needed, stringvars & widgets update...
