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 PC 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 Tk 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 you can 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; simpler is more educational, it can be improved once it is working. I like this strategy.
I use one main window, I call it master, it could have been named anything, many books call the first window root. I create a second window totally within the master window, this is of type canvas which is a window which allows drawing graphical objects such as thick lines and rectangles. I also 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 if 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. There are also IntVars etc., for other derived types.
Here is the code, with a few lines commented out that interface with the Arduino through the PySerial library and the USB UART connected to the Arduino. With these lines commented out you can run the python code in isolation. Note I have "hardwired" the code to use COM6, you will need to change this eventually to suit your system.
# crude graphic program to allow sending rudder and sail positions to# an arduino connected to port COM6, adjust port to suit and re run# The comms code blocks, first we send 2 numbers as 4 chars# the code freezes until a string is received, no timeouts are set## Testable without comms by commenting out function write_read and# and gps.set line around line 74 and port open code, line 96#######################################################################from tkinter import *from tkinter import ttkfrom math import *import serialimport time############################## FUNCTION DEFINITIONS ##################'''def write_read(x,y):arduino.write(str(x).encode()) # sends utf-8, chars as bytesarduino.write(str(y).encode())arduino.write('\n'.encode())time.sleep(0.05)data=arduino.readline().decode().strip()return data # string'''def keypress(event): # we bind the arrow keys, rest ignored here"""Receive a keypress and move the Servos by a specified amount"""# first undraw Rudder and Sailx1=200y1=260x2=x1+50*sin((Rudder.get()-50)/100.0)y2=y1+50*cos((Rudder.get()-50)/100.0)canvas.create_line(x1,y1,x2,y2,fill='white',width=10)x1=200y1=60x2=x1+150*sin((Sail.get()-50)/100.0)y2=y1+150*cos((Sail.get()-50)/100.0)canvas.create_line(x1,y1,x2,y2,fill='white',width=6)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# Undraw and Draw Rudder on Canvas, rudder is 0 to 99, 50 = 50%x1=200y1=260x2=x1+50*sin((Rudder.get()-50)/100.0)y2=y1+50*cos((Rudder.get()-50)/100.0)canvas.create_line(x1,y1,x2,y2,fill='red',width=3)# Undraw and Draw Sail position on Canvas, 0 to 99, 50 = 50%x1=200y1=60x2=x1+150*sin((Sail.get()-50)/100.0)y2=y1+150*cos((Sail.get()-50)/100.0)canvas.create_line(x1,y1,x2,y2,fill='green',width=3)canvas.create_rectangle(175,10,225,260,width=1)#redraw boat# now send out to arduino and return GPS stringgps = StringVar()# gps.set( (write_read(int(Rudder.get()),int(Sail.get()))).ljust(40," "))gps.set("42.42424N,005.42424W,000.0,000.0")Label(master,text="Sending").place(x=150,y=40)Label(master,textvariable=Rudder).place(x=250,y=40)Label(master,textvariable=Sail).place(x=300,y=40)Label(master,text="String from GPS").place(x=150,y=60)Label(master,textvariable=gps).place(x=250,y=60)############################# MAIN CODE BEGINS #######################master = Tk()master.geometry("600x600")Port = StringVar()Port.set("COM6")Label(master, text = "Using Port ").place(x=1,y=10)Label(master, textvariable = Port).place(x=60, y=10)####arduino = serial.Serial(port=Port.get(),baudrate=9600)# setup rudder and sail controls to arrow keysRudder = 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=175,y=10)Label(master, textvariable = Rudder).place(x=275, y=10)Label(master, text = "Sail Position = ").place(x=325,y=10)Label(master, textvariable = Sail).place(x=405, y=10)# paint graphic of boat here...canvas=Canvas(master,width=400,height=400,background='white')canvas.place(x=100,y=100)canvas.create_rectangle(175,10,225,260,width=1) #draw boatmainloop() # infinite loop, stringvars, widgets & events update ok
#################################### code ends #######################
Running the program causes the screen below to appear - after you hit any of the
arrow keys, the left and right arrows affect the rudder and the up and down
arrows affect the sail position.
Click on the [x] in the top righthand corner to exit.
I will post a very very well commented version here soon that will explain the code line by line to beginning Python programmers.
No comments:
Post a Comment