The guide presented here deals primarially with Python. Some of you may prefer to use a language like Node.js. Happily, your kind classmate Billy Kwok has written a great guide about how do do that here!
If you write a guide for a different language I'd love to put it here as well.
In last week’s Thursday lab we discussed making our Arduino programs interactive. The approach outlined there was limited though as we could only perform that interaction from inside of the Arduino IDE. In practice and in your projects we want to escape the IDE and thus increase the amount of digital real estate that our tangible interfaces have access to.
In today’s Thursday bonus lab we will perform this escape act and start communicating with our Arduinos from Python programs. Having done that we will then explore how we can expand the types of user input that we can use to control our Arduino.
As we saw last week, we can send messages to our Arduino via the IDE’s serial monitor. For example, with the code below loaded onto our Arduino we can send messages to the device which it will send back by opening Tools -> Serial Monitor
in the context menu and typing into the top bar.
void setup() {
Serial.begin(9600);
}
void loop() {
if (Serial.available()) {
String message = Serial.readStringUntil('\n');
Serial.print(": " + message + "\n");
}
}
An interesting feature of this ‘serial’ style of interaction is that it is neither unique to Arduinos nor the Arduino IDE. We can communicate with our Arduino via this method using essentially whatever language or method we’d like. Let’s take a look at how we might do so using the Python programming language.
Python is a nice choice for this task as its syntax is designed to be relatively human readable even if you don’t know the language and it has an extensive echosystem of libraries to interface with other systems.
Start by following the directions here to install Python if you do not have it installed already. Having done that you ought to be able to open the Terminal / Command Prompt application on macOS / Windows and run python by typing python
.
Here is an example python session in my terminal where I ask the language to print hello!
to the screen:
$ python3
>>> print('hello!')
hello!
>>> exit()
Having installed python we can install a library to interface with the Arduino’s serial port by running the following command in our terminals:
$ python3 -m pip install pyserial
Having gotten set up, go ahead and upload the “echo” Arduino sketch from the introduction to this section to your Arduino board. Then copy the following python program into a file called pyserial.py
on your computer:
import serial
def get_ports():
"""List all of the avaliable ports on the device."""
import serial.tools.list_ports
return serial.tools.list_ports.comports()
def prompt_for_and_get_port():
"""Prompts the user to select a port. Returns the selected
one.
Input is expected to be a number in [0, num_ports) and is
read from standard in. An input outside of that range will result
in re-prompting until a valid input is provided.
"""
ports = get_ports()
for index, port in enumerate(ports):
print("{}) {}\t{}".format(index, port.device, port.description))
selection = int(input("please input the port number that your port is on: "))
while selection < 0 or selection >= len(ports):
print("error: {} is not between 0 and {}".format(selection, len(ports) - 1))
selection = int(input("please input the port number that your port is on: "))
return ports[selection]
def prompt_for_and_get_serial(baudrate=9600):
"""Prompts the user to select a port and returns a serial object
connected to that port.
By default the returned serial object uses a 9600 baudrate but
this can be modified by passing one to the function.
"""
port = prompt_for_and_get_port()
return serial.Serial(port=port.device, baudrate=baudrate)
serial = prompt_for_and_get_serial()
while True:
send = input('>> ')
serial.write(bytes(send, 'utf-8'))
print(serial.readline().decode('utf-8')[:-1])
If you are using a Mac you can do this by copying the above code and running pbpaste > pyserial.py
inside of your Terminal window. If you are on Windows and you are comfortable using a text editor you may create the file like that.
The program that you’re looking at exposes a function called prompt_for_and_get_serial
which when run will ask the user what port their Arduino is connected to and open a serial connection to the Arduino there. It will then take input from the user, send that input to the Arduino, and display the Arduino’s response.
Here is an example of an interaction with the program. Note that I run python3 pyserial.py
to start the program.
$ python3 pyserial.py
0) /dev/cu.blueberry-SPPDev-11 n/a
1) /dev/cu.blueberry-SPPDev-4 n/a
2) /dev/cu.Bluetooth-Incoming-Port n/a
3) /dev/cu.iPhone-WirelessiAPv2 n/a
4) /dev/cu.elzekeo-SPPDev n/a
5) /dev/cu.usbmodem14201 Arduino Uno
please input the port number that your port is on: 5
>> hello
: hello
>> goodbye
: goodbye
Having done that we have successfully escaped the Arduino IDE and are controlling the Arduino from a python program! You can find the complete source code with some documentation for this program here.
Recall from the Wednesday lab item two for your homework:
Change the code so that you can control the RGB values with multiple key presses. For example, pressing ‘r’ 5 times will set the brightness to 50% (or brightness = 127) and pressing ‘r’ 10 times will set it to 100% (or brightness = 255)
Performing this interaction in the Arduino IDE produces a less than desirable result. In order to send data to the Arduino via the IDE you are required to type your input into the text box and then press enter. This effectively transforms the interaction from ‘press r five times’ to ‘press r followed by enter 5 times.’
An advantage of using our Python script is that we no longer have this constraint. We can send input to the user whenever we would like with serial.write()
.
In order to do this I’ve provided a python script which exposes a getch
function. getch
reads a character from input on the terminal without requiring that the user presses enter. You can find the source code here along with instructions on using it at the top of the file.
Here’s an example of a program that sends a number to the Arduino representing the amount of time that has passed between when the user last pressed a key and the current key press. This program uses the functions from the two provided python programs to do this:
def get_time():
return round(time.time() * 1000)
serial = prompt_for_and_get_serial()
lastPress = get_time()
while True:
prompt = getch()
if prompt == '.':
break
pressTime = get_time()
elapsed = 3 * (pressTime - lastPress) // 4
if elapsed < 255:
serial.write(bytes(str(255 - elapsed), 'utf-8'))
# Write a space so that the Arduino can tell the sent numbers
# apart.
serial.write(b' ')
lastPress = pressTime
Use the example code here to communicate with the Arduino. Can you enable an interaction not otherwise possible with just the Arduino IDE?
Some ideas:
Serial.parseInt()
may be helpful here) and then use python to send some information about your computers internet connection to the Arduino.