|
| 1 | +--- |
| 2 | +title: "Microbit Python Program with pySerial and GUIZero" |
| 3 | +layout: text-width-sidebar |
| 4 | + |
| 5 | +# Meta description for google and sharing |
| 6 | +meta-description: "Using GUIZero & pySerial to send commands to the microbit." |
| 7 | + |
| 8 | +############# |
| 9 | +## Options ## |
| 10 | +############# |
| 11 | + |
| 12 | +share: true |
| 13 | +author: jez |
| 14 | + |
| 15 | +################################# |
| 16 | +## Component Template Specific ## |
| 17 | +################################# |
| 18 | + |
| 19 | +video: |
| 20 | + teaser: microbit_blink_led_example |
| 21 | + |
| 22 | +video: |
| 23 | + - teaser: true |
| 24 | + |
| 25 | +# About Box is on the left of the program page |
| 26 | +about: "Using GUIZero---a learner-friendly tkinter wrapper---to create a serial connection to the microbit in Python with pySerial." |
| 27 | + |
| 28 | +# Category of howto: I2C, other, visual basic, component examples, python, data logging |
| 29 | +cats: python |
| 30 | + |
| 31 | +# Name of Component for index page |
| 32 | +simple-description: pySerial Microbit GUI |
| 33 | + |
| 34 | +date: 2016-12-23T10:20:00Z |
| 35 | +--- |
| 36 | + |
| 37 | +{:.ui .image .small .floated .right} |
| 38 | + |
| 39 | +It is possible to send commands to the microbit over a serial connection from your computer. The `REPL` function in the mu editor is simply a serial connection. For example, `display.show(Image.HAPPY)` causes a face to show on the microbit's display. |
| 40 | + |
| 41 | +There's [more about the USB serial connection on our website](http://localhost:4000/howto/microbit-serial-connection-over-usb). |
| 42 | + |
| 43 | +To send Python commands, the microbit must be running a micropython `.hex` file (eg, created in mu). |
| 44 | + |
| 45 | +In this example, a Python GUI program sends commands to the microbit flashed in mu with a blank script. |
| 46 | + |
| 47 | +### About the Program |
| 48 | + |
| 49 | +This program uses two Python modules: pySerial and GUIZero. |
| 50 | + |
| 51 | +* pySerial handles the serial communication. It scans for the microbit and creates a serial connection. |
| 52 | + |
| 53 | +* GUIZero is for drawing the GUI. It is a learner-friendly wrapper for TKInter. |
| 54 | + |
| 55 | +#### pySerial |
| 56 | + |
| 57 | +Handles the serial connection to the microbit. |
| 58 | + |
| 59 | +##### `find_microbit_comport()` |
| 60 | + |
| 61 | +``` |
| 62 | +def find_microbit_comport(): |
| 63 | + ports = list(list_ports.comports()) |
| 64 | + for p in ports: |
| 65 | + if (p.pid == 516) and (p.vid == 3368): |
| 66 | + return str(p.device) |
| 67 | +``` |
| 68 | + |
| 69 | +This function returns a string of the microbit's COM port. On Windows this might be `COM3`, on Linux `/dev/ttyS2`. |
| 70 | + |
| 71 | +The `product ID` & `vendor ID` of each attached device is evaluated. If it is the same as the ones on a microbit (`516` & `3368`) then return the device name (eg COM4). |
| 72 | + |
| 73 | +`serial.tools.list_ports` must be imported. |
| 74 | + |
| 75 | +##### Initialise |
| 76 | + |
| 77 | +Create a new instance of the Serial class as `ser`: |
| 78 | + |
| 79 | +``` |
| 80 | +ser = serial.Serial() |
| 81 | +``` |
| 82 | + |
| 83 | +Set `ser` baud rate to 115200. This is the default baud rate of the microbit: |
| 84 | + |
| 85 | +``` |
| 86 | +ser.baudrate = 115200 |
| 87 | +``` |
| 88 | + |
| 89 | +Set the `ser` COM port to the one found by `find_microbit_comport()` . |
| 90 | + |
| 91 | +``` |
| 92 | +ser.port = find_microbit_comport() |
| 93 | +``` |
| 94 | + |
| 95 | +##### Writing Data |
| 96 | + |
| 97 | +Before writing data the COM port must be opened. `Serial.open()` throws an exception if a connection cannot be made. |
| 98 | + |
| 99 | +``` |
| 100 | +ser.open() |
| 101 | +``` |
| 102 | + |
| 103 | +With the COM port open, we can write a command: |
| 104 | + |
| 105 | +``` |
| 106 | +ser.write("display.show(Image.HAPPY) \r".encode() ) |
| 107 | +
|
| 108 | +``` |
| 109 | + |
| 110 | +The `\r` is a carriage return. It tells `REPL` on the microbit that return has been pressed and it should execute the line sent. |
| 111 | + |
| 112 | +`.encode()` turns the string into bytes. |
| 113 | + |
| 114 | +### GUI Zero |
| 115 | + |
| 116 | +GUI Zero is a learner-friendly wrapper for `Tkinter`. It allows beginners to easily create GUIs for the program by removing many of the complexities of `Tkinter`. |
| 117 | + |
| 118 | +It's still at the beginning phase and I'm hoping this is the first program using it! |
| 119 | + |
| 120 | +##### App window |
| 121 | + |
| 122 | +The program's window is an instance of the `App` class. In this example it's assigned to `app`. |
| 123 | + |
| 124 | +``` |
| 125 | +app = App() |
| 126 | +
|
| 127 | +# The app window can be modified by with parameters. |
| 128 | +# EG app = App(title="My Microbit Program") for a title. |
| 129 | +# The width, height, and other things can be changed. |
| 130 | +``` |
| 131 | + |
| 132 | +[App() GUIZero docs](https://lawsie.github.io/guizero/pushbutton/) |
| 133 | + |
| 134 | + |
| 135 | +##### Create Button |
| 136 | + |
| 137 | +``` |
| 138 | +connect_button = PushButton(app, text="Connect", command=connect) |
| 139 | +
|
| 140 | +# create an instance of the PushButton class. |
| 141 | +# The GUI parent (or master) is app (main app window). |
| 142 | +# Text is "Connect" |
| 143 | +# When it is clicked, connect() is executed |
| 144 | +``` |
| 145 | + |
| 146 | +[PushButton() GUIZero docs](https://lawsie.github.io/guizero/pushbutton/) |
| 147 | + |
| 148 | +##### Display the GUI |
| 149 | + |
| 150 | +The final line is `app.display()`. This renders the GUI we have set up. |
| 151 | + |
| 152 | +##### Note About Box() |
| 153 | + |
| 154 | +`Box()` in GUIZero extends the `Frame` class in `Tkinter`. It allows GUI elements such as `PushButton()` or `ButtonGroup()` to be arranged together. |
| 155 | + |
| 156 | +``` |
| 157 | +button_box = Box(app) |
| 158 | +``` |
| 159 | + |
| 160 | +In the example below, the `connect` and `disconnect` sit within the `button_box` instance of `Box()` |
| 161 | + |
| 162 | +``` |
| 163 | +connect_button = PushButton(button_box, text="Connect", command=connect) |
| 164 | +``` |
| 165 | + |
| 166 | +This allows the collection buttons to sit together in the GUI. |
| 167 | + |
| 168 | +[Box() GUIZero docs](https://lawsie.github.io/guizero/box/) |
| 169 | + |
| 170 | +### Final Code |
| 171 | + |
| 172 | +{% highlight python %} |
| 173 | + |
| 174 | +from guizero import * |
| 175 | + |
| 176 | +import serial |
| 177 | +from serial.serialutil import SerialException |
| 178 | +from serial.tools import list_ports |
| 179 | + |
| 180 | +def connect(): |
| 181 | + try: |
| 182 | + ser.open() |
| 183 | + ser.write("from microbit import * \r".encode()) |
| 184 | + except SerialException: |
| 185 | + alerts.error(app, "No Connection! Unplug microbit and try again") |
| 186 | + |
| 187 | +def disconnect(): |
| 188 | + ser.close() |
| 189 | + |
| 190 | +""" |
| 191 | +send_data() |
| 192 | +run when send_button is clicked. Gets data from faces_to_send_list |
| 193 | +and writes to serial port as bytes |
| 194 | + |
| 195 | +1. .get selected face from faces_to_send_list |
| 196 | +2. convert to bytes |
| 197 | +3. .write to serial port |
| 198 | + |
| 199 | +Alternatively, 1 + 2 + 3 all on the same: |
| 200 | +ser.write((faces_to_send_list.get() + '\r').encode()) |
| 201 | +""" |
| 202 | + |
| 203 | +def send_data(): |
| 204 | + command_to_send = faces_to_send_list.get() + '\r' |
| 205 | + command_to_send_bytes = command_to_send.encode() |
| 206 | + try: |
| 207 | + ser.write(command_to_send_bytes) |
| 208 | + except SerialException: |
| 209 | + alerts.error(app, "Could not Send. Connected?") |
| 210 | + |
| 211 | +""" |
| 212 | +find_microbit_comport() |
| 213 | +returns COM port / device the microbit is attached to. |
| 214 | + |
| 215 | +For each com port on the computer, check whether the |
| 216 | +attached device's product ID and vendor ID match |
| 217 | +the microbits. |
| 218 | +""" |
| 219 | + |
| 220 | +def find_microbit_comport(): |
| 221 | + ports = list(list_ports.comports()) |
| 222 | + for p in ports: |
| 223 | + if (p.pid == 516) and (p.vid == 3368): |
| 224 | + return str(p.device) |
| 225 | + |
| 226 | +# Main Program Begins |
| 227 | + |
| 228 | +# serial ports |
| 229 | +ser = serial.Serial() |
| 230 | +ser.baudrate = 115200 |
| 231 | +ser.port = find_microbit_comport() |
| 232 | + |
| 233 | + |
| 234 | +# Window setup |
| 235 | +app = App(layout='grid', |
| 236 | + height='300', |
| 237 | + width='200', |
| 238 | + title="Python Microbit Smile") |
| 239 | + |
| 240 | +# button_box and its elements |
| 241 | +button_box = Box(app, grid=[0, 0]) |
| 242 | + |
| 243 | +connect_button = PushButton(button_box, |
| 244 | + text="Connect", |
| 245 | + command=connect, |
| 246 | + padx=3, |
| 247 | + pady=3) |
| 248 | + |
| 249 | +disconnect_button = PushButton(button_box, |
| 250 | + text="Disconnect", |
| 251 | + command=disconnect, |
| 252 | + padx=3, |
| 253 | + pady=3) |
| 254 | + |
| 255 | +# faces box and its elements |
| 256 | +face_box = Box(app, grid=[0, 1]) |
| 257 | + |
| 258 | +faces_to_send_list = ButtonGroup(face_box, [ |
| 259 | + ["Happy", "display.show(Image.HAPPY)"], |
| 260 | + ["Sad", "display.show(Image.SAD)"], |
| 261 | + ["Silly", "display.show(Image.SILLY)"], |
| 262 | + ["Yes", "display.show(Image.YES)"], |
| 263 | + ["No", "display.show(Image.NO)"], |
| 264 | + ["Pacman", "display.show(Image.PACMAN)"], |
| 265 | + ["Cow", "display.show(Image.COW)"] |
| 266 | + ], |
| 267 | + "display.show(Image.HAPPY)" |
| 268 | + ) |
| 269 | + |
| 270 | +send_button = PushButton(face_box, |
| 271 | + text="Send", |
| 272 | + command=send_data) |
| 273 | + |
| 274 | + |
| 275 | +# render app window |
| 276 | +app.display() |
| 277 | + |
| 278 | +{% endhighlight %} |
0 commit comments